diff --git a/README.md b/README.md new file mode 100644 index 000000000..8a7567008 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# 간단 스프링 어플리케이션 + +## Core +### 8단계 +- [x] 시간 테이블 설정 +- [x] 시간 조회, 추가, 삭제 기능 추가 +- [x] DB 초기 값 설정 +### 9단계 +- [x] 해당 시간이 존재하는 예약이 남아있을 때, 시간 삭제 못하도록 예외처리 +- [x] 중복된 시간(시간의 time값이 같은) DB에 저장하지 못하도록 예외처리 +### 10단계 + +### 고민사항 +- 프로젝트 구조:
+[도메인 우선 vs 레이어 우선](https://codewithandrea.com/articles/flutter-project-structure/) +
+우선 프로젝트 구조를 잘 가져가면 가져올 수 있는 효과가 무엇이 있는지 궁금합니다 +미션처럼 크기가 작은 프로젝트에서는 레이어 구조 또한 괜찮은 방향 같지만 서비스 크기가 +큰 앱을 개발하는데 있어서는 도메인 단위로 나누는 것이 프로젝트으 복잡성을 줄일 수 있게 되는거 같습니다. +그러나 도메인 단위로 나뉘게 된다면 중복된 Entity(테이블이 서로 연관되어 있을 가능성이 크기 )에 대한 관리를 어떻게 하는지 궁금합니다! + +[Repository 계층, 도메인과 영속성 엔티티 사이의 간극](https://kokodakadokok.tistory.com/entry/Repository-%EA%B3%84%EC%B8%B5-%EB%8F%84%EB%A9%94%EC%9D%B8%EA%B3%BC-%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%82%AC%EC%9D%B4%EC%9D%98-%EA%B0%84%EA%B7%B9-%EB%A7%A4%EA%BE%B8%EA%B8%B0) +
+스프링은 기술적으로 편의를 위해서? +데이터베이스 테이블과 Java의 class를 매핑해준 Jpa 기술을 사용하는 것으로 알고 있습니다. +Jpa의 @Entity라는 annotation을 사용하여 정의하는 Class는 Domain과의 간극이 존재한다고 +생각합니다. 아직 이 간극에 대해 완벽히 알지 못하여서 Entity와 Domain을 혼돈해서 사용 +하게 되는거 같습니다. 최소한 Service를 나눌때 Domain기준으로 나누고 싶은데 +Entity(Table)단위로 나뉘게 되는 경향이 강한거 같습니다! +지금은 TimeService와 ReservationService가 나뉘어져 있지만 +TimeService와 ReservationService가 하고 있는 역할은 결국 예약이라는 하나의 도메인에 속한다는 생각이 들어 +이 둘을 하나의 ReservationService로 합할까 고민하게 되었습니다. + +- Entity 패키지를 따로 둔 이유
+제가 따로 Entity 패키지를 분리한 이유는 Service, Repository, Contoller(controller에서 dto를 entity로 변환하기에) 모두다 +Entity를 바라보기 때문에 분리하게 되었습니다. + + +## JDBC +### 5단계 +- [x] 데이터베이스 설정 +- [x] 데이터베이스 연결 + +### 6단계 +- [x] 데이터 조회하기 + +### 7단계 +- [x] 데이터 추가하기 +- [x] 데이터 삭제하기 +- [x] 데이터 삭제 잘못된 요청시 예외처리 + +### 7 단계 고민 +- Entity id에 setter 함수를 두면 위험성이 크다는 생각이 들어 새로운 객체를 생성해서 return 해야 되겠다 라는 생각을 가지게 되었습니다 + 과연 spring은 새로 db에 생성된 entity에 관해 어떻게 id를 주입하나 궁금함군요 🤔 + +### 질문사항 +- 어플리케이션 실행중 어디서든 exception이 발생하면 ControllerAdvice를 exception과 관련된 응답을 전달해줍니다. + 대부분의 exception을 최종적으로 ControllerAdvice에서 처리를 하다 보니 Service, Repository, Entity 등등 아무데서나 exception을 + 던질 수 있겠다라는 착각을 하게 되는것 같습니다. 혹시 exception은 보통 어느 layer에서 처리를 하는지 궁금하며 예외처리는 어떻게 관리 + 되는지 궁금합니다! + diff --git a/build.gradle b/build.gradle index b11bfe1be..9912baed1 100644 --- a/build.gradle +++ b/build.gradle @@ -16,11 +16,22 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-log4j2' + + runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.rest-assured:rest-assured:5.3.1' } +configurations { + configureEach { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' + } +} + test { useJUnitPlatform() } diff --git a/src/main/java/roomescape/api/ReservationController.java b/src/main/java/roomescape/api/ReservationController.java index 9ce39e3f0..3f2bef776 100644 --- a/src/main/java/roomescape/api/ReservationController.java +++ b/src/main/java/roomescape/api/ReservationController.java @@ -1,12 +1,13 @@ package roomescape.api; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import roomescape.api.dto.ReservationRequestDto; import roomescape.api.dto.ReservationResponseDto; import roomescape.entity.Reservation; -import roomescape.repository.ReservationRepositoryImpl; import roomescape.service.ReservationService; import java.util.List; @@ -16,30 +17,38 @@ public class ReservationController { private final ReservationService reservationService; - public ReservationController() { - this.reservationService = new ReservationService(new ReservationRepositoryImpl()); + public ReservationController( + @Autowired ReservationService reservationService + ) { + this.reservationService = reservationService; } @GetMapping("/reservations") public ResponseEntity> readReservations() { - List reservations = reservationService.readReservations(); + return ResponseEntity + .ok() + .body( + reservationService.readReservations().stream(). + map(ReservationResponseDto::fromEntity) + .toList() + ); + } - List response - = reservations.stream() - .map(ReservationResponseDto::fromEntity) - .toList(); + @GetMapping("/reservations/{id}") + public ResponseEntity readReservations(@PathVariable Long id) { + Reservation reservation = reservationService.readReservation(id); return ResponseEntity - .status(HttpStatus.OK) - .body(response); + .ok() + .body(ReservationResponseDto.fromEntity(reservation)); } @PostMapping("/reservations") - public ResponseEntity createReservation(@RequestBody ReservationRequestDto reservationDto) { - Reservation reservation = reservationService.createReservation(reservationDto.toEntity()); - + public ResponseEntity createReservation( + @RequestBody @Valid ReservationRequestDto reservationDto + ) { ReservationResponseDto response = - ReservationResponseDto.fromEntity(reservation); + ReservationResponseDto.fromEntity(reservationService.createReservation(reservationDto.toEntity())); return ResponseEntity .status(HttpStatus.CREATED) @@ -50,8 +59,6 @@ public ResponseEntity createReservation(@RequestBody Res @DeleteMapping("/reservations/{id}") public ResponseEntity deleteReservation(@PathVariable Long id) { reservationService.deleteReservation(id); - return ResponseEntity - .status(HttpStatus.NO_CONTENT) - .build(); + return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/roomescape/api/StaticPageController.java b/src/main/java/roomescape/api/StaticPageController.java index 369caa10d..d48fa7026 100644 --- a/src/main/java/roomescape/api/StaticPageController.java +++ b/src/main/java/roomescape/api/StaticPageController.java @@ -10,8 +10,14 @@ public String mainPage() { return "home"; } + @GetMapping("/reservation") public String reservationPage() { - return "reservation"; + return "new-reservation"; + } + + @GetMapping("/time") + public String timesPage() { + return "time"; } } diff --git a/src/main/java/roomescape/api/TimeController.java b/src/main/java/roomescape/api/TimeController.java new file mode 100644 index 000000000..f95750372 --- /dev/null +++ b/src/main/java/roomescape/api/TimeController.java @@ -0,0 +1,57 @@ +package roomescape.api; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import roomescape.api.dto.TimeRequestDto; +import roomescape.api.dto.TimeResponseDto; +import roomescape.entity.Time; +import roomescape.service.TimeService; + +import java.util.List; + +@RestController +@RequestMapping("/times") +public class TimeController { + + private final TimeService timeService; + + public TimeController( + @Autowired TimeService timeService + ) { + this.timeService = timeService; + } + @GetMapping + public ResponseEntity> readTimes() { + List