[BE] TODO Layer - Controller layer

김주언·2022년 6월 9일
0

TODO LIST

목록 보기
3/18
post-thumbnail
  • 레이어드 아키텍처 패턴
  • REST 아키텍처 패턴
  • 스프링 어노테이션
  • JPA와 스프링 Data JPA

백엔드 서비스 아키텍처

레이어드 아키텍처 패턴

스프링 프로젝트 내부에서 코드를 분배하고 관리하는 방식. 애플리케이션 구성요소들을 수평적으로 분리한다


Model, Entity, DTO

보통 자바 클래스는 기능 수행 클래스와 데이터 저장 클래스로 구분할 수 있다.
기능 수행 클래스는 컨트롤러, 서비스, 퍼시스턴스처럼 로직을 수행한다.
기능 없이 DB에서 반환된 데이터를 담기 위한 클래스들은 기능에 따라 Entity, Model, DTO 등으로 부른다.

모델과 엔티티

이 프로젝트는 모델과 엔티티를 한 클래스에 구현한다.
즉 모델은 비즈니스 데이터 담는 역할 + DB의 테이블과 스키마 표현 역할

TodoEntity.java

package com.example.demo.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class TodoEntity {
    private String id;
    private String userId;
    private String title;
    private boolean done;
    
}

@Builder

디자인 패턴 중 빌더 패턴 (생성 패턴), 롬복 사용시 빌더 클래스를 따로 개발하지 않아도 된다. 빌더 패턴 사용은 생성자를 이용해 오브젝트를 생성하는 것과 비슷하다.

// 롬복이 이러한 코드를 자동 생성해준다
ToodEntity todo = TodoEntity.builder()
				  .id("id값")
                  .userId("userID값")
                  .title("제목")
                  .build()

@NoArgsConstructor

매개변수가 없는 생성자를 구현해준다.

public TodoEntity() {

}

@AllArgsConstructor

클래스의 모든 멤버 변수를 매개변수로 받는 생성자 구현

public TodoEntity(String id, String userId, String title, boolean done){
	super();
    this.id = id;
    this.userId = userId;
    this.title = title;    
    this.done = done;
}

@Data

클래스 멤버 변수의 Getter / Setter 메서드 구현

public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
	// 생략
    public void setDone(boolean done) {
        this.done = done;
    }

DTO

서비스가 요청을 처리하고 클라이언트로 반환 시 Model 그 자체로 보내기보단 DTO로 변환해서 리턴한다. (Data Transfer Object, 데이터 전달에 사용하는 오브젝트)

DTO로 보내는 이유?

  • 비즈니스 로직 캡슐화
  • 클라이언트에게 필요한 정보가 모델에 모두 포함되어 있지 않은 경우가 잦기에
    (에러 메세지 등)

DTO 예제

package com.example.demo.dto;

import com.example.demo.model.TodoEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class TodoDTO {
    private String id;
    private String title;
    private boolean done;

    public TodoDTO(final TodoEntity entity) {
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.done = entity.isDone();
    }
}


REST API

REST 아키텍처 스타일
클라이언트가 서비스를 이용하기 위해 어떠한 형식으로 요청을 보내고 응답을 받는지에 대한 것. REST아키텍처를 따라 구현된 서비스를 REST 서비스라고 한다. REST 아키텍처 스타일은 6가지 제약조건이 있고, 이 제약조건을 따르는 API를 RESTful API라고한다.

  • 클라이언트 - 서버
  • Stateless
  • Cacheable 데이터
  • 레이어 시스템
    - 서버가 인증서버, 캐싱서버, 로드 밸런서를 거쳐 최종적으로 애플리케이션에 도착한다고 가정할 때, 이 사이의 레이어는 응답에 영향을 미치지 않으며 클라이언트는 레이어의 존재 인식하지 못한다.
  • code-on-demand (optional)
    - 클라이언트는 서버에 코드를 요청할 수 있고, 서버가 리턴한 코드를 실행할 수 있다.
  • 일관적 인터페이스


Controller Layer

스프링 REST API Controller

