Dto에 NoargsConstructor를 사용하는 이유
코드를 작성하다보면 프로젝트를 오랜만에하면은 Dto에는 기본생성자를 생성해줘야하는지 매번 헷갈리며 이유를 제대로 알아보고자 합니다.
@Data
@NoArgsConstructor
public class SignUpRequestDto {
private String email;
private String password;
}
우선 생성자가 필요한 이유를 알기위해선
스프링이 어떻게 Dto를 JSON으로 매핑하는지 원리를 알아야합니다.
스프링이 매핑하는 원리는 Jackson라이브러의 ObjectMapper를 사용하여 JSON으로 매핑합니다.
여기서 ArgumentResolver는 JSON데이터를 객체로 변환하기 위해서 MessageConverter가 사용됩니다. 그 중MappiongJackson2HttpMessageConverter가 선택됩니다.
아마 기본생성자를 통해 생성하고 바인딩하는것같다. 리플렉션과 같은원리인것같다. 만약 다른생성자가있다면 헷갈리고 상태가 정확하지않기때문에 기본생성자를 사용하는것같다.
ObjectMapper는 JSON으로 직렬화, 역직렬화를 수행합니다.
직렬화 : Java Object → JSON
역직렬화 : JSON → Java Object
컨트롤러에서 DTO를 @RequestBody를 통해 가져올때 바인딩은 ObjectMapper가 수행하게됩니다. 이렇게 직렬화, 역직렬화를 수행하여 매핑할때 DTO를 DTO의 기본생성자를 이용하여 생성하게 됩니다.
기본생성자 없으면 : Object로 역직렬화
기본생성자있으면 이를 이용
참고로 Entity에 기본생성자가 필요한이유는 자바리플렉션을 사용하기때문이다.
@Valid, @Validator
간단하게 알아보겠습니다.
valid는 자바 진영 스펙입니다.
valid동작 원리
모든 요청은 프론트 컨트롤러인 디스패처 서블릿을 통해 컨트롤러로 전달된다. 전달 과정에서는 컨트롤러 메소드의 객체를 만들어주는 ArgumentResolver가 동작하는데, @Valid 역시 ArgumentResolver에 의해 처리가 된다.
대표적으로 @RequestBody는 Json 메세지를 객체로 변환해주는 작업이 ArgumentResolver의 구현체인
RequestResponseBodyMethodProcessor가 처리하며, 이 내부에서 @Valid로 시작하는 어노테이션이 있을 경우에 유효성 검사를 진행한다. (이러한 이유로 @Valid가 아니라 커스텀 어노테이션인 @ValidMangKyu여도 동작한다.)
그리고 검증에 오류가 있다면 MethodArgumentNotValidException 예외가 발생하게 되고, 디스패처 서블릿에 기본으로 등록된 예외 리졸버(Exception Resolver)인 DefaultHandlerExceptionResolver에 의해 400 BadRequest 에러가 발생한다.
이러한 이유로 @Valid는 기본적으로 컨트롤러에서만 동작하며 기본적으로 다른 계층에서는 검증이 되지 않는다. 다른 계층에서 파라미터를 검증하기 위해서는 @Validated와 결합되어야 하는데, 아래에서 @Validated와 함께 자세히 살펴보도록 하자.
@Validated
입력 파라미터의 유효성 검증은 컨트롤러에서 최대한 처리하고 넘겨주는 것이 좋다. 하지만 개발을 하다보면 불가피하게 다른 곳에서 파라미터를 검증해야 할 수 있다. Spring에서는 이를 위해 AOP 기반으로 메소드의 요청을 가로채서 유효성 검증을 진행해주는 @Validated를 제공하고 있다. @Validated는 JSR 표준 기술이 아니며 Spring 프레임워크에서 제공하는 어노테이션 및 기능 이다.
@Service
@Validated
public class UserService {
public void addUser(@Valid AddUserRequest addUserRequest) {
...
}
}
@Validated 동작원리
특정 ArgumnetResolver에 의해 유효성 검사가 진행되었던 @Valid와 달리, @Validated는 AOP 기반으로 메소드 요청을 인터셉터하여 처리된다. @Validated를 클래스 레벨에 선언하면 해당 클래스에 유효성 검증을 위한 AOP의 어드바이스 또는 인터셉터(MethodValidationInterceptor)가 등록된다. 그리고 해당 클래스의 메소드들이 호출될 때 AOP의 포인트 컷으로써 요청을 가로채서 유효성 검증을 진행한다.
이러한 이유로 @Validated를 사용하면 컨트롤러, 서비스, 레포지토리 등 계층에 무관하게 스프링 빈이라면 유효성 검증을 진행할 수 있다. 대신 클래스에는 유효성 검증 AOP가 적용되도록 @Validated를, 검증을 진행할 메소드에는 @Valid를 선언해주어야 한다.
이러한 이유로 @Valid에 의한 예외는 MethodArgumentNotValidException이며, @Validated에 의한 예외는 ConstraintViolationException이다.
Validated의 추가기능
유요성 검증 그룹을 지정할수있습니다. 이는 따로알아보겠습니다.
개인적으로 컨트롤러에서 검증을 끝내야지 굳이 컨트롤러이후에 검증이 시작된다면 시간을 낭비하여 비효율적이라고 저는생각합니다.
[Spring] @Valid와 @Validated를 이용한 유효성 검증의 동작 원리 및 사용법 예시 - (1/2)
왜 JPA에 @Transactional을 써줘야하는가?
아마 영속성컨텍스트를 작동시키기위해서 JPA를 사용할때는 사용해주어야하는것같다. 글들은 주로 트랜잭션을 위해서인데 이말도 맞지만 영속성컨텍스트를 사용하기위해서 Transaction을 사용하는것이고 JPA는 영속성컨텍스트를 이용한다.
@RestControllerAdvice, @ControllerAdvice
둘다 전역 예외처리를 위한 어노테이션입니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
~~
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
~~
}
차이점
@RestControllerAdvice는 @ResponseBody가 추가로 붙어있기때문에 @ControllerAdvice와 달리 응답의 body에 JSON을 넣어 반환이 가능하다.
@ExceptionHandler
핸들러 즉 하나의 핸들러만 처리한다는것입니다. 이름에서보다싶이 핸들러 처리기라서 Service와 Repository가 적용된곳에서는 사용이 불가능합니다.
@ExceptionHandler는 등록된 해당 Controller 에서만 적용이 된다. 다른 컨트롤러의 예외는 잡을 수 없다. 같은 예외가 발생한 것이고 같은 처리를 해주고 싶은 경우가 있을 수 있다. 다른 컨트롤러에서의 작업이라면 해당 컨트롤러에 같은 @ExceptionHandler를 적용해주어야 한다. 똑같은 기능을 하는 똑같이 생긴 코드를 반복하는 것은 번거럽고 낭비이다. 이러한 번거로움을 해결할 수 있는 방법이 있는데, 바로 @ControllerAdvice입니다.
하지만 Controller에서 사용하면 코드의 유지보수가 힘들어질거같다. 왜냐하면 예외핸들러를 위해서 컨트롤러 코드를 바꿔야하기때문이다. 따라서 저는 전역 핸들러를 통해 따로 Assign해주는것을 선호합니다. 또한 컨트롤러계층은 request와 response를 담당하는 SRP을 위해서 따로 뺐습니다.
ArgumentResolver
Argument Resolver는 어떠한 요청이 컨트롤러에 들어왔을 때, 요청에 들어온 값으로부터 원하는 객체를 만들어 내는 일을 간접적으로 해줄 수 있다.
파라미터 바인딩일을 하는것으로 알고있다. 특히 HttpMessageConverter를 사용하여서 변환하는일을한다.
중복 코드를 지울수있다는 장점이있습니다.
Mockito @Spy @Mock @InjectMocks
테스트 더블이란?
예를 들어 우리가 데이터베이스로부터 조회한 값을 연산하는 로직을 구현했다고 하자. 해당 로직을 테스트하기 위해선 항상 데이터베이스의 영향을 받을 것이고, 이는 데이터베이스의 상태에 따라 다른 결과를 유발할 수도 있다.
이렇게 테스트하려는 객체와 연관된 객체를 사용하기가 어렵고 모호할 때 대신해 줄 수 있는 객체를 테스트 더블이라 한다.
독립적인 테스트를위해서 테스트 더블을 사용
실제 관심사를 테스트하기위한것이다.
테스트 더블 종류
- Stub
- Spy
- Mock
Mock
호출에 대한 기대를 명세할 수 있고, 그 명세 내용에 따라 동작하도록 프로그래밍된 객체 이다. Mock 외의 것은 개발자가 임의로 코드를 사용하여 생성할 수 있지만, Mock은 Mocking 라이브러리에 의해 동적으로 생성된다. 또한 설정에 따라 Mock은 충분히 Dummy, Stub, Spy 처럼 동작할 수 있다. 즉, 가장 강력한 테스트 더블이라고 할 수 있을 것 같다.
Mockito
테스트더블에 대한 Mock으로 Java진영에서는 대표적으로 Mockito가 있습니다.
Mockito는 Java 진영에서 널리 사용되는 Mocking 라이브러리이다.
Mockito주요어노테이션
- @Mock
- @MockBean
- @Spy
- @SpyBean
- @InjectMocks
@Mock
@Mock으로 만든 mock 객체는 가짜 객체이며 그 안에 메소드 호출해서 사용하려면 반드시 스터빙(stubbing)**을 해야합니다.
@Spy
@Spy로 만든 mock 객체는 진짜 객체이며 메소드 실행 시 스터빙을 하지 않으면 기존 객체의 로직을 실행한 값을, 스터빙을 한 경우엔 스터빙 값을 리턴 합니다.
@InjectMocks
@InjectMock은 DI를 @Mock이나 @Spy로 생성된 mock 객체를 자동으로 주입해주는 어노테이션입니다.
예를들어 UserService는 필드로 UserRepository를 가지는데 이를 위해
UserService를 InjectMocks로 하고 UserRepository를 Mock이나 Spy로 지정하면됩니다.
[Java] Mockito 사용법 (2) - 설정, Mock 생성 (@Mock, @Spy, @InjectMocks)