Conversation
|
@reversalSpring 안녕하세요 준형님 ~ 열심히 참여 해주셔서 감사합니다! 1주차 리뷰 진행하도록 하겠습니다 :) |
| "com.movie.api", | ||
| "com.movie.application", | ||
| "com.movie.infra", | ||
| "com.movie.domain" |
There was a problem hiding this comment.
이런 부분들은 asterisk(*) 로 한줄로 처리가 가능할 것 같아요 :)
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| public class HelloController { |
There was a problem hiding this comment.
불필요한 컨트롤러는 제거 부탁드립니다 :)
|
|
||
| private final MovieService movieService; | ||
|
|
||
| public MovieController(MovieService movieService) { |
There was a problem hiding this comment.
MovieService 필드가 final 로 되어있어서, @requiredargsconstructor 를 사용하면 생성자를 제거할 수 있을 것 같습니다 :)
movie-application/build.gradle
Outdated
| implementation project(":movie-infra") | ||
|
|
||
| implementation 'org.springframework.boot:spring-boot-starter:3.1.0' | ||
| implementation 'org.springframework:spring-tx:5.3.10' |
There was a problem hiding this comment.
버전(e.g 3.1.0) 을 의존성 마다 직접 명시하는 방법대신, 의존성 버전들을 한곳에서 관리하는 방법도 있습니다 !
|
|
||
| @Getter | ||
| @Setter | ||
| public class MovieResponseDto { |
There was a problem hiding this comment.
데이터 전송 객체에 자바의 record 를 사용해보는 것을 어떨까요 ?
| @Getter | ||
| @Setter | ||
| @Table(name = "movie") | ||
| public class Movie extends BaseEntity { |
There was a problem hiding this comment.
@Setter 를 사용하게 되면 객체지향적인프로그래밍에서 벗어나 절차지향으로 퇴화된다는 특징이 있습니다.
캡슐화의 특성상, 내부 데이터는 접근 권한 제어를 통해 숨겨져야 하며, 외부에서는 클래스에서 제공하는 제한된 인터페이스를 통해서만 내부 데이터에 접근할 수 있어야 합니다 !
| private Theater theater; | ||
|
|
||
| private LocalDateTime startTime; | ||
| private LocalDateTime endTime; |
There was a problem hiding this comment.
타입이 LocalDateTime 인 경우 변수명도 startDateTime 등으로 해주는 것이, 네이밍과 타입간의 불일치를 해소해줄 것 같습니다 ! 저는 개인적으로 at 이라는 네이밍을 선호합니다.
| private String createdBy; | ||
| private java.time.LocalDateTime createdAt; | ||
| private String updatedBy; | ||
| private java.time.LocalDateTime updatedAt; |
There was a problem hiding this comment.
BaseEntity 를 사용 중이셔서, 하위 클래스에 system properties 를 중복 정의할 필요는 없을 것 같아요 !
| import org.springframework.stereotype.Repository; | ||
|
|
||
| @Repository | ||
| public interface MovieJpaRepository extends JpaRepository<Movie, Long>, MovieRepository { |
There was a problem hiding this comment.
MovieRepository 인터페이스를 통해서 application 에서 infra 를 직접 참조하지 않도록 DIP 를 적용해주신 부분 훌륭합니다 👍🏿
|
|
||
| @Override | ||
| @EntityGraph(attributePaths = {"movie", "theater"}) | ||
| List<Schedule> findAllFetchMovieTheater(); |
There was a problem hiding this comment.
EntityGraph 를 사용하는 경우 left outer join 이 발생하는 것으로 알고 있는데, left outer join 이 맞을지 고민해주시면 좋을 것 같아요 !
settings.gradle
Outdated
| include 'movie-domain' | ||
| include 'movie-infra' | ||
| include 'movie-application' | ||
| include 'movie-api' No newline at end of file |
There was a problem hiding this comment.
movie prefix 는 생략 가능해보입니다 ! 있어도 무방하지만, 저는 생략 하더라도 의미전달이 충분히 된다면
생략하는 것을 선호합니다 :)
|
|
||
| return movieList.stream() | ||
| .map(movie -> { | ||
| List<ScheduleInfo> scheduleInfos = scheduleList.stream() |
There was a problem hiding this comment.
2개의 쿼리를 통해서 Nested Stream 을 사용 중인데, scheduleList 를 통해서도 Movie 를 가져올 수 있을 것 같습니다. movieList 를 얻기 위한 로직이 꼭 필요한지 고민해주시면 좋을 것 같아요 :)
|
|
||
| @Service | ||
| @Transactional(readOnly = true) | ||
| public class MovieService { |
There was a problem hiding this comment.
MovieService 가 application 모듈에 있는 것을 보아, 비지니스로직을 오케스트레이션 하는 역할의 애플리케이션 서비스 로 설계를 해주신 것으로 생각됩니다.
만약, 현재 구조에서 도메인 로직을 담당하는 도메인 서비스가 필요한 경우에는 어떤 네이밍을 사용할지, 현재 작성된 서비스의 로직이 애플리케이션 서비스에 있는게 맞을지 등에 대한 추가적인 고민이 있으면 좋을 것 같아요 !
사실 현재 1주차 요구사항만 봤을때는 비지니스 로직을 오케이스트레이션 할 만한 요구 사항이 없어서 애플리케이션 서비스가 필요 없을 것 같습니다!
MovieService 가 도메인 서비스였다면 Domain 모듈로 이동을 시키실 것 같아요. 이때 MovieResponseDto 도 같이 Domain 모듈로 들어가게 될텐데 이 경우 domain <-> application 간의 순환 참조가 발생할 수 있습니다.
이 부분을 어떻게 해결할 수 있을지 고민해주시면 좋을 것 같습니다 !
| dto.setRunningTime(movie.getRunningTime() != null ? movie.getRunningTime() : 0); | ||
| dto.setGenre(movie.getGenre()); | ||
| dto.setSchedules(scheduleInfos); | ||
| return dto; |
There was a problem hiding this comment.
getNowShowingMovies 메서드는 되게 심플한 메서드인데, DTO 로 변환하는 로직 때문에 코드가 길어져서 가독성이 떨어진다는 단점이 있습니다 ! 가독성 개선을 위한 추가적인 고민을 해주시면 좋을 것 같습니다 :)
| - api : 외부 통신 레이어 | ||
| - application : 서비스 레이어 | ||
| - domain : 도메인 레이어 | ||
| - infra : 인프라 레이어 |
There was a problem hiding this comment.
Layered 형식으로 모듈을 잘 구성 해주셨네요 ! 다만, application 과 api 모듈이 별도로 구분되어야 하는지는 추가적인 고민이 필요해 보입니다.
api 모듈에는 Controller 가 존재하고, application 모듈에는 dto 가 존재하는데, 사실 이 둘은 같은 Layer 에 포함되어야 하는 성격이라고 생각됩니다. 즉, 계층을 많이 추가하는 경우 응집성 이 떨어질 수 있다는 단점이 있습니다.
| - Schedule | ||
| - Long id PK | ||
| - DateTime startTime | ||
| - DateTime endTime |
| this.movieService = movieService; | ||
| } | ||
|
|
||
| @GetMapping("/api/v1/movies/now-showing") |
There was a problem hiding this comment.
/api/v1 실무에서 자주 사용되는 패턴을 잘 적용 해주셨네요 👍🏿
| } | ||
|
|
||
| public List<MovieResponseDto> getNowShowingMovies() { | ||
|
|
movie-api/build.gradle
Outdated
| implementation 'org.springframework.boot:spring-boot-starter' | ||
| implementation 'org.springframework.boot:spring-boot-starter-web' | ||
| implementation 'org.springframework.boot:spring-boot-starter-data-jpa' | ||
| implementation 'com.h2database:h2' |
There was a problem hiding this comment.
docker-compose 를 누락하신 것 같아요 ! 2주차에서는 redis 도 사용해야하니
2주차에서 docker-compose 설정을 반영해주시면 좋을 것 같습니다 :)
|
@reversalSpring 좋았던 점
아쉬웠던 점
리뷰 포인트 및 추가 질문에 대한 답변
꼭 필요한 연관관계를 맺는 방법 도 좋은 습관이라고 생각됩니다.
필요할 때마다 조회하셔도 됩니다. 대신 필요할 때마다 조회를 잘 하기 위해서는 쿼리를 직접 작성해야할 것이며 |
…이동, RateLimitInterceptor를 API 모듈로 이동, 불필요한 테스트 파일 제거 및 테스트 코드 개선
제목(title)
[1주차] 멀티 모듈 설계 및 상영 중인 영화 조회 API 구현
작업 내용
Doamin
멀티모듈 구성
발생했던 문제와 해결 과정을 남겨 주세요.
이번 주차에서 고민되었던 지점이나, 어려웠던 점을 알려 주세요.
리뷰 포인트
기타 질문