-
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
Changes from all commits
41a10d4
e3b288e
85dce71
82f8d7b
481905f
2bf0af9
62d7982
ca69e78
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 |
---|---|---|
@@ -1,8 +1,12 @@ | ||
package org.depromeet.spot.application.config; | ||
|
||
import org.depromeet.spot.jpa.config.JpaConfig; | ||
import org.depromeet.spot.usecase.config.UsecaseConfig; | ||
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(value = {UsecaseConfig.class, 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.dto.request.MemberRequest; | ||
import org.depromeet.spot.application.member.dto.response.MemberResponse; | ||
import org.depromeet.spot.usecase.port.in.MemberUsecase; | ||
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 MemberUsecase memberUsecase; | ||
|
||
@PostMapping | ||
@ResponseStatus(HttpStatus.CREATED) | ||
@Operation(summary = "Member 생성 API") | ||
public MemberResponse create(@RequestBody MemberRequest request) { | ||
val member = memberUsecase.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.dto.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.dto.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,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; | ||
} | ||
} |
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
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. 실행 안하는 파일은 bootJar 옵션 false줘서 jar 파일로 빌드 안되게 하고, jar은 true줘서 웹서버 없는 일반 라이브러리 jar파일 만드는 구나 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
dependencies { | ||
implementation(project(":domain")) | ||
implementation(project(":usecase")) | ||
|
||
// 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.usecase.port.out.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
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. 여기서 from으로 도메인 객체를 엔티티 객체로 바꿔준다음에 jpa repository로 save하고 다시 반환할 때는 entity객체를 domain으로 바꿔주는 거지. 이렇게 domain객체와 entity객체를 따로 분리하는 이유는 domain 객체로 비지니스 로직을 담당해서 필드 값이 변할 수 있기 때문이야? 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. 웅 jpaEntity는 DB와의 연결을 위해 사용하는 값이지, 실제 비즈니스 로직에 참여하는 값은 아니니까~ 1차 MVP는 기간이 짧은만큼, 그냥 도메인이랑 영속성 entity를 동일하게 유지하는 방법도 고민했지만..! |
||
} |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,7 @@ 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. 요거는 IntelliJ에서 모듈 생성하면 자동으로 작성되는 코드더라고! |
||
include("usecase") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
.gradle | ||
build/ | ||
!gradle/wrapper/gradle-wrapper.jar | ||
!**/src/main/**/build/ | ||
!**/src/test/**/build/ | ||
|
||
### IntelliJ IDEA ### | ||
.idea/modules.xml | ||
.idea/jarRepositories.xml | ||
.idea/compiler.xml | ||
.idea/libraries/ | ||
*.iws | ||
*.iml | ||
*.ipr | ||
out/ | ||
!**/src/main/**/out/ | ||
!**/src/test/**/out/ | ||
|
||
### Eclipse ### | ||
.apt_generated | ||
.classpath | ||
.factorypath | ||
.project | ||
.settings | ||
.springBeans | ||
.sts4-cache | ||
bin/ | ||
!**/src/main/**/bin/ | ||
!**/src/test/**/bin/ | ||
|
||
### NetBeans ### | ||
/nbproject/private/ | ||
/nbbuild/ | ||
/dist/ | ||
/nbdist/ | ||
/.nb-gradle/ | ||
|
||
### VS Code ### | ||
.vscode/ | ||
|
||
### Mac OS ### | ||
.DS_Store |
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. 여기에서 논의 결과에 따라 usecase 모듈 추가 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
dependencies { | ||
implementation(project(":domain")) | ||
|
||
// spring | ||
implementation("org.springframework.boot:spring-boot-starter") | ||
implementation("org.springframework.boot:spring-boot-starter-web") | ||
} | ||
|
||
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.usecase.config; | ||
|
||
import org.springframework.context.annotation.ComponentScan; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
@Configuration | ||
@ComponentScan( | ||
basePackages = { | ||
"org.depromeet.spot.usecase.port", | ||
"org.depromeet.spot.usecase.service", | ||
}) | ||
public class UsecaseConfig {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.depromeet.spot.usecase.port.in; | ||
|
||
import org.depromeet.spot.domain.member.Member; | ||
|
||
public interface MemberUsecase { | ||
|
||
Member create(String name); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.depromeet.spot.usecase.port.out; | ||
|
||
import org.depromeet.spot.domain.member.Member; | ||
|
||
public interface MemberRepository { | ||
|
||
Member save(Member member); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package org.depromeet.spot.usecase.service; | ||
|
||
import org.depromeet.spot.domain.member.Member; | ||
import org.depromeet.spot.usecase.port.in.MemberUsecase; | ||
import org.depromeet.spot.usecase.port.out.MemberRepository; | ||
import org.springframework.stereotype.Service; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import lombok.val; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class MemberService implements MemberUsecase { | ||
|
||
private final MemberRepository memberRepository; | ||
|
||
@Override | ||
public Member create(final String name) { | ||
val member = new Member(null, name); | ||
return memberRepository.save(member); | ||
} | ||
} | ||
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. 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.
외부 의존성을 관리하는 infrastructure 모듈!
여기 하위에 JPA, Redis 등등을 모듈로 관리할 예정이야
e.g.,) redis 사용이 필요하다면? -> infrastructure:redis 모듈을 새로 추가해서 사용
구현체를 갈아끼우기 쉽게 하기 위해서 모듈로 따로 분리했어~
클라이언트 코드가 구현체에 아예 접근하지 못하도록 interface(port)는 application 모듈에 두고, 여기는 구현체만 두는 식으로 계획했는데, infrastructure 모듈의 JpaConfig를 MainApplication에 config import 하려면 application -> infrastructure로 의존성이 생겨서 순환참조가 발생하더라고ㅠㅠ
둘 중 어느쪽이 더 좋을지 여러분의 의견이 필요하다..! 👀 @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에 config/jpaconfig.java를 두고, port도 둬서 member repository interface를 만들어서 관리하면 되려나
그리고 infrastructure 모듈들이 이 인터페이스를 구현하도록 하고
그러면
이러면 의존성 방향이 application<-infrastructure:jpa로 될 거 같애.