[우아한테크코스] 4월 21일 TIL

4 minute read

오늘 배운 것

  1. 체스 스프링 피드백 적용

[Spring] dto의 사용 영역

  • dto 관련
    비슷한 필드를 갖는 DTO여도 각 DTO마다 역할이 다르고 추후 나아갈 방향이 다르기 때문에 존재할 수 있다. 따라서 requestDto, responseDto가 많이 발생할 수 있다.
    하지만 서비스 레이어에서 Dto를 받거나 Dto를 리턴하게 되면 해당 Dto를 사용하는 특정 Controller 에 종속되게 된다. 컨트롤러는 특정 뷰에 종속되어 있기 때문에 때에 따라서는 서비스가 뷰에 종속되게 되는 경우가 생길 수 있으므로 service가 dto에 대한 종속성을 가지지 않도록 하자.

  • 레이어드 아키텍쳐란
    우리는 프로그램의 유지보수성을 좋게 하기 위해 계층별로 프로젝트를 나누어 모듈화하여 관리한다.
    각 계층은 하위 계층, 혹은 동 계층에게만 의존해야 하고 상위 계층이나 그 외에 계층에는 의존해서는 안된다.
    이번 chess spring project에는 client <-> controller <-> service <-> repository(dao)의 계층이 존재한다.
    이때 나는 각 계층을 이동하는 모든 데이터를 dto로 만들어주었다.
    그렇게 함으로써 db table과 직결되는 도메인이 드러나지 않도록 보호할 수 있었다.
    하지만 controller는 view에 종속되어있고, 그것을 service에까지 같은 객체로 데려가면 view에서 다른 요청을 보내면 service까지 영향을 미쳐서 바꿔줘야하는 일이 발생하는 것이다.
    각각의 trade-off가 존재하지만 선택이 필요한 부분이라고 생각했다.
    repository<->service를 다니는 dto 또한 repository 자체가 domain으로 보여지는 persistance 영역이므로 dao를 통해 primitive나 도메인 객체 자체를 얻어와서 이를 business 영역을 수행하는 service에게 넘겨주는 것이 올바른 방식으로 생각된다. 아직까지는!

[Network] REST API

체스 미션을 수행하면서 나는 각 dto들에게 roomId를 주어서 생성자에 넣어서 만들고, getter를 사용해 꺼내서 쓰는 방식을 사용해왔다.
그러던 중 평소 내가 보는 url에는 /1와 같이 id를 주는 것을 알게되었고, 페어의 리뷰에도 @PathVariable을 이용해 id를 전달하지 않는 이유에 대해서 물어보게 되어 생각하게 되었다.
위와 같이 url에 id를 줘서 get, post, put, delete를 수행하는 방식이 REST API라는 것을 알게 되었고 적용하게 되었다.

  • rest api란?
    자원(uri), 행위(http method), 표현을 가진 것이다.
    uniform interface로 uri로 지정한 자원에 대한 조작을 통일되고 한정적인 인터페이스로 수행하도록 한다.
    stateless로 상태를 가지고 있지 않다. 세션 정보나 쿠키를 별도로 가지지 않기 때문에 api는 요청을 수행하기만 하면 된다.
    cacheable로 http가 가진 캐싱 기능을 Last-Modified나 E-Tag를 사용하면 구현할 수 있다.
    self-descriptiveness로 메시지만 보고 알아서 표현할 수 있는 구조이다.
    client-server 구조로 서버는 api 제공, 클라이언트는 사용자 인증, 세션, 로그인 정보 같은 컨텍스트를 직접 관리하도록 한다. 역할이 구분되므로 서로 간의 의존성이 줄어든다.
    계층형 구조로 다중 계층이 될 수 있고 proxy, 게이트웨이 같은 네트워크 기반 중간매체도 사용할 수 있다.
  • rest api 규칙
    1. uri는 정보의 자원을 표현(명사)
      GET: /room/1 (자원 요청)
    2. 행위는 HTTP METHOD로 표현
      DELETE: /room/2 (자원 삭제)
      POST: /room/3 (자원 생성)
      PUT: /room/4 (자원 수정)
    3. /는 계층 관계를 나타낼 때 사용
    4. uri 마지막엔 /가 존재하지 않는 다.
    5. -는 가독성이 떨어질 때 사용
    6. _는 사용하지 않는다.
    7. 소문자를 사용한다.
    8. 확장자 사용하지 않는다.
  • http 응답 코드
    200: 정상
    201: post 성공
    400: 요청 부적절
    401: 클라이언트가 부적절
    403: 응답할 수 없는 자원 요청
    405: 사용 불가능한 method
    301: 클라이언트의 uri 변경
    500: 서버 문제

[Test] RestAssured를 사용한 테스트 구현

