오봉이와 함께하는 개발 블로그

스프링 week_5 본문

자바/스프링 튕튕

스프링 week_5

오봉봉이 2021. 11. 15. 23:39
728x90

Artineer Spring 5주차 강의노트

Remind

Q1. 의존성 주입을 할 때 사용하는 스프링 어노테이션은 ?

  • @Autowired
  • 스프링은 의존성을 관리하기 위해 만들어진 프레임워크
  • @Autowired를 통해 의존성 주입
  • 필드나 생성자레벨에 넣어주면 스프링 컨테이너에 빈들이 등록되고 빈들을 바로 주입 받아 사용한다.

@Q2. @Autowired 어노테이션이 필드에 존재하냐, 생성자에 존재하냐에 따라 필드주입, 생성자주입으로 갈린다. 스프링은 기본적으로 생성자 주입을 선호하는데 그 이유는?

  • 순환참조 이슈가 있기 때문
  • 순환참조란 a와 b 두 객체가 있을 때 a는 b를 의존하고 있고 b도 a를 의존하는 상황
  • 객체지향에선 좋지 않은 구조
  • a객체를 만들 때 b가 필요, b를 만들 때 a가 필요하기 때문에 코드에서 데드락 발생.

Q3. 빌더 패턴이 가지는 이점?

  • lombok이라는 라이브러리를 사용하면 @builder라는 어노테이션을 통해 빌더패턴을 사용할 수 있다
  • 필드 값을 주입할 때 함수 값들이 필드 이름으로 되어있기 때문에 명시적 주입 가능
  • Immutable 하게 코드 작성 가능
    • setter를 통해 주입하는 것 보다 불변하게 코드 작성 가능
  • 필드가 많을 경우 생성자 오버로딩을 많이 하지 않고 주입 가능
    • 필드가 여러 개 있다면 생성자로써 주입할 수 있는 코드를 만든다면 만들어야 하는 생성자가 많아진다.

Q4. Restful API 제공하는 기본적인 Http Method 4가지

  • GET : 조회
  • POST : 생성
  • PUT : 수정
  • DELETE : 제거

Q5. DTO 객체를 생성하는 이유?

  • Domain Layer과 비즈니스 로직을 처리하고 실제 화면에 내용을 표시할 Presentation Layer이 다르기 때문에 구분하기 위함.
    • Presentation Layer에서 사용하기 위함
  • 두 계층간 객체의 역할은 다르다

Q6. Transaction 이란?

  • 특정 비즈니스에서 한 업무 단위
  • ACID
    • Atomic (원자성) : 업무가 하나의 일련 과정이어야 한다
    • Consistency (일관성) : 업무 결과가 작업 끝난 후에도 유지되어야 한다.
    • Isolation (고립성) : 업무 처리 시 다른 업무가 끼어들 수 없어야 한다.
    • Durability (영구성) : 작업 내용이 영구히 저장되어야 한다. (이건 데이터베이스 기준이기 때문에 어플리케이션 레벨에서는 달라질 수 있다.)

Q7. 정적 팩토리 메서드 (of Pattern)

  • 객체 생성하는 코드를 팩토리 메서드 형태로 제공
    • 정해진 형태로 사용 한다면 한 곳에 객체를 만들어 놓고 사용하는 것
  • 코드 중복 최소화
  • 비즈니스와 객체 생성에 대한 영역을 구분할 수 있음

Q8. 컨트롤러 앞단에서 예외처리 해주도록 스프링에서 제공해주는 기능 ?

  • @ControllerAdvice
    • 예외처리에 종속된 어노테이션은 아님
    • 컨트롤러 이전에 무언가를 한다. 라는 것이 이 어노테이션의 요점
  • @RestControllerAdvice

Q9. 자바 8 이후 null 처리를 강제하기 위해서 추가된 기능 ?

  • Optional

Q10. 테스트코드를 작성하는 이유

  • 테스트 코드는 다른 개발자들에게 하나의 문서로도 사용된다
    • 내가 만든 함수에 대한 테스트를 명시적으로 보여줌으로써 다른 개발자들은 이 테스트 코드를 보고 내가 만든 함수가 어떻게 동작하는지를 보다 정확히 알 수 있다.
    • 작성된 함수가 동작할 때 흔하지 않은 예외 케이스들을 접할 수 있는데 그럴때 마다 테스트 케이스를 추가해두면 이런 부분에 대해서도 추후 다른 개발자들에게 인계가 강제적으로 가능하다.
  • 테스트 코드가 실패했을 시 빌드 실패가 되도록 강제할 수 있어서 후 리팩토링이나 비지니스가 수정되어 소스를 수정해야 할 때 방어책이 될 수 있다.

통합 테스트

  • 유닛 테스트는 특정 모듈만을 테스트하기 위해 Mocking을 했지만 통합 테스트는 필요 없다.
    • 실제 객체를 사용하기 때문
  • @SpringBootTest를 사용하면 모든 빈을 컨테이너에 등록할 수 있다.
  • WebApplicationContext는 스프링프레임워크(스프링부트가 아님)에서 많이 사용했었다.
    • 스프링부트에서는 자동 설정됐기 때문
    • 통합 테스트에서는 실제 객체를 가져오기 위해 사용한다.

ArticleIntegrationTest.java

@SpringBootTest
public class ArticleControllerIntegrationTest {

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private ArticleRepository articleRepository;

    private MockMvc mvc; // API테스트 할 때 API를 요청하는 클라이언트를 대신 해줌

    @BeforeEach
    public void setUp() {
        mvc = MockMvcBuilders
                .webAppContextSetup(context)
                .build();
    }

