-
Notifications
You must be signed in to change notification settings - Fork 1
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
[BSVR-44] 영속성 계층 (JPA) 모듈 추가 #6
Conversation
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.
JPA 연결 확인용!!
실제 DB 인스턴스와 연결할 땐 환경변수를 이용하거나, gitignore 등을 이용하는 식으로 public 환경에 노출되지 않게 설정할 예정~!
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.
외부 의존성을 관리하는 infrastructure 모듈!
여기 하위에 JPA, Redis 등등을 모듈로 관리할 예정이야
e.g.,) redis 사용이 필요하다면? -> infrastructure:redis 모듈을 새로 추가해서 사용
구현체를 갈아끼우기 쉽게 하기 위해서 모듈로 따로 분리했어~
클라이언트 코드가 구현체에 아예 접근하지 못하도록 interface(port)는 application 모듈에 두고, 여기는 구현체만 두는 식으로 계획했는데, infrastructure 모듈의 JpaConfig를 MainApplication에 config import 하려면 application -> infrastructure로 의존성이 생겨서 순환참조가 발생하더라고ㅠㅠ
- 현 상태 유지
- �application 모듈 하위에 infrastructure의 interface (port)를 두자!
- 이 경우, JpaConfig 설정 파일을 application 모듈에서 직접 관리
둘 중 어느쪽이 더 좋을지 여러분의 의견이 필요하다..! 👀 @depromeet/15th-6team-server
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.
난 두번째 방법이 좀 더 나은 것 같아.
application 모듈 하위에 infrastrueture의 interface(port)를 두는 방법
왜냐하면
- application 모듈이 interface에만 의존하게 되서 결합도가 낮아지고
- 누나말대로 jpa config 설정을 application 모듈에서 관리하면 순환 참조 문제를 해결할 수 있을 것 같아
그러면 application에 config/jpaconfig.java를 두고, port도 둬서 member repository interface를 만들어서 관리하면 되려나
그리고 infrastructure 모듈들이 이 인터페이스를 구현하도록 하고
그러면
- MemberRepository 인터페이스가 application 모듈의 port 패키지에 정의되고
- JpaMemberRepository는 infrastructure의 jpa모듈에 있고 application 모듈의 membeRepository 인터페이스를 implement하는거지
이러면 의존성 방향이 application<-infrastructure:jpa로 될 거 같애.
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.
수고 많으셨습니다~
tasks.bootJar { enabled = false } | ||
tasks.jar { enabled = true } |
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.
실행 안하는 파일은 bootJar 옵션 false줘서 jar 파일로 빌드 안되게 하고, jar은 true줘서 웹서버 없는 일반 라이브러리 jar파일 만드는 구나
@Override | ||
public Member save(Member member) { | ||
val memberEntity = memberJpaRepository.save(MemberEntity.from(member)); | ||
return memberEntity.toDomain(); | ||
} |
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.
여기서 from으로 도메인 객체를 엔티티 객체로 바꿔준다음에 jpa repository로 save하고 다시 반환할 때는 entity객체를 domain으로 바꿔주는 거지. 이렇게 domain객체와 entity객체를 따로 분리하는 이유는 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.
웅 jpaEntity는 DB와의 연결을 위해 사용하는 값이지, 실제 비즈니스 로직에 참여하는 값은 아니니까~
이렇게 하면 JPA 관련 변경 or 도메인 관련 변경이 필요할 때, 상대 계층에 영향을 미치지 않으니까 확장성이 좋겠다고 생각했어.
1차 MVP는 기간이 짧은만큼, 그냥 도메인이랑 영속성 entity를 동일하게 유지하는 방법도 고민했지만..!
개발 일정에 영향을 줄 정도의 크리티컬한 작업량은 아닌 것 같아서 일단 분리해봤다!
@@ -7,3 +7,6 @@ plugins { | |||
|
|||
include("domain") | |||
include("application") | |||
include("infrastructure") | |||
include("infrastructure:jpa") | |||
findProject(":infrastructure:jpa")?.name = "jpa" |
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.
jpa로 이름 재정의하는거구나. 간결하게 파악하려고 하는 거 외에 다른 이유가 있어?
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.
요거는 IntelliJ에서 모듈 생성하면 자동으로 작성되는 코드더라고!
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.
여기에서 논의 결과에 따라 usecase 모듈 추가
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.
유스케이스 모듈이 추가되었네요. 수고하셨습니다~
private final MemberUsecase memberUsecase; | ||
|
||
@PostMapping | ||
@ResponseStatus(HttpStatus.CREATED) | ||
@Operation(summary = "Member 생성 API") | ||
public MemberResponse create(@RequestBody MemberRequest request) { | ||
val member = memberService.create(request.name()); | ||
val member = memberUsecase.create(request.name()); | ||
return MemberResponse.from(member); | ||
} | ||
} |
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.
UseCase에서도 Dto를 만들어서 한 번 더 검증을 수행하지 않은 건 우리 코드 복잡성이 아직 크지 않아서인거 맞아?
컨트롤러에서 MemberRequest로 HTTP요청에 대한 유효성을 검사하잖아(필수 필드가 왔는지, 유효한 형식인지) 등등, 그러면 Service로 넘길 때 UseCase에 대한 MemberDto를 사용하면 비지니스 로직에 대한 유효성을 검사할 수 있을 것 같아.
분리했을 때의 장점은 만약 우리가 API request에 대한 형식을 바꾸거나 확장하면서 업데이트 할 일이 생긴다면 비지니스 로직에 대한 데이터 검증은 UseCase에 역할을 덜어놨으니 MemberRequest dto에서만 수정을 하면 되고!
그래서 만약 UseCase Dto를 만든다면 수정방향은
Application 내의 controller
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "Member 생성 API")
public MemberResponse create(@RequestBody MemberRequest request) {
MemberDto memberDto = new MemberDto(null, request.name()); // MemberDto는 usecase내에 있음!
MemberDto createdMember = memberUsecase.create(memberDto);
return MemberResponse.from(createdMember);
}
UseCase내의 MemberService.java
@Service
@RequiredArgsConstructor
public class MemberService implements MemberUsecase {
private final MemberRepository memberRepository;
@Override
public MemberDto create(final MemberDto memberDto) {
Member member = new Member(null, memberDto.getName());
Member savedMember = memberRepository.save(member);
return new MemberDto(savedMember.getId(), savedMember.getName());
}
}
그리고 UseCase내에 MemberDto 추가..
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.
궁금한 점이 있어! usecase로 넘어갈 때 Dto를 한 번 더 사용하는 것의 이점이 뭘까?
UseCase에 대한 MemberDto를 사용하면 비지니스 로직에 대한 유효성을 검사할 수 있을 것 같아.
라고 해줬는데, 어차피 로직에 참여하는 주체는 도메인이니까, 유효성 검사는 도메인에서 처리해야 하지 않을까?
- 외부 통신체 (request, response 등)의 유효성은 controller의 dto에서 처리
- ex. 길이 체크, not null 체크 등.. 클라이언트가 보낸 요청이 우리 api의 포맷에 부합하는가 확인
- 비즈니스 로직 관련 유효성 검사는 도메인에서 처리
- ex. 우리 서비스는 성인만 이용할 수 있다던지.. 도메인 로직 (요구사항)에 부합하는가 확인
DTO는 말 그대로 계층간 이동을 위해 존재하는 객체라서(data transfer), 여기에서 비즈니스 로직을 작성하는건 성격에 맞지 않는 것 같아.
우리가 API request에 대한 형식을 바꾸거나 확장하면서 업데이트 할 일이 생긴다면 비지니스 로직에 대한 데이터 검증은 UseCase에 역할을 덜어놨으니 MemberRequest dto에서만 수정을 하면 되고!
이건 requestDto를 바로 도메인으로 바꾸는 지금과 동일한 것 같고.
DTO는 계층 이동에 사용되는 객체이고, usecase와 domain은 같은 계층이라는 생각이 들었어.
infrastructure는 영속성, application은 http로, 이후 충분히 변동 가능성이 있는 계층이지만, usecase와 domain은 모두 외부에 의존하는 값이 아니라 순수 Java로 동작할 계층이니까!
(ex. rdb는 nosql로, http 어플리케이션은 grpc로 바뀔 수 있지만, usecase와 domain은 항상 자바 로직일 것)
결과적으로 usecase dto는 도메인과 request/response의 맵퍼 정도의 역할만 수행해주는 것 같아. 혹시 내가 의도를 잘못 이해했다면 알려주어~
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.
I agree!! LGTM~
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.
수고하셨습니다!
val member = new Member(null, name); | ||
return memberRepository.save(member); | ||
} | ||
} |
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.
LGTM~
📌 개요 (필수)
🔨 작업 사항 (필수)
⚡️ 관심 리뷰 (선택)
🌱 연관 내용 (선택)