REST Assured는 JSON과 HTTP를 기반으로 한 테스트로 어플리케이션의 언어와는 무관하게 request에 따른 response를 테스트할 수 있다.
빌드에 testImplementation 'io.rest-assured:rest-assured:3.3.0' 추가하고 import io.restassured.RestAssured; 임포트 해서 사용할 수 있다.

  • given: test setup(data, parameter)
  • when: test action(method)
  • then: test verification(response data 검증)
  • 응답 상태 코드 확인 테스트
    RestAssured.given().log().all()
        .contentType(MediaType.APPLICATION_JSON_VALUE)
        .when().post("/api/pieces/1")
        .then().statusCode(200);
    
  • Response data 검증 방법
    .body("listName.size()", 3)과 같이 hamcrest matchers를 사용해 이름과 간단한 메서드가 매칭되어 값을 도출한다.
    equalTo: 같은 값인지 확인
    hasItems: 특정한 값이 포함되어 있는지 확인
    startsWith, endsWithPath: 시작값/끝값 확인(html 반환에 유용) body("href", and(startsWith("http:/localhost:8080/"), endsWithPath("userId")));
    time: 응답 시간 측정
    containsString: 문자열 포함 확인
  • gson은 requeset body에 json을 호출할 때 json 형식 풀거나 묶는 역할을 한다.


[Spring] request 처리 과정

  1. Client 요청 받으면 DispatcherServlet 호출
    servlet? Client의 요청 처리하고 결과를 Cllient에게 전송하는 기술
    client의 요청에 동적으로 작동, html으로 요청에 응답, java thread, mvc 패턴에서 controller로 이용된다.
    servlet-context.xml이 controller, annotation, viewResolver 등을 설정
    root spring container를 root-container.xml로 정의 -> service, repository, db 등 설정
    spring container 생성
  2. DispatcherServlet은 받은 요청을 HandlerMapping에게 준다. url을 분석해 적합한 controller를 선택해 반환
  3. DispatcherServlet은 HandlerAdapter 호출, controller에서 적합한 method 반환
  4. controller는 business logic 처리한 결과를 view에 전달할 객체를 model에 저장
  5. controller는 view name을 DispatcherServlet에게 반환
  6. DispatcherServlet은 ViewResolver를 호출해 view name으로 view 매핑
  7. DispatcherServlet은 View 객체에 처리결과를 넘겨 최종 결과 요청
  8. View 객체는 View를 호출해 model에서 화면 노출에 필요한 객체를 가져와 view에 보이고 client에 전달

-spring mvc


[Spring] @ResponseBody의 역할

  • @ResponseBody는 java 객체를 HTTP 응답의 body로 변환해 전송한다. HTTP 요청의 body 내용으로 매핑
  • @RequestBody는 HTTP 요청의 body를 java 객체로 전달한다. HTTP 요청의 body 내용을 java 객체로 매핑
    RestController를 선언하면 자동으로 @ResponseBody가 붙어 선언이 필요하지 않다.
    HttpMessageConverter 사용


[Spring] @ComponentScan과 Bean 의존성 주입 방법

  • @Configuration
    bean 설정 파일(xml 대체) 어노테이션

  • @ComponentScan
    스프링이 직접 클래스를 검색해서 빈으로 등록해주는 어노테이션
    (basePackages = {“chess”})와 같이 등록하면 스캔 대상 패키지를 선언할 수 있다.
    @filter 어노테이션의 type 속성값도 따로 설정해서 하고 싶은 것만 스캔할 수 있다.
    보통 config 파일 상단에 선언

  • @Component
    스프링이 빈으로 등록할 수 있게 클래스에 붙여주는 어노테이션
    괄호 넣고 (“~~”) 이름 넣으면 빈의 이름을 설정해줄 수 있다.

서로 다른 패키지에 @Component 어노테이션을 가진 같은 클래스가 존재한다면 빈 이름이 충돌난다. 이 경우에 하나에 명시적인 빈의 이름을 주거나 자동으로 생성되는 빈에 @Qualifier 어노테이션을 추가해 적절한 빈을 선택해서 사용해야한다.

그 이유는 ContextLoaderListener(root context), DispatcherServlet(root context의 자식 context)은 각각 WebApplicationContext를 생성한다.
자식은 부모의 설정 bean을 사용할 수 있다. 양쪽 context에 모두 등록되면 불필요한 bean 등록이 일어날 수 있으므로 적절한 exclude, include 사용이 필요하다.

  • bean 의존성 주입의 방법
    1. @Autowired
      필드 변수에 @Autowired 어노테이션을 붙여 빈 주입
    2. 생성자 주입
      이미 빈으로 등록된 것을 생성자에서 전달받아 빈 주입
    3. setter 주입
      setter에 @Autowired 어노테이션을 붙여 빈 주입
      spring의 객체 == bean