    @Test
    @DisplayName("post() 실행되면 article 객체가 새로 생성되어야 한다")
    public void post_whenItIsOccured_thenArticleShouldbeStored() throws Exception {
        // given
        ArticleDto.ReqPost requestBodyStub = ArticleDto.ReqPost.builder()
                .title("title")
                .content("content")
                .build();

        mvc.perform(post("/api/v1/article/")
                        .characterEncoding("utf-8")
                        .accept(MediaType.APPLICATION_JSON) // 어떤 타입으로 줘야 하는지 알려주는 코드
                        .contentType(MediaType.APPLICATION_JSON) // 실제로 내려오는 코드
                        .content(new ObjectMapper().writeValueAsString(requestBodyStub))
                )
                .andDo(print())
                .andExpect(status().isOk());

        // when
        long size = articleRepository.count();

        // then
        assertThat(size).isEqualTo(1);
    }
}

Validation

  • 클라이언트가 서버한테 요청을 보냈을 때 정상적인지 체크하는 로직

ApiCode.java

public enum ApiCode {
    /* ... */
    BAD_REQUEST("CM0002", "요청 정보가 올바르지 않습니다"); /* 새로운 Code 추가 */
    // 잘못된 값이 전달되면 상위 응답으로 내려감.

    /* ... */
}

Asserts.java

// 입력 문자열이 null 이거나 빈 공백 일 수 있기 때문에 이를 검증하기 위한 isBlank 함수를 만든다.
public class Asserts {

    /* ... */

    public static void isBlank(@Nullable String str, ApiCode code, String msg) {
        if(Strings.isBlank(str)) {
            throw new ApiException(code, msg);
        }
    }
}

ArticleController.java

@RequiredArgsConstructor
@RequestMapping("/api/v1/article")
@RestController
public class ArticleController {

   /* ... */ 

    @PostMapping
    public Response<Long> post(@RequestBody ArticleDto.ReqPost request) {
      Asserts.isBlank(request.getTitle(), ApiCode.BAD_REQUEST, "'title' 은 필수 값입니다.");
      Asserts.isBlank(request.getContent(), ApiCode.BAD_REQUEST, "'content' 는 필수 값입니다.");

      return Response.ok(articleService.save(Article.of(request)));
    }

    /* ... */
}

Spring Validation으로 Validation

  • Controller에서 검증한다는 것은 Controller의 일이 많아지는 것이다
    • 모든 Controller가 다 해야하기 때문에 Validation의 업무가 반복이 된다.
    • 그래서 Controller에서 빼는 방법을 사용한다.
  • 관련 의존성 추가
// build.gradle

dependencies {
  // ...

  implementation 'org.springframework.boot:spring-boot-starter-validation'

  // ...
}
  • 검증하고자 하는 객체에 @Valid 어노테이션을 추가한다.

ArticleController.java

public class ArticleController {

    // ...

    @PostMapping
    public Response<Long> post(@RequestBody @Valid ArticleDto.ReqPost request) {
        return Response.ok(articleService.save(Article.of(request)));
    }

    // ...
}

ArticleDto.java

public class ArticleDto {
    @Getter
    @Builder
    public static class ReqPost {
        @NotBlank
        String title;
        @NotBlank
        String content;
    }

    // ...
}
  • Validation 처리가 중요한 이유는 API를 사용하는 클라이언트에게 어떤 값이 잘못됐는지 정보를 제공할 수 있다는 점이다.
  • Spring Validation는 코드를 추상화 시켜서 짤 수 있지만 메세지가 아쉽다
    • 커스터마이징이 불가능
  • 직접 하게 된다면 메세지는 디테일하지만 Validation하는 코드가 반복된다

ControllerExceptionHandler.java

  • Spring Validation의 오류 내용을 좀 더 알기 쉽도록 하는 코드
  • 내용이 깔끔하지 않지만 필요한 내용은 모두 담고 있다.
@RestControllerAdvice
public class ControllerExceptionHandler {

    // ...

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Response<String> methodArgumentNotValidException(MethodArgumentNotValidException e) {
        return Response.<String>builder().code(ApiCode.BAD_REQUEST).data(e.getMessage()).build();
    }
}

Optional) @RequestParam, @RequestBody

  • @RequestParam은 GET 통신에서 QueryString을 받을 수 있다.
  • @PathVariable은 URI에 포함된 값을 변수로 가져오는 어노테이션
  • @RequestBody는 POST기반 통신에서 Body를 받아오는 어노테이션
  • @RequestBody 기반으로 들어온 데이터가 검증 실패할 경우 MethodArgumentNotValidException 오류를 던지지만 @RequestParam 은 ConstraintViolationException 오류를 던진다.
  • RequestParam을 대비하기 위한 코드
@RestControllerAdvice
public class ControllerExceptionHandler {
    // ...

    /* @RequestBody 데이터 검증 실패시 발생한다. */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Response<String> methodArgumentNotValidException(MethodArgumentNotValidException e) {
        return Response.<String>builder().code(ApiCode.BAD_REQUEST).data(e.getMessage()).build();
    }

    /* @RequestParam 데이터 검증 실패시 발생한다. */
    @ExceptionHandler(ConstraintViolationException.class)
    public Response<String> constraintViolationException(ConstraintViolationException e) {
        return Response.<String>builder().code(ApiCode.BAD_REQUEST).data(e.getMessage()).build();
    }
}
728x90

'자바 > 스프링 튕튕' 카테고리의 다른 글

스프링 week_7  (0) 2021.11.29
스프링 week_6  (0) 2021.11.22
스프링 week_4  (0) 2021.11.02
스프링 week_3  (0) 2021.11.02
스프링 week_2  (0) 2021.11.02
Comments