Skip to content

[1주차] 멀티 모듈 설계 및 요구사항 구현#20

Open
sina-log wants to merge 69 commits intohanghae-skillup:mainfrom
sina-log:develop
Open

[1주차] 멀티 모듈 설계 및 요구사항 구현#20
sina-log wants to merge 69 commits intohanghae-skillup:mainfrom
sina-log:develop

Conversation

@sina-log
Copy link

@sina-log sina-log commented Jan 11, 2025

제목(title)

[1주차] 멀티 모듈 설계 및 상영 중인 영화 조회 API 구현

작업 내용

Doamin

  • Movie
    • Long id PK
    • String title
    • String grade
    • String genre
    • Integer runningTime
    • Date releaseDate
    • String thumbnailUrl
  • Theater
    • Long id PK
    • String name
  • Schedule
    • Long id PK
    • DateTime startTime
    • DateTime endTime

멀티모듈 구성

  • api : 외부 통신 레이어
  • application : 서비스 레이어
  • domain : 도메인 레이어
  • infra : 인프라 레이어

발생했던 문제와 해결 과정을 남겨 주세요.

  • 멀티모듈 구성에 의존성 오류에서 좀 애를 먹었습니다.

이번 주차에서 고민되었던 지점이나, 어려웠던 점을 알려 주세요.

  • 개인적인 시간이 없는 이슈가 가능 문제 였습니다. 다음주 부턴 시간확보를 더 많이 해보겠습니다.

리뷰 포인트

  • 도메인 분리가 잘 되었는지 궁금 합니다.
  • 도메인들이 JPA 연관관계를 잘 맺었는지랑 꼭 맺어야 하는지 궁금합니다

기타 질문

  • JPA 연관관계 없이 그냥 필요할 때마다 조회를 따로하는건 안되는지 궁금합니다.

@BAEKJungHo
Copy link
Contributor

@reversalSpring 안녕하세요 준형님 ~ 열심히 참여 해주셔서 감사합니다!

1주차 리뷰 진행하도록 하겠습니다 :)

"com.movie.api",
"com.movie.application",
"com.movie.infra",
"com.movie.domain"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 부분들은 asterisk(*) 로 한줄로 처리가 가능할 것 같아요 :)

import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불필요한 컨트롤러는 제거 부탁드립니다 :)


private final MovieService movieService;

public MovieController(MovieService movieService) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MovieService 필드가 final 로 되어있어서, @requiredargsconstructor 를 사용하면 생성자를 제거할 수 있을 것 같습니다 :)

implementation project(":movie-infra")

implementation 'org.springframework.boot:spring-boot-starter:3.1.0'
implementation 'org.springframework:spring-tx:5.3.10'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

버전(e.g 3.1.0) 을 의존성 마다 직접 명시하는 방법대신, 의존성 버전들을 한곳에서 관리하는 방법도 있습니다 !


