시작에 앞서
Spring을 초록스터디를 통해 처음 공부해보았는데, 첫번째 방탈출 미션을 드디어 완료하였다. JAVA 미션을 진행할때는 잘 몰랐던 것을 배울 수 있어서 좋았다. 특히, 멘토 오찌님이 공부하고 생각할 거리를 많이 주셔서 배울 수 있었던 것 같다. 이번 core 미션은 Spring context를 이해하는데에 초점을 맞추어 공부했으나, 어렵다 . . . 따라서, 이번 미션을 진행하며 고민해보았거나, 오찌님이 질문해주신 부분들을 정리해두려 한다.
Spring Context 란 빈(Bean) 객체들을 생성하고 관리하는 스프링의 핵심 컨테이너이다.
예외처리는 도메인에서 처리하는 것이 좋을까, 서비스에서 처리하는 것이 좋을까
도메인에서 처리할 수 있는 예외는 도메인에서, 그 외에는 서비스에서 처리하는 것이 좋다.
-> 최대한 더 낮은 계층에서 처리하는 것을 원칙으로 삼자.
시간 객체의 시간 값에 null이 들어왔다 -> 도메인에서 예외처리
시간 객체의 시간 값은 중복이면 안된다 -> 도메인 객체만으로는 안되고 저장된 다른 객체를 봐야함 -> 서비스에서 예외처리
[Controller]
↓ 요청 전달 / 응답 반환 [DTO][Service]
↓ 비즈니스 로직 처리
[Repository]
↓ DB 접근
[Domain]
↔ DB 매핑
+) [Exception] : 예외처리
+) [View] : 화면에 보이는 것
이와 같은 관계를 갖고 애플리케이션은 설계되는데,
Controller 는 요청/응답 처리에 집중해야하며, Repository는 DB 접근에 집중해야함.
또한, 비즈니스 로직은 Service에서 진행해야함.
-> 이 부분 제발 좀 ... 지키자 희정아 ...
✅ 설계 시작 전에 계층별 책임 적어보기 "이건 Service 책임인가?" 먼저 생각하기
✅ 단위 테스트 작성 비즈니스 로직은 Service에서 테스트할 수 있어야 함
✅ 객체 간 의존 구조 그려보기 간단한 UML, 화살표 그리기 등으로 시각화
✅ Controller에서 로직이 길어지면 "서비스로 옮길 수 있지 않을까?" 되묻기
@Autowired 없이도 작동 ?public class ReservationController {
private final ReservationService reservationService;
@Autowired
public ReservationController(ReservationService reservationService) {
this.reservationService = reservationService;
}
-> 이 코드에서는 @Autowired 없이도 작동 가능함.
Spring 4.3부터 클래스에 생성자가 하나 뿐이라면, 자동으로 주입가능한 Bean을 찾아서 주입함.
당연히, 생성자 2개 이상일 때는 생략하지 않아도 됨.
ResponseEntity
.status(HttpStatus.CREATED)
.location(location)
.body(ReservationResponse.from(savedReservation));
↓
ResponseEntity
.created(location)
.body(ReservationResponse.from(savedReservation));
로 축약할 수 있음.
+)
ok = 200
created(URI) = 201
accepted() = 202
noContent() = 204
badRequest() = 400
notFound() = 404
boolean deleted = timeService.deleteTime(id);
if (!deleted) {
return ResponseEntity.badRequest().build();
}
본래 Controller에서 단순 삭제 성공 여부만 판단하고 있다고 생각하여 Controller에 예외처리 로직을 작성하였는데, 삭제 실패와 같은 비즈니스 로직 실패는 Service에서 다루는 것이 맞는 방식이다.
따라서, 말씀해주신대로,
삭제 실패 -> Service
성공 흐름 -> Controller
나머지 예외는 exception에서 처리하는 것으로 수정하였다.
<수정 후>
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTime(@PathVariable Long id) {
timeService.deleteTime(id);
return ResponseEntity.noContent().build();
}
2.Service에서 예외 표현함(비즈니스 로직 예외)
public boolean deleteTime(Long id) {
if(!timeRepository.deleteById(id)) {
throw new InvalidTimeException("없는 시간입니다." + id);
}
return timeRepository.deleteById(id);
}
도메인 객체에 setter를 열어두는 것은 객체 상태를 마음대로 바꿀 수 있어, 캡슐화에 어긋나고, 객체 불변성 유지에 어려워짐.
보통 도메인 객체를 수정해야하는 경우
서비스 클래스에서 도메인 객체를 받아
서비스 클래스에서 상태 변경을 수행하고,
변경된 도메인 객체를 Repository에 저장하는 방식을 사용할 것 !
까먹지 말긔 ...