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
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions application/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
dependencies {
implementation(project(":domain"))
implementation(project(":infrastructure:jpa"))

// spring
implementation("org.springframework.boot:spring-boot-starter")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package org.depromeet.spot.application.config;

import org.depromeet.spot.jpa.config.JpaConfig;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@ComponentScan(basePackages = {"org.depromeet.spot.application"})
@Configuration
@Import({JpaConfig.class})
public class SpotApplicationConfig {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.depromeet.spot.application.member.controller;

import org.depromeet.spot.application.member.controller.request.MemberRequest;
import org.depromeet.spot.application.member.controller.response.MemberResponse;
import org.depromeet.spot.application.member.service.port.MemberService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.val;

// FIXME: JPA 확인용 샘플 컨트롤러 입니다. 이후 실제 작업 시작할 때 삭제 예정이에요!
@RestController
@RequiredArgsConstructor
@Tag(name = "멤버")
@RequestMapping("/api/members")
public class MemberController {

private final MemberService memberService;

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "Member 생성 API")
public MemberResponse create(@RequestBody MemberRequest request) {
val member = memberService.create(request.name());
return MemberResponse.from(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.depromeet.spot.application.member.controller.request;

public record MemberRequest(String name) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.depromeet.spot.application.member.controller.response;

import org.depromeet.spot.domain.member.Member;

public record MemberResponse(Long id, String name) {

public static MemberResponse from(Member member) {
return new MemberResponse(member.getId(), member.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.depromeet.spot.application.member.service;

import org.depromeet.spot.application.member.service.port.MemberService;
import org.depromeet.spot.domain.member.Member;
import org.depromeet.spot.jpa.member.repository.port.MemberRepository;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import lombok.val;

@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {

private final MemberRepository memberRepository;

@Override
public Member create(final String name) {
val member = new Member(null, name);
return memberRepository.save(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.depromeet.spot.application.member.service.port;

import org.depromeet.spot.domain.member.Member;

public interface MemberService {

Member create(String name);
}
3 changes: 3 additions & 0 deletions application/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ server:
shutdown: graceful

spring:
# 서브모듈 profile
profiles:
include: jpa
# swagger를 이용해 API 명세서 생성
doc:
swagger-ui:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.depromeet.spot.domain.member;

import lombok.Getter;

@Getter
public class Member {

private final Long id;
private final String name;

public Member(Long id, String name) {
this.id = id;
this.name = name;
}
}
2 changes: 2 additions & 0 deletions infrastructure/build.gradle.kts
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로 될 거 같애.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
tasks.bootJar { enabled = false }
tasks.jar { enabled = true }
Comment on lines +1 to +2
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파일 만드는 구나

13 changes: 13 additions & 0 deletions infrastructure/jpa/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
dependencies {
implementation(project(":domain"))

// spring
implementation("org.springframework.boot:spring-boot-starter-data-jpa:_")

// h2 - DB (또는 도커) 세팅 후 사라질 예정,,
runtimeOnly("com.h2database:h2")

}

tasks.bootJar { enabled = false }
tasks.jar { enabled = true }
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.depromeet.spot.jpa.config;

import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@ComponentScan(basePackages = {"org.depromeet.spot.jpa"})
@EnableJpaRepositories(basePackages = {"org.depromeet.spot.jpa"})
@EntityScan(basePackages = {"org.depromeet.spot.jpa"})
public class JpaConfig {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.depromeet.spot.jpa.member.entity;

/* JPA 설정 확인용 샘플 엔티티. 실제 피처 개발 시작할 때 삭제 예정! */

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import org.depromeet.spot.domain.member.Member;

import lombok.NoArgsConstructor;

@Entity
@Table(name = "member")
@NoArgsConstructor
public class MemberEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;

@Column(name = "name", nullable = false)
private String name;

public MemberEntity(Long id, String name) {
this.id = id;
this.name = name;
}

public static MemberEntity from(Member member) {
return new MemberEntity(member.getId(), member.getName());
}

public Member toDomain() {
return new Member(id, name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.depromeet.spot.jpa.member.repository;

import org.depromeet.spot.jpa.member.entity.MemberEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberJpaRepository extends JpaRepository<MemberEntity, Long> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.depromeet.spot.jpa.member.repository;

import org.depromeet.spot.domain.member.Member;
import org.depromeet.spot.jpa.member.entity.MemberEntity;
import org.depromeet.spot.jpa.member.repository.port.MemberRepository;
import org.springframework.stereotype.Repository;

import lombok.RequiredArgsConstructor;
import lombok.val;

@Repository
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepository {

private final MemberJpaRepository memberJpaRepository;

@Override
public Member save(Member member) {
val memberEntity = memberJpaRepository.save(MemberEntity.from(member));
return memberEntity.toDomain();
}
Comment on lines +17 to +21
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를 동일하게 유지하는 방법도 고민했지만..!
개발 일정에 영향을 줄 정도의 크리티컬한 작업량은 아닌 것 같아서 일단 분리해봤다!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.depromeet.spot.jpa.member.repository.port;

import org.depromeet.spot.domain.member.Member;

public interface MemberRepository {

Member save(Member member);
}
19 changes: 19 additions & 0 deletions infrastructure/jpa/src/main/resources/application-jpa.yaml
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 환경에 노출되지 않게 설정할 예정~!

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:spot
username: sa
password:

jpa:
database: h2
hibernate:
ddl-auto: create
database-platform: org.hibernate.dialect.H2Dialect
open-in-view: false
defer-datasource-initialization: true

h2:
console:
enabled: true
path: /h2-console
3 changes: 3 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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에서 모듈 생성하면 자동으로 작성되는 코드더라고!

3 changes: 3 additions & 0 deletions versions.properties
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ version.org.projectlombok..lombok=1.18.30

version.org.springframework.boot..spring-boot-starter-test=3.0.1

version.org.springframework.boot..spring-boot-starter-data-jpa=3.0.1

version.org.springdoc..springdoc-openapi-starter-webmvc-ui=2.5.0