@Getter
@Setter
public class MovieResponseDto {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

데이터 전송 객체에 자바의 record 를 사용해보는 것을 어떨까요 ?

@Getter
@Setter
@Table(name = "movie")
public class Movie extends BaseEntity {
Copy link
Contributor

@BAEKJungHo BAEKJungHo Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Setter 를 사용하게 되면 객체지향적인프로그래밍에서 벗어나 절차지향으로 퇴화된다는 특징이 있습니다.

캡슐화의 특성상, 내부 데이터는 접근 권한 제어를 통해 숨겨져야 하며, 외부에서는 클래스에서 제공하는 제한된 인터페이스를 통해서만 내부 데이터에 접근할 수 있어야 합니다 !

private Theater theater;

private LocalDateTime startTime;
private LocalDateTime endTime;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

타입이 LocalDateTime 인 경우 변수명도 startDateTime 등으로 해주는 것이, 네이밍과 타입간의 불일치를 해소해줄 것 같습니다 ! 저는 개인적으로 at 이라는 네이밍을 선호합니다.

private String createdBy;
private java.time.LocalDateTime createdAt;
private String updatedBy;
private java.time.LocalDateTime updatedAt;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BaseEntity 를 사용 중이셔서, 하위 클래스에 system properties 를 중복 정의할 필요는 없을 것 같아요 !

import org.springframework.stereotype.Repository;

@Repository
public interface MovieJpaRepository extends JpaRepository<Movie, Long>, MovieRepository {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MovieRepository 인터페이스를 통해서 application 에서 infra 를 직접 참조하지 않도록 DIP 를 적용해주신 부분 훌륭합니다 👍🏿


@Override
@EntityGraph(attributePaths = {"movie", "theater"})
List<Schedule> findAllFetchMovieTheater();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

movie prefix 는 생략 가능해보입니다 ! 있어도 무방하지만, 저는 생략 하더라도 의미전달이 충분히 된다면
생략하는 것을 선호합니다 :)


return movieList.stream()
.map(movie -> {
List<ScheduleInfo> scheduleInfos = scheduleList.stream()
Copy link
Contributor

@BAEKJungHo BAEKJungHo Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2개의 쿼리를 통해서 Nested Stream 을 사용 중인데, scheduleList 를 통해서도 Movie 를 가져올 수 있을 것 같습니다. movieList 를 얻기 위한 로직이 꼭 필요한지 고민해주시면 좋을 것 같아요 :)


@Service
@Transactional(readOnly = true)
public class MovieService {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNowShowingMovies 메서드는 되게 심플한 메서드인데, DTO 로 변환하는 로직 때문에 코드가 길어져서 가독성이 떨어진다는 단점이 있습니다 ! 가독성 개선을 위한 추가적인 고민을 해주시면 좋을 것 같습니다 :)

- api : 외부 통신 레이어
- application : 서비스 레이어
- domain : 도메인 레이어
- infra : 인프라 레이어
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layered 형식으로 모듈을 잘 구성 해주셨네요 ! 다만, application 과 api 모듈이 별도로 구분되어야 하는지는 추가적인 고민이 필요해 보입니다.

api 모듈에는 Controller 가 존재하고, application 모듈에는 dto 가 존재하는데, 사실 이 둘은 같은 Layer 에 포함되어야 하는 성격이라고 생각됩니다. 즉, 계층을 많이 추가하는 경우 응집성 이 떨어질 수 있다는 단점이 있습니다.

- Schedule
- Long id PK
- DateTime startTime
- DateTime endTime
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좌석 테이블이 미반영된 것 같습니다.

this.movieService = movieService;
}

@GetMapping("/api/v1/movies/now-showing")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/api/v1 실무에서 자주 사용되는 패턴을 잘 적용 해주셨네요 👍🏿

}

public List<MovieResponseDto> getNowShowingMovies() {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불필요한 줄바꿈이 있습니다.

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'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker-compose 를 누락하신 것 같아요 ! 2주차에서는 redis 도 사용해야하니
2주차에서 docker-compose 설정을 반영해주시면 좋을 것 같습니다 :)

@BAEKJungHo
Copy link
Contributor

BAEKJungHo commented Jan 11, 2025

@reversalSpring

좋았던 점

  • JPA Repository 를 infra 에 정의하고, application 모듈에서 infra 를 참조하지 않도록 인터페이스 설계 사상 을 잘 적용해주셨습니다.
  • Layered 형태로 모듈을 잘 구성해주셨습니다.

아쉬웠던 점

  • 확보하신 시간이 부족하셔서 몇몇 요구사항이 누락, 로직 작성에 대한 고민 할 시간이 부족한 모습이 느껴졌습니다.

리뷰 포인트 및 추가 질문에 대한 답변

도메인 분리가 잘 되었는지 궁금 합니다.

  • MovieService 의 위치가 application 모듈이 맞을지 고민이 필요합니다.
  • Movie, Theater, Schedule 엔티티에 대한 설계는 필요한 컬럼들을 잘 정의해주셨습니다.
  • Domain 모듈내에서 패키지를 도메인 단위가 아닌 기능(entity, repository) 단위로 구분해주셨는데, 도메인 단위 패키지 형식이 추후에 코드 구조를 전반적으로 파악하는데 도움이 됩니다.

도메인들이 JPA 연관관계를 잘 맺었는지랑 꼭 맺어야 하는지 궁금합니다

  • Schedule 에 ManyToOne 을 잘 사용해주셨습니다. 다만 N+1 문제를 해결하기 위해서 default_batch_fetch_size 옵션도 고려되면 좋을 것 같습니다.
  • 꼭 맺지 않아도 됩니다. 간접 참조 방식(e.g 객체가 아닌 Id 를 참조하는 방식)을 사용해도 됩니다. 다만 간접 참조 방식을 사용하는 경우 JPA 의 dirty-checking, entity graph 탐색 등의 기능을 포기하는게 되기 때문에 JPA 대신 다른 기술을 고려할 수 있습니다.

꼭 필요한 연관관계를 맺는 방법 도 좋은 습관이라고 생각됩니다.

JPA 연관관계 없이 그냥 필요할 때마다 조회를 따로하는건 안되는지 궁금합니다.

필요할 때마다 조회하셔도 됩니다. 대신 필요할 때마다 조회를 잘 하기 위해서는 쿼리를 직접 작성해야할 것이며 @Query, QueryDsl 등의 도구를 충분히 활용해주셔야합니다. (또한 내 코드가 너무 절차지향적으로 가고 있는 것은 아닌지 중간중간 확인도 필요합니다.)

@phyeran phyeran changed the base branch from main to reversalSpring January 17, 2025 02:06
@spartacontents spartacontents deleted the branch hanghae-skillup:main January 17, 2025 03:20
…이동, RateLimitInterceptor를 API 모듈로 이동, 불필요한 테스트 파일 제거 및 테스트 코드 개선
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants