레벨 2 첫 미션인 자동차 경주 미션이 끝났습니다.
스프링을 처음 접한 미션이었는데요, 이 과정을 통해 얻은 것들을 정리해보고자 합니다.
페어와 룰 정하기 + 아이스 브레이킹의 중요성
이번 미션의 페어는 체인저였는데요, 체인저의 제안으로 미션을 시작하기에 앞서 페어 프로그래밍 룰을 정했습니다.
스위치 시간이나 컨벤션, 그리고 구현 계획을 미리 정했는데요. 이 과정에서 체인저와 더 친해질 수 있었고 편한 마음으로 미션을 시작할 수 있었습니다. 게다가 이런 페어 프로그래밍 룰을 미리 정해 두었기에, 비교적 개발 피로도도 적었던 것 같아 좋았습니다. 스프링으로 하는 첫 미션이라 조급하고 막막한 마음이 있었는데, 확실히 규칙을 정하고 하니 안정적이라는 느낌을 받았습니다. 정말 좋은 방식인 것 같아, 앞으로 페어 프로그래밍이나 협업을 할 때 적용할만한 가치가 충분하다고 생각됩니다.
@Controller vs. @RestController
전통적인 Spring MVC의 컨트롤러는 View를 반환합니다.
반면 데이터를 반환해야 경우도 존재하고, 이런 경우에는 @ResponseBody 어노테이션을 활용합니다.
@RestController는 @Controller + @ResponseBody입니다.
데이터를 반환해야 하는 경우 (주로 REST API를 개발할 때) 사용할 수 있습니다.
비슷하게 컨트롤러 전역 처리를 하는 경우도 @ControllerAdvice, @RestControllerAdvice로 나뉘는데
이 경우도 마찬가지로 @RestControllerAdvice를 @ControllerAdvice + @ResponseBody로 생각하면 될 것 같습니다.
ResponseEntity
HttpEntity를 상속받는(확장한) 객체입니다.
HttpEntity란, HTTP 요청 또는 응답에 해당하는 HttpHeader와 HttpBody를 포함하는 클래스입니다.
여기에 추가로 ResponseEntity는 상태 코드를 지정할 수 있습니다.
따라서 핸들러는 이런 ResponseEntity 객체를 반환해 클라이언트에게 필요한 정보를 제공할 수 있습니다.
@Valid
@Valid는 Bean Validator를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션입니다.
실제로는 LocalValidatorFactoryBean 이라는 어댑터가 제약 조건을 검증하는데, SpringBoot에서는 간단하게 의존성만 추가해주면 사용할 수 있게 됩니다.
클라이언트 측에서 넘어온 데이터를 객체에 바인딩할 때 이를 통해 유효성 검사를 할 수 있으며, 직접 필드에 @NotNull, @NotEmpty와 같은 제약조건을 설정할 수 있습니다.
@JdbcTest
테스트를 진행할 때 사용할 수 있는 어노테이션입니다.
@SpringBootTest와의 차이점은 다음과 같은 것들이 존재합니다.
- Spring 컴포넌트를 전부 가져와 테스트하는 것이 아닌, Jdbc와 관련된 컴포넌트만 가져와 슬라이스 테스트를 진행합니다.
- 이 어노테이션이 붙은 테스트는 기본적으로 Transactional 하며, 테스트가 끝날 때마다 롤백됩니다.
- Datasource를 대체해 인메모리 Database를 사용합니다. 하지만 @AutoConfigureTestDatabase 를 통해 이런 설정을 덮어씌울 수는 있습니다.
이번 미션에서는 Repository 테스트를 할 때 JdbcTest를 진행했는데요, 인메모리 저장소를 사용한다는 점과 기본적으로 트랜잭션 롤백이 된다는 점 때문에 앞으로도 유용하게 사용할 것 같습니다.
@ControllerAdvice 예외처리 순서
이번 미션에서는 ControllerAdvice를 예외처리용으로만 사용했는데요,
만약 어떤 예외가 발생하고, 해당 예외의 super 타입에 대한 exceptionHandler가 여러 개 존재한다면 어떻게 될까요?
Spring에서는 가장 구체적인 예외 핸들러에 매핑한다고 합니다.
예를 들어, RuntimeException을 상속하는 AException이 있고, AException을 상속하는 BException이 있다고 해보겠습니다.
이런 경우 ExceptionHandler가 AException, BException에 대해 모두 존재해도 BException이 발생하는 순간 가장 구체적인 예외처리기(BExceptionHandler)에 매핑됩니다.
Repository, DAO, Entity
미션을 진행하면서 Repository, DAO, Entity 사용에 대한 주관적인 기준이 잡혔고, 다음과 같습니다.
- Repository란
Repository의 메소드 시그니쳐는 도메인의 성격을 띠지만, 구현부는 영속성의 개념을 가집니다. 따라서 조금 특이하다고 할 수 있는데요, 기본적으로 애그리거트에 대한 컬렉션으로 작동합니다. 호출자는 단순히 도메인 엔티티가 제대로 생성/저장/조회/삭제 될 것임을 알 뿐입니다. 이런 Repository를 통해 Service나 유스케이스는 비즈니스 로직을 수행할 수 있습니다.
- DAO란
Data Access Object로, 데이터 계층에 대한 추상화를 제공합니다. DB를 사용하는 경우 DAO는 하나의 테이블에 매핑될 수 있습니다. 하지만 굳이 테이블 하나에 매핑되지 않더라도 DAO는 사용될 수 있다고 생각합니다. DB가 아닌 다른 분야더라도 DAO는 사용될 수 있는데요, 예를 들어 외부 API에 접근해서 정보를 가져오는 경우라거나, 파일 시스템에 접근하는 경우 DAO를 활용해 데이터에 대한 추상화를 할 수 있을 것 같습니다. 즉, DAO는 데이터베이스에 대한 추상화가 아닌 영속성에 대한 추상화라는 것을 깨달았습니다.
- Entity란
도메인 엔티티와는 다른 개념으로, 일반적으로 DAO에서 입력/반환 값으로 사용될 수 있는데요. DB를 영속성으로 사용하는 경우 Entity는 하나의 테이블 행에 매핑됩니다. DAO가 구체적인 정보를 반환하지 않고 Entity를 반환했을 때의 장점은 특성 컨텍스트에 묶이지 않을 수 있다는 것입니다. DAO는 보편적인 정보(Entity)만 제공하고 이를 조합해 사용하는 책임은 호출자에게 맡기면 됩니다. 하지만 쿼리 최적화나 성능 이슈로 인해 항상 Entity를 반환할 수는 없으며, DTO를 반환해야 할 때도 있다고 생각합니다.
Transactional
@Transactional 어노테이션을 사용하는 경우 해당 어노테이션이 붙은 메소드를 트랜잭션으로 구성하는데요, (예외가 발생해도 롤백됨)
빈이 호출하는 메소드가 아닌, 내부적으로 사용되는 private 메소드의 경우 @Transactional 키워드가 적용되지 않음을 확인했습니다. 이는 AOP와 관련된 것 같은데 더 이상 깊게 학습하는 건 무리라고 판단해 추후 학습해 볼 예정입니다.
'우테코 5기' 카테고리의 다른 글
[레벨 2 미션] 웹 장바구니 미션 학습 기록 (2) (0) | 2023.05.20 |
---|---|
[레벨 2 미션] 웹 장바구니 미션 학습 기록 (1) (1) | 2023.05.14 |
[트러블슈팅] Interceptor 생성으로 인해 컨트롤러 테스트가 깨지는 경우 (8) | 2023.05.05 |
장바구니 미션) 도메인에서 영속성 개념 분리해보기 (6) | 2023.04.29 |
우아한테크코스 레벨1 회고 (6) | 2023.04.06 |