Skip to content
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

Merged
merged 8 commits into from
Jul 3, 2024
Merged

Conversation

EunjiShin
Copy link
Collaborator

📌 개요 (필수)

  • 영속성 계층인 infrastructure:jpa 모듈을 세팅하고 JPA 예제를 추가했어요.

🔨 작업 사항 (필수)

  • infrastructure, infrastructure:jpa 모듈 생성
  • JPA 관련 설정 (의존성 추가, Spring configuration)
  • JPA 샘플 코드 추가
  • h2 설정 (도커 컨테이너 or DB 인스턴스 세팅되면 지울예정!)

⚡️ 관심 리뷰 (선택)

  • application-domain-infrastrucutre 모듈 사이의 맵핑 구조에 수정 or 보완할 점이 없는지 리뷰받고 싶어요~

🌱 연관 내용 (선택)

  • 잘 동작하는 것 확인 완료!
스크린샷 2024-07-01 오후 9 17 49 스크린샷 2024-07-01 오후 9 18 23

@github-actions github-actions bot added the size/M label Jul 1, 2024
@EunjiShin EunjiShin self-assigned this Jul 1, 2024
Copy link
Collaborator Author

@EunjiShin EunjiShin Jul 1, 2024

Choose a reason for hiding this comment

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

JPA 연결 확인용!!
실제 DB 인스턴스와 연결할 땐 환경변수를 이용하거나, gitignore 등을 이용하는 식으로 public 환경에 노출되지 않게 설정할 예정~!

Copy link
Collaborator Author

@EunjiShin EunjiShin Jul 1, 2024

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로 의존성이 생겨서 순환참조가 발생하더라고ㅠㅠ

  1. 현 상태 유지
  2. �application 모듈 하위에 infrastructure의 interface (port)를 두자!
  • 이 경우, JpaConfig 설정 파일을 application 모듈에서 직접 관리

둘 중 어느쪽이 더 좋을지 여러분의 의견이 필요하다..! 👀 @depromeet/15th-6team-server

Copy link
Collaborator

Choose a reason for hiding this comment

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

난 두번째 방법이 좀 더 나은 것 같아.

application 모듈 하위에 infrastrueture의 interface(port)를 두는 방법
왜냐하면

  1. application 모듈이 interface에만 의존하게 되서 결합도가 낮아지고
  2. 누나말대로 jpa config 설정을 application 모듈에서 관리하면 순환 참조 문제를 해결할 수 있을 것 같아

그러면 application에 config/jpaconfig.java를 두고, port도 둬서 member repository interface를 만들어서 관리하면 되려나
그리고 infrastructure 모듈들이 이 인터페이스를 구현하도록 하고
그러면

  1. MemberRepository 인터페이스가 application 모듈의 port 패키지에 정의되고
  2. JpaMemberRepository는 infrastructure의 jpa모듈에 있고 application 모듈의 membeRepository 인터페이스를 implement하는거지

이러면 의존성 방향이 application<-infrastructure:jpa로 될 거 같애.

pminsung12
pminsung12 previously approved these changes Jul 1, 2024
Copy link
Collaborator

@pminsung12 pminsung12 left a comment

Choose a reason for hiding this comment

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

수고 많으셨습니다~

Comment on lines +1 to +2
tasks.bootJar { enabled = false }
tasks.jar { enabled = true }
Copy link
Collaborator

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파일 만드는 구나

Comment on lines +17 to +21
@Override
public Member save(Member member) {
val memberEntity = memberJpaRepository.save(MemberEntity.from(member));
return memberEntity.toDomain();
}
Copy link
Collaborator

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 객체로 비지니스 로직을 담당해서 필드 값이 변할 수 있기 때문이야?

Copy link
Collaborator Author

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"
Copy link
Collaborator

Choose a reason for hiding this comment

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

jpa로 이름 재정의하는거구나. 간결하게 파악하려고 하는 거 외에 다른 이유가 있어?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

요거는 IntelliJ에서 모듈 생성하면 자동으로 작성되는 코드더라고!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@EunjiShin EunjiShin requested a review from pminsung12 July 2, 2024 15:40
@EunjiShin EunjiShin dismissed pminsung12’s stale review July 2, 2024 16:10

새로 커밋 찍음!

Copy link
Collaborator

@pminsung12 pminsung12 left a comment

Choose a reason for hiding this comment

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

유스케이스 모듈이 추가되었네요. 수고하셨습니다~

Comment on lines 25 to 34
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);
}
}
Copy link
Collaborator

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 추가..

Copy link
Collaborator Author

@EunjiShin EunjiShin Jul 3, 2024

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의 맵퍼 정도의 역할만 수행해주는 것 같아. 혹시 내가 의도를 잘못 이해했다면 알려주어~

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree!! LGTM~

Copy link
Collaborator

@pminsung12 pminsung12 left a 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);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

LGTM~

@pminsung12 pminsung12 merged commit 6fcad26 into main Jul 3, 2024
1 check passed
@pminsung12 pminsung12 deleted the feat/BSVR-44 branch July 3, 2024 11:21
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.

2 participants