스프링 부트 스타터 웹 디펜던시 설정을 해두었다. 이 패키지가 제공하는 어노테이션을 사용하면 REST 구현이 쉽다.

@RestController / @RequestMapping

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("test") // 리소스
public class TestController {
}
  • REST API를 구현하므로 이 어노테이션을 통해 해당 컨트롤러가 RestController임을 명시한다. RestController 이용 시 HTTP 관련 코드 및 요청/응답 매핑을 스프링이 지원한다.

@GetMapping

메서드를 추가하고 @GetMapping 설정해준다

public class TestController {
    
    @GetMapping
    public String testController() {
        return "hello world";
    }
}

  • @GetMapping 은 HTTP에 매핑
  • @RequestMapping(/test) 는 URI에 매핑됨

앱 실행 후 http://localhost:8080/test 로 접근하면 Hello World 출력됨

@GetMapping에 HTTP 메서드 지정 말고도 경로를 지정하는 것 또한 가능하다!

    @GetMapping("/testGetMapping")
    public String testControllerWithPath() {
        return "hello world & testGetMapping";
    }

위의 코드는 아래와 같이 매핑된다.


@PathVariable

/test/{id}와 같은 URI경로로 넘어오는 파라미터를 변수로 받을 수 있다.

@GetMapping("/{id}")
    public String testControllerPathVar(@PathVariable(required = false) int id) {
        return "Hello world " + id;
    }

@RequestParams

/test?id=123과 같은 요청 파라미터를 받을 수 있다. ?뒤의 파라미터에 매핑된다.

@RequestBody

반환하고자 하는 리소스가 복잡할 때 사용한다.

DTO 추가 후 DTO를 요청 바디로 받는 메서드 추가하여 확인해보기

package com.example.demo.dto;

import lombok.Data;

// DTO 
@Data
public class TestRequestBodyDTO {
    private int id;
    private String message;
}
// 컨트롤러 클래스에 메서드 추가
// /test경로는 이미 존재하므로 /test/testRequestBody로 지정
	@GetMapping("/testRequestBody")
	public String testControllerRequestBody(@RequestBody TestRequestBodyDTO testRequestBodyDTO) {
		return "Hello World! ID " + testRequestBodyDTO.getId() + " Message : " + testRequestBodyDTO.getMessage();
	}

@RequestBody TestRequestBodyDTO testRequestBodyDTO는 Request Body로 보내오는 JSON을 TestRequestBodyDTO 오브젝트로 변환하여 가져온다. 따라서 클라이언트가 전송하는 요청 바디의 JSON 문자열 내부는 TestRequestBodyDTO와 의미적으로 동일해야 한다.

@ResponseBody@ResponseEntity

문자열 리턴말고 복잡한 오브젝트 리턴하기

@RestController@ResponseBody@Controller의 조합으로 이루어져 있다. @Controller는 @Component로서 스프링이 이 클래스의 오브젝트를 자동으로 생성, 의존성 연결을 수행한다. @ResponseBody는 이 클래스의 반환값이 웹의 ResponseBody라는 의미이다. 즉, 스프링은 메서드가 리턴할 때 리턴된 오브젝트를 JSON 형태로 변환 후 HttpResponse에 담아 반환한다는 것이다.

	@GetMapping("/testResponseBody")
    public ResponseDTO<String> testControllerResponseBody() {
        List<String> list = new ArrayList<>();
        list.add("Hello World! ResponseDTO");
        ResponseDTO<String> response = ResponseDTO.<String>builder().data(list).build();
        return response;
    }

    @GetMapping("/testResponseEntity")
    public ResponseEntity<?> testControllerResponseEntity() {
        List<String> list = new ArrayList<>();
        list.add("Hello World! ResponseEntity. Error 400!");
        ResponseDTO<String> response = ResponseDTO.<String>builder().data(list).build();
        // http status를 400로 설정.
        return ResponseEntity.badRequest().body(response);
    } 

ResponseEntity는 HTTP 응답의 바디 외에도 status나 header 등 조작 가능

profile
학생 점심을 좀 차리시길 바랍니다

0개의 댓글