diff --git a/application/build.gradle.kts b/application/build.gradle.kts index dcf46841..b48dbec5 100644 --- a/application/build.gradle.kts +++ b/application/build.gradle.kts @@ -1,5 +1,7 @@ dependencies { implementation(project(":domain")) + implementation(project(":usecase")) + implementation(project(":infrastructure:jpa")) // spring implementation("org.springframework.boot:spring-boot-starter") diff --git a/application/src/main/java/org/depromeet/spot/application/config/SpotApplicationConfig.java b/application/src/main/java/org/depromeet/spot/application/config/SpotApplicationConfig.java index 0806b2f2..e99fe8c0 100644 --- a/application/src/main/java/org/depromeet/spot/application/config/SpotApplicationConfig.java +++ b/application/src/main/java/org/depromeet/spot/application/config/SpotApplicationConfig.java @@ -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 {} diff --git a/application/src/main/java/org/depromeet/spot/application/member/controller/MemberController.java b/application/src/main/java/org/depromeet/spot/application/member/controller/MemberController.java new file mode 100644 index 00000000..f7b9c633 --- /dev/null +++ b/application/src/main/java/org/depromeet/spot/application/member/controller/MemberController.java @@ -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); + } +} diff --git a/application/src/main/java/org/depromeet/spot/application/member/dto/request/MemberRequest.java b/application/src/main/java/org/depromeet/spot/application/member/dto/request/MemberRequest.java new file mode 100644 index 00000000..59ef51cd --- /dev/null +++ b/application/src/main/java/org/depromeet/spot/application/member/dto/request/MemberRequest.java @@ -0,0 +1,3 @@ +package org.depromeet.spot.application.member.dto.request; + +public record MemberRequest(String name) {} diff --git a/application/src/main/java/org/depromeet/spot/application/member/dto/response/MemberResponse.java b/application/src/main/java/org/depromeet/spot/application/member/dto/response/MemberResponse.java new file mode 100644 index 00000000..06ab8203 --- /dev/null +++ b/application/src/main/java/org/depromeet/spot/application/member/dto/response/MemberResponse.java @@ -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()); + } +} diff --git a/application/src/main/resources/application.yaml b/application/src/main/resources/application.yaml index ae781783..1077c07f 100644 --- a/application/src/main/resources/application.yaml +++ b/application/src/main/resources/application.yaml @@ -3,6 +3,9 @@ server: shutdown: graceful spring: + # 서브모듈 profile + profiles: + include: jpa # swagger를 이용해 API 명세서 생성 doc: swagger-ui: diff --git a/domain/src/main/java/org/depromeet/spot/domain/member/Member.java b/domain/src/main/java/org/depromeet/spot/domain/member/Member.java new file mode 100644 index 00000000..7c141f54 --- /dev/null +++ b/domain/src/main/java/org/depromeet/spot/domain/member/Member.java @@ -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; + } +} diff --git a/infrastructure/build.gradle.kts b/infrastructure/build.gradle.kts new file mode 100644 index 00000000..86f2104e --- /dev/null +++ b/infrastructure/build.gradle.kts @@ -0,0 +1,2 @@ +tasks.bootJar { enabled = false } +tasks.jar { enabled = true } \ No newline at end of file diff --git a/infrastructure/jpa/build.gradle.kts b/infrastructure/jpa/build.gradle.kts new file mode 100644 index 00000000..06299a39 --- /dev/null +++ b/infrastructure/jpa/build.gradle.kts @@ -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 } \ No newline at end of file diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/common/.gitkeep b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/common/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/config/JpaConfig.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/config/JpaConfig.java new file mode 100644 index 00000000..4a0b3267 --- /dev/null +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/config/JpaConfig.java @@ -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 {} diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/entity/MemberEntity.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/entity/MemberEntity.java new file mode 100644 index 00000000..f8d00adf --- /dev/null +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/entity/MemberEntity.java @@ -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); + } +} diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberJpaRepository.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberJpaRepository.java new file mode 100644 index 00000000..502030df --- /dev/null +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberJpaRepository.java @@ -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 {} diff --git a/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberRepositoryImpl.java b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberRepositoryImpl.java new file mode 100644 index 00000000..42507851 --- /dev/null +++ b/infrastructure/jpa/src/main/java/org/depromeet/spot/jpa/member/repository/MemberRepositoryImpl.java @@ -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(); + } +} diff --git a/infrastructure/jpa/src/main/resources/application-jpa.yaml b/infrastructure/jpa/src/main/resources/application-jpa.yaml new file mode 100644 index 00000000..8f7afdca --- /dev/null +++ b/infrastructure/jpa/src/main/resources/application-jpa.yaml @@ -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 diff --git a/settings.gradle.kts b/settings.gradle.kts index 276501f9..9fc46dae 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,3 +7,7 @@ plugins { include("domain") include("application") +include("infrastructure") +include("infrastructure:jpa") +findProject(":infrastructure:jpa")?.name = "jpa" +include("usecase") diff --git a/usecase/.gitignore b/usecase/.gitignore new file mode 100644 index 00000000..b63da455 --- /dev/null +++ b/usecase/.gitignore @@ -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 \ No newline at end of file diff --git a/usecase/build.gradle.kts b/usecase/build.gradle.kts new file mode 100644 index 00000000..506b4ad6 --- /dev/null +++ b/usecase/build.gradle.kts @@ -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 } \ No newline at end of file diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/config/UsecaseConfig.java b/usecase/src/main/java/org/depromeet/spot/usecase/config/UsecaseConfig.java new file mode 100644 index 00000000..97cf008e --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/config/UsecaseConfig.java @@ -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 {} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/in/MemberUsecase.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/MemberUsecase.java new file mode 100644 index 00000000..79af200b --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/in/MemberUsecase.java @@ -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); +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/MemberRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/MemberRepository.java new file mode 100644 index 00000000..b7b5fe40 --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/MemberRepository.java @@ -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); +} diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/MemberService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/MemberService.java new file mode 100644 index 00000000..061ec57c --- /dev/null +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/MemberService.java @@ -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); + } +} diff --git a/versions.properties b/versions.properties index 85ea6494..c0dbde06 100644 --- a/versions.properties +++ b/versions.properties @@ -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 +