-
Notifications
You must be signed in to change notification settings - Fork 37
[1주차] 멀티 모듈 설계하기 #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| /gradlew text eol=lf | ||
| *.bat text eol=crlf | ||
| *.jar binary |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| HELP.md | ||
| .gradle | ||
| build/ | ||
| !gradle/wrapper/gradle-wrapper.jar | ||
| !**/src/main/**/build/ | ||
| !**/src/test/**/build/ | ||
|
|
||
| ### STS ### | ||
| .apt_generated | ||
| .classpath | ||
| .factorypath | ||
| .project | ||
| .settings | ||
| .springBeans | ||
| .sts4-cache | ||
| bin/ | ||
| !**/src/main/**/bin/ | ||
| !**/src/test/**/bin/ | ||
|
|
||
| ### IntelliJ IDEA ### | ||
| .idea | ||
| *.iws | ||
| *.iml | ||
| *.ipr | ||
| out/ | ||
| !**/src/main/**/out/ | ||
| !**/src/test/**/out/ | ||
|
|
||
| ### NetBeans ### | ||
| /nbproject/private/ | ||
| /nbbuild/ | ||
| /dist/ | ||
| /nbdist/ | ||
| /.nb-gradle/ | ||
|
|
||
| ### VS Code ### | ||
| .vscode/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,21 @@ | ||
| ## [본 과정] 이커머스 핵심 프로세스 구현 | ||
| [단기 스킬업 Redis 교육 과정](https://hh-skillup.oopy.io/) 을 통해 상품 조회 및 주문 과정을 구현하며 현업에서 발생하는 문제를 Redis의 핵심 기술을 통해 해결합니다. | ||
| > Indexing, Caching을 통한 성능 개선 / 단계별 락 구현을 통한 동시성 이슈 해결 (낙관적/비관적 락, 분산락 등) | ||
| ## Multi Module Design | ||
| Module을 나누는 기준은 여러 개가 있지만, 이번 프로젝트에서는 Layered Architecture 에서 설명되는 Layer 별로 구분하였습니다. | ||
| - module-presentation: 사용자의 요청을 받고, 응답한다. | ||
| - module-application: 사용자가 요청한 기능을 처리한다. | ||
| - module-domain: 시스템이 제공할 도메인 규칙을 구현한다. | ||
| - module-infra: 데이터베이스 같은 외부 시스템과의 연동을 처리한다. | ||
| - module-core: Convertor, Error-Response 와 같이 특정 layer에 종속되지 않는 기능을 처리한다. | ||
|
|
||
| 각 module은 하위 module에만 의존합니다. <br> | ||
| JPA에서 다른 기술로 변경될 것이라고 생각하여 DIP를 적용하는 것은 오버 엔지니어링이라고 생각되어, JPA 기술에 종속되지 않는 POJO 객체들을 생성하지 않고, 도메인 계층에 infra 계층에 종속되는 코드로 구현하였습니다. (@Entity 클래스, JpaRepository 등) | ||
|
|
||
| ## Table Design | ||
|  | ||
| - Movie 테이블과 Theater 테이블은 N:N 관계로 중간에 Screening 테이블을 두고 있습니다. | ||
| - Theater 별로 시간표가 구분되는 것을 고려하여 Screening 테이블은 상영 시간표 정보를 포함하고 있습니다. | ||
| - 좌석별 등급 등 좌석 개별의 특성이 추가될 수 있다고 생각하여 Seat 테이블을 생성하였습니다. | ||
| - Theater 테이블과 Seat 테이블은 1:N 관계입니다. | ||
| - Seat 테이블과 User 테이블은 1:N 관계입니다. | ||
|
|
||
| ## N+1 문제 해결 | ||
| 저는 N+1 문제가 ID 참조을 사용하기 때문이라고 생각합니다. 따라서 해당 프로젝트에 간접참조를 사용하여, N+1 문제를 해결하고자 합니다. 뿐만 아니라, 간접 참조를 사용하면 도메인 간 물리적인 연결을 제거하기 때문에 도메인 간 의존을 강제적으로 제거합니다. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| plugins { | ||
| id 'java' | ||
| id 'org.springframework.boot' version '3.4.1' | ||
| id 'io.spring.dependency-management' version '1.1.7' | ||
| } | ||
|
|
||
| repositories { | ||
| mavenCentral() | ||
| } | ||
|
|
||
| allprojects { | ||
| // 모든 하위 모듈들에 이 설정을 적용 | ||
| group 'com.example' | ||
| version '0.0.1-SNAPSHOT' | ||
| sourceCompatibility = '17' | ||
|
|
||
| apply plugin: 'java' | ||
| apply plugin: 'java-library' | ||
| apply plugin: 'org.springframework.boot' | ||
| apply plugin: 'io.spring.dependency-management' | ||
|
|
||
| configurations { | ||
| compileOnly { | ||
| extendsFrom annotationProcessor | ||
| } | ||
| } | ||
|
|
||
| repositories { | ||
| mavenCentral() | ||
| } | ||
|
|
||
| dependencies { // 모든 하위 모듈에 추가 될 의존성 목록입니다. | ||
| implementation 'org.springframework.boot:spring-boot-starter' | ||
| compileOnly 'org.projectlombok:lombok' | ||
| annotationProcessor 'org.projectlombok:lombok' | ||
| testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
| testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 5.8.1 와 같이 버전을 의존성 마다 직접 명시하는 방법대신, 의존성 버전들을 한곳에서 관리하는 방법도 있습니다 !
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dependencyManagement 말씀하시는 걸까요?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @yeonjookang 네 dependencyManagement 도 있지만, 명시적인 의존성이 필요한 경우도 있는데요, |
||
| testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' | ||
| } | ||
|
|
||
| test { | ||
| useJUnitPlatform() | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| version: '3.8' | ||
| services: | ||
| mysql: | ||
| image: mysql:latest # 이미 로컬에 있는 MySQL 이미지 사용 | ||
| environment: | ||
| MYSQL_ROOT_PASSWORD: 1234 # 루트 계정 비밀번호 | ||
| MYSQL_DATABASE: hanghae99 # 생성할 기본 데이터베이스 | ||
| MYSQL_USER: user # 사용자 계정 (선택) | ||
| MYSQL_PASSWORD: password # 사용자 비밀번호 (선택) | ||
| ports: | ||
| - "3305:3306" # 호스트의 3306 포트를 컨테이너의 3306 포트와 연결 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| distributionBase=GRADLE_USER_HOME | ||
| distributionPath=wrapper/dists | ||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip | ||
| networkTimeout=10000 | ||
| validateDistributionUrl=true | ||
| zipStoreBase=GRADLE_USER_HOME | ||
| zipStorePath=wrapper/dists |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Layer 를 기준으로 모듈을 잘 구성해주셨네요 :) 많은 고민의 흔적이 보이는 것 같아서 좋습니다 ㅎㅎ
추가로 몇가지 리뷰를 드리면,
Layered Architecture 를 사용한다고 해서 모듈도 완전 동일한 형태의 Layer 로 나눌 필요는 없습니다 :)
presentation + application 을 하나의 모듈로 구성할 수도 있을 것 같아요.
그리고 추후에 분리가 필요하면 그때 분리해도 될 것 같습니다.
단, 지금은 현재 모듈 형태를 그대로 유지하고 2주차로 넘어가주세요 ! Layer 를 많이 쪼갠 형태의 모듈 구조를 사용하는 경우 프로젝트가 점진적으로 발전하면서 어떤 영향이 있을지, 직접 느껴보는 것도 많은 인사이트를 느낄 수 있을 것 같습니다. 진행 하시면서 presentation or application 이 통합되어도 괜찮겠다고 판단되는 지점이 올 수 있고, 혹은 분리해도 충분히 괜찮다고 느끼실 수도 있을 것 같아요.
core 모듈은 common 역할을 하는 모듈로 보여지는데, 경우에 따라서 해당 모듈을 anti-pattern 으로 보기도 합니다. (팀 by 팀)
공통 기능을 위한 하나의 모듈을 만드는 경우, 개발자들이 클래스들을 설계 하면서 (실제로 공통 역할이 아니지만) core(or common) 모듈에 패키지를 생성해서 정의하는 경우도 있습니다 !
전체적으로 모듈을 잘 구성해주셨습니다 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
모듈을 구성하면서 너무 많은 모듈로 쪼갠 건 아닐까 라는 고민이 많이 되었습니다! 멘토님 말씀대로 직접 경험하면서 느껴봐야겠습니다! core 모듈은 제 프로젝트에서는 없애는 게 맞는 것 같아요 수정하겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| AS-IS
infra -> domain <- application <- presentation
infra <- application <- presentation
| TO-BE : infra -> domain <- application <- presentation
에서, MoviesNowShowingDbDto를 domain 모듈로 위치를 변경해도 application이 여전히 JpaRepository를 참조하고 있기 때문에 TO-BE 관계로 변경은 되지 않습니다.
그럼에도, MoviesNowShowingDbDto를 domain 모듈로 위치하는 것이 좋을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TO-BE 로 만들려면 MovieRepository 또한 domain 으로 이동시켜야 합니다.
그러면 현재 설계 기준으로 infra 가 모듈이 없어지겠죠?, 즉, 의존성 방향을 TO-BE 처럼� 고수준(domain) 으로 흐르게끔 만드는 것이 clean architecture 입니다.
앞으로 남은 주차를 진행하면서 Presentation > Application > Persistence > Database 와 같이 단방향으로 흐르는 Layered Architecture 를 끝까지 가져가면서 개선해볼지, 혹은 진행하면서 다른 아키텍처로 변경할 것인지 자신만의 trade-off 기준을 세워서 선택해주시면 됩니다 :)