kafka μ€ν ν ν΄λΉ νλ‘μ νΈ κΈ°λ κ°λ₯
docker run --name kafka -p 9092:9092 apache/kafka:3.7.1
ν₯μ¬κ³ λ μν€ν μ²(Hexagonal Architecture)λ ν¬νΈμ μ΄λν° μν€ν μ²(Ports and Adapters Architecture)λΌκ³ λ λΆλ¦¬λ μννΈμ¨μ΄ μν€ν μ² μ€ νλλ‘ μ£Όμ λͺ©νλ μμ© νλ‘κ·Έλ¨μ λΉμ¦λμ€ λ‘μ§μ μΈλΆ μΈκ³λ‘λΆν° 격리μμΌ μ μ°νκ³ ν μ€νΈνκΈ° μ¬μ΄ ꡬ쑰λ₯Ό λ§λλ κ²μ λλ€. μ΄λ₯Ό μν΄ ν΅μ¬ λΉμ¦λμ€ λ‘μ§μ μ€μμ λλ©μΈ μμμ μμΉνλ©°, μ λ ₯κ³Ό μΆλ ₯μ μ²λ¦¬νλ ν¬νΈμ μ΄λν°λ₯Ό ν΅ν΄ μΈλΆμ μν΅ν©λλ€.
- Adapter : λͺ¨λ μΈλΆ μμ€ν
κ³Όμ μ§μ μ μΈ μνΈμμ©μ λ΄λΉ
- ex) Controller, Kafka Listener, DB DAO
- Inbound & Outbound port : κ° μλΉμ€ λΉμ¦λμ€ λ‘μ§μ λ§κ² μ μλ μΈν°νμ΄μ€
μΈλ°μ΄λ μ΄λμ² -> μΈλ°μ΄λ ν¬νΈ -> λΉμ¦λμ€ λ‘μ§ -> μμλ°μ΄λ ν¬νΈ -> μμλ°μ΄λ μ΄λν°
λ΄λΆμ λ‘μ§μ μ€μ§ μΈλΆλ₯Ό ν΅ν΄μλ§ μ κ·Όμ΄ κ°λ₯ν 컨μ μΌλ‘ μΈλΆ μλΉμ€μμ μνΈ μμ©μ λ΄λΉνλ Adapterλ λΉμ¦λμ€ λ‘μ§κ³Όμ μμ μ μ μν ν¬νΈ(μΈν°νμ΄μ€)λλ§ μλ‘ ν΅μ ν©λλ€.
λ μ΄μ΄λ μν€ν μ²λ λΉμ¦λμ€ λ μ΄μ΄κ° μΈνλΌ λ μ΄μ΄μ μμ‘΄νμ¬ κ°κ²°ν©λλ ꡬ쑰λ₯Ό κ°μ§κ³ μμ΅λλ€. JPAλ‘ μλ₯Ό λ€λ©΄, PersistenceInterfaceλ JPA interfaceκ° λκ³ , μ΄μ λν Adapterλ‘λ SimpleJpaRepositoryκ° λ μλ μκ³ QueryDslμ μ¬μ©νλ€λ©΄ μΆκ°μ μΈ customImplμ΄ Adapterκ° λ μ μμ΅λλ€.
μμ‘΄μ± μμ λΉμ¦λμ€ λ μ΄μ΄κ° μΈνλΌ λ μ΄μ΄μ μμ‘΄νκ² λλ©΄μ μΈνλΌ λ μ΄μ΄μ μΈν°νμ΄μ€κ° λ³κ²½λλ©΄ λΉμ¦λμ€ λ μ΄μ΄λ ν¨κ» λ³κ²½λμ΄μΌ ν©λλ€. μ¦, μΈνλΌ λ μ΄μ΄ λ΄μ μ½λ λ³κ²½μ μν΄ λΉμ¦λμ€ λ μ΄μ΄λ ν¨κ» μ€μΌλ©λλ€. ν₯μ¬κ³ λ μν€ν μ²μμλ λ€μκ³Ό κ°μ΄ μμ‘΄μ±μ μμ ν©λλ€.
μμ‘΄μ± μμ μμΉμ μμκ³ μ μ΄λν°μ μ μ©νμ¬ ν΅μ¬ λλ©μΈ λΆλΆμ λ€λ₯Έ λ μ΄μ΄μ μμ‘΄νμ§ μκ² λμ΄ λ μμ μΌλ‘ κ°λ° λ° λ°°ν¬κ° κ°λ₯ν΄μ‘μ΅λλ€. λλΆμ ν μ€νΈ μ©μ΄μ± λν ν보λμμ΅λλ€.
μΈν°νμ΄μ€ λΆλ¦¬ λ³΄ν΅ λΉμ¦λμ€ λ μ΄μ΄μ μλΉμ€λ νλ μ΄μμ μ μ¦μΌμ΄μ€λ₯Ό ꡬννκ³ μμ΅λλ€. μ¦, UI λ μ΄μ΄μμ λΉμ¦λμ€ λ μ΄μ΄μ μλΉμ€λ₯Ό νΈμΆν λ, μ΄λ€ μ μ¦μΌμ΄μ€λ₯Ό μ¬μ©ν΄μΌν μ§ λͺ ννμ§ μμ κ²½μ°κ° λ°μν μ μμ΅λλ€. λν, μ κ·Έλ¦Όμ appServiceλ₯Ό κ·Έλλ‘ μ¬μ©νκ² λλ©΄ μ¬λ¬ μ μ¦μΌμ΄μ€λ€μ λΆνμν λΆλΆλ€μ λͺ¨λ μμ‘΄νλ μν©μ΄ λ°μν©λλ€. ν₯μ¬κ³ λ μν€ν μ²μμλ λ€μκ³Ό κ°μ΄ μΈν°νμ΄μ€λ₯Ό λΆλ¦¬ν©λλ€.
μΈν°νμ΄μ€λ₯Ό λΆλ¦¬νκ³ μ΄λ₯Ό appServiceκ° κ΅¬ννκ² νκ³ UI λ μ΄μ΄μμλ μ μ ν μΈμ»€λ° ν¬νΈλ₯Ό μ¬μ©νκ² λ©λλ€. μ΄λ₯Ό ν΅ν΄ ν΄λΉ κΈ°λ₯κ³Ό κ΄λ ¨ μλ λ€λ₯Έ λΆλΆμ μμ‘΄νμ§ μκ² λ©λλ€.
λ μ΄μ΄λ μν€ν μ²λ₯Ό ν₯μ¬κ³ λ μν€ν μ²λ‘ λ³ννλ κ³Όμ μ 보면 λ€μκ³Ό κ°μ μ₯μ μ λ³Ό μ μμ΅λλ€.
- μ μ§λ³΄μμ± : μ± μμ΄ λΆλ¦¬λμ΄ μμ΄ μ½λ μ΄ν΄μ μμ μ΄ μ©μ΄ν©λλ€.
- μ μ°μ± : ν¬νΈμ μ΄λν°λ₯Ό μ¬μ©ν¨μΌλ‘μ¨, λ€μν λ³νμ λν΄ μ μ°νκ² λμ²ν μ μμ΅λλ€.
- ν μ€νΈ μ©μ΄μ± : κ° μ»΄ν¬λνΈλ₯Ό λ 립μ μΌλ‘ μΈλΆ μμ‘΄μ± μμ΄ ν μ€νΈν μ μμ΅λλ€.
- ꡬ쑰μ μΌλ‘ SOLID μμΉμ λμ± μ½κ² μ μ© κ°λ₯ν©λλ€.
ν₯μ¬κ³ λ μν€ν μ²κ° λͺ¨λ μν©μ μ ν©ν κ²μ μλλλ€. ν₯μ¬κ³ λ μν€ν μ²μ κ²½μ° κΈ°μ‘΄ λ μ΄μ΄λ μν€ν μ²μ λΉν΄ μ½λλμ΄ μλΉν μ¦κ°νλ©° μ²μ κ°λ° μ΄ν ν° λΉμ¦λμ€ λ‘μ§μ λ³νκ° μ‘΄μ¬νμ§ μλ νλ‘μ νΈμ κ²½μ° μ€νλ € λ μ΄μ΄λ μν€ν μ²κ° λμ± μμ μ μΌ μ μμ΅λλ€. ν₯μ¬κ³ λ μν€ν μ²λ λ³΄ν΅ λΉ λ₯Έ νμ₯μ±κ³Ό μ μ°μ±μ΄ νμ°μ μΌλ‘ νμν MSA νκ²½μμ μ μ ν μν€ν μ²λΌκ³ νννλ κ²½μ°λ μμΌλ μ μ ν μν©μ λ§κ² μ¬μ©ν΄μΌ ν©λλ€.
νλμ λ ν¬μ§ν λ¦¬λ‘ μμ μ¬λ¬ κ°μ νλ‘μ νΈλ₯Ό λ§λ€κ³ κ°κ°μ νλ‘μ νΈλ λ©ν° λͺ¨λλ‘ κ΅¬μ±νλ λ°λͺ¨λ₯Ό λ§λ€μ΄λ³΄κ² μ΅λλ€.
λ©ν° νλ‘μ νΈ λ©ν° λͺ¨λμ ꡬμΆνκΈ° μν΄μλ μ¬μ μ§μμ΄ νμν©λλ€.
- implementation : implementationλ₯Ό μ¬μ©νλ©΄ ν΄λΉ μ’ μμ±μ νμ¬ λͺ¨λ λ΄λΆμμλ§ μ κ·Όν μ μμ΅λλ€. μ¦, μ΄ λͺ¨λμ μμ‘΄νλ λ€λ₯Έ λͺ¨λμμλ implementationμΌλ‘ μΆκ°λ μ’ μμ±μ μ§μ μ κ·Όν μ μμ΅λλ€.
- api : νμ¬ λͺ¨λκ³Ό μ΄ λͺ¨λμ μμ‘΄νλ λ€λ₯Έ λͺ¨λμμλ μ κ·Όν μ μλλ‘ ν©λλ€. μ¦, νμ¬ λͺ¨λμ΄ apiλ‘ μ’ μμ±μ μΆκ°νλ©΄, μ΄ λͺ¨λμ μ¬μ©νλ λͺ¨λ λ€λ₯Έ λͺ¨λλ ν΄λΉ μ’ μμ±μ μ§μ μ¬μ©ν μ μμ΅λλ€.
// A Module
public class A
// B Module
implementation project(':A')
// C Module
implementation project(':B')
public class C {
public void act() {
new A() // compile error
}
}
A λͺ¨λμμ AλΌλ ν΄λμ€λ₯Ό μ 곡νλ€κ³ νμλ, B λͺ¨λμμ Aλͺ¨λμ implementationμΌλ‘ μμ‘΄μ±μ κ°μ Έμ¨λ€λ©΄ Bλͺ¨λμμλ A ν΄λμ€λ₯Ό μ¬μ©ν μ μμ΅λλ€. μ΄ μνμμ C λͺ¨λμμ B λͺ¨λμ implementationμΌλ‘ μμ‘΄μ±μΌλ‘ κ°μ Έμ¨λ€λ©΄ Cλͺ¨λμμλ Aν΄λμ€λ₯Ό μ¬μ©ν μ μμ΅λλ€. νμ§λ§ Bλͺ¨λμμ implementationμ΄ μλ apiλ₯Ό μ¬μ©ν΄μ Aλͺ¨λμ μμ‘΄μ±μ κ°μ Έμλ€λ©΄ Cλͺ¨λμμλ Aλͺ¨λμμ μ 곡νλ κΈ°λ₯μ μ¬μ©ν μ μμΌλ―λ‘ A ν΄λμ€λ₯Ό μ¬μ©ν μ μκ² λ©λλ€.
https://docs.gradle.org/current/userguide/composite_builds.html
- include : μΌλ°μ μΌλ‘ νλμ λ£¨νΈ νλ‘μ νΈμ μ¬λ¬ μλΈ νλ‘μ νΈλ‘ ꡬμ±λ ꡬ쑰μμ μ¬μ©λ©λλ€. includeλ₯Ό μ¬μ©νλ©΄ μλΈ νλ‘μ νΈλ€μ νλμ μ€μ μΌλ‘ κ²°ν©νκ³ , μ΄λ€ μ¬μ΄μ μμ‘΄μ±μ κ΄λ¦¬ν μ μμ΅λλ€.
- includeBuild : λ€λ₯Έλ₯Έ λ 립μ μΈ λΉλλ₯Ό ν¬ν¨νλ λ° μ¬μ©λ©λλ€. λ³΄ν΅ μ¬λ¬ κ°μ λ 립μ μΌλ‘ λΉλ κ°λ₯ν νλ‘μ νΈλ₯Ό νλμ λΉλμ ν¬ν¨νκ³ μ ν λ μ¬μ©ν©λλ€.
λ©ν° λͺ¨λμ κ²½μ° λ³΄ν΅ includeλ§ μ¬μ©νμ§λ§ λ©ν° νλ‘μ νΈλ₯Ό λ§λλ κ²½μ°, ν νλ‘μ νΈμ λͺ¨λμ κ°μ Έμ€κΈ° μν΄ includeBuildλ₯Ό μ¬μ©ν©λλ€.
βββ common-library
β βββ json
β βββ settings.gradle.kts
β βββ build.gradle.kts
βββ sample-service
β βββ application
β βββ build.gradle.kts
β βββ settings.gradle.kts
βββ build.gradle.kts
βββ settings.gradle.kts
μμ κ°μ ꡬ쑰μ sample-service, common-library νλ‘μ νΈκ° μμ λ, common-library νλ‘μ νΈμ json λͺ¨λμ sample-service νλ‘μ νΈμ application λͺ¨λμμ μ¬μ©νκΈ° μν΄μ includeBuildλ₯Ό μ¬μ©ν©λλ€.
// root νλ‘μ νΈμ settings.gradle.kts
includeBuild("common-library")
includeBuild("sample-service")
// sample-serviceμ settings.gradle.kts
rootProject.name = "sample-service"
includeBuild("../common-library")
// sample-serviceμ application λͺ¨λμ build.gradle.kts
dependencies {
// ν¨ν€μ§λͺ
:λͺ¨λλͺ
implementation("com.sample.hexagonal.common:json")
}
μμ κ°μ κ΅¬μ‘°λ‘ μμ‘΄μ±μ λ°μμ¬ μ μμ΅λλ€.
βββ build-plugin
βββ common-library
β βββ exception
β βββ json
β βββ utils
βββ sample-service
β βββ adapter
β β βββ inbound
β β β βββ controller
β β β βββ listener
β β βββ outbound
β β βββ producer
β β βββ repository
β βββ application
β βββ domain
β βββ infrastructure
β β βββ h2
β β βββ mongo
β βββ server
β β βββ api
β β βββ consumer
- build-plugin : μ¬λ¬ λͺ¨λμμ μ¬μ©ν build-pluginμ κ΄λ¦¬νλ νλ‘μ νΈ
- common-library : μ¬λ¬ λͺ¨λμμ 곡μ©μΌλ‘ μ¬μ©ν libraryλ₯Ό κ΄λ¦¬νλ νλ‘μ νΈ
- sample-service : μλΉμ€ νλ‘μ νΈ
- adapter.inbound : μΈλΆ μμ€ν κ³Όμ μνΈμμ©μ λ΄λΉνλ Adapter λͺ¨λλ‘ μΈλΆμμ λ΄λΆλ₯Ό νΈμΆνλ μν (controller, kafka-listener λͺ¨λ)
- adapter.outbound : μΈλΆ μμ€ν κ³Όμ μνΈμμ©μ λ΄λΉνλ Adapter λͺ¨λλ‘ λ΄λΆμμ μΈλΆλ₯Ό νΈμΆνλ μν (repository, kafka-producer λͺ¨λ)
- application : λΉμ¦λμ€ λͺ¨λ
- domain : λλ©μΈ λͺ¨λ
- infrastructure : outbound λͺ¨λμμ μ¬μ©νλ μΈλΆ μΈνλΌ (h2, mongo λͺ¨λ)
- server : μλ²λ₯Ό λμ°κΈ° μν (api, consumer λͺ¨λ)
μ΅μμ settings.gradle.kts
includeBuild("build-plugin")
includeBuild("common-library")
includeBuild("sample-service")
μ΅μμ settings.gradle.ktsμλ composite buildλ₯Ό μν΄ includeBuildλ₯Ό μ¬μ©νμ¬ κ°κ°μ νλ‘μ νΈλ₯Ό λ±λ‘ν΄μ€λλ€.
build-plugin νλ‘μ νΈλ ν νλ‘μ νΈμ λͺ¨λμμ μ¬μ©ν 곡ν΅μ μΈ μμ‘΄μ±μ κ΄λ¦¬νκΈ° μν λͺ©μ μ νλ‘μ νΈ μ λλ€. 곡μλ¬Έμμμ κ°μ΄λνλ Precompiled script plugin λ°©μμ μ¬μ©νμ¬ μ¬λ¬ νλ‘μ νΈμμ 곡μ©μΌλ‘ μ¬μ©ν build-pluginμ λ§λ€κ² μ΅λλ€.
βββ build.gradle.kts
βββ settings.gradle.kts
βββ src
βββ main
βββ kotlin
βββ sample-kotlin-jvm.gradle.kts
βββ sample-springboot.gradle.kts
build-plugin νλ‘μ νΈμ treeꡬ쑰λ μμ κ°μ΅λλ€.
sample-kotlin-jvm.gradle.kts ν΄λΉ νμΌμ springμ΄ μλ λ¨μ kotlinλ§μ μ¬μ©νλ λͺ¨λμ μν νλ¬κ·ΈμΈ νμΌμ λλ€.
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
plugins {
id("org.jlleitschuh.gradle.ktlint")
id("org.jetbrains.kotlinx.kover")
id("java-library")
kotlin("jvm")
kotlin("kapt")
}
repositories {
mavenCentral()
}
java {
sourceCompatibility = JavaVersion.VERSION_17
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("io.github.microutils:kotlin-logging-jvm:3.0.5")
testImplementation("io.kotest:kotest-runner-junit5-jvm:5.8.0")
testImplementation("io.kotest:kotest-assertions-core-jvm:5.8.0")
testImplementation("io.kotest:kotest-framework-datatest:5.8.0")
testImplementation("io.mockk:mockk:1.13.8")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
sample-springboot.gradle.kts ν΄λΉ νμΌμ springμ μ¬μ©νλ λͺ¨λμ μν νλ¬κ·ΈμΈ νμΌμ λλ€.
plugins {
id("sample-kotlin-jvm") // μμ μμ±ν sample-kotlin-jvm.gradle.kts νμΌμ νλ¬κ·ΈμΈμΌλ‘ μ¬μ©ν©λλ€.
id("org.springframework.boot")
id("io.spring.dependency-management")
kotlin("plugin.spring")
}
dependencies {
kapt("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.3")
testImplementation("com.ninja-squad:springmockk:4.0.2")
}
μμ λ§λ€μλ sample-kotlin-jvm.gradle.kts νμΌμ νλ¬κ·ΈμΈμΌλ‘ μ¬μ©νμ΅λλ€. μ΄μ κ°μ λ°©μμΌλ‘ μ΄μ μμΌλ‘ λ§λ€μ΄λΌ λͺ¨λλ€μ build.gradle.kts νμΌμλ springμ΄ νμν κ²½μ° id("sample-springboot") λ₯Ό μ¬μ©νκ³ kotlinμ λν μμ‘΄μ±λ§ νμν κ²½μ° id("sample-kotlin-jvm") μ μ¬μ©ν΄μ build.gradle.kts νμΌμ λμ± κ°κ²°νκ² λ§λ€μ΄λΌ μ μμ΅λλ€.
build.gradle.kts build.gradle.ktsμ plugins λΈλ‘μλ μλλ νλ¬κ·ΈμΈμ λ²μ μ 보λ₯Ό ν¨κ» λͺ μν΄μΌ ν©λλ€. νμ§λ§ convention νλ¬κ·ΈμΈμ λ§λ€ λλ λͺ μν νλ¬κ·ΈμΈμ λ²μ μ build.gradleμ dependencyλ‘ μ§μ ν΄μ€μΌ ν©λλ€. κ΄λ ¨ λ΄μ©μ forumsμμ νμΈν μ μμ΅λλ€.
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
// jvm
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20")
implementation("org.jetbrains.kotlin.kapt:org.jetbrains.kotlin.kapt.gradle.plugin:1.9.20")
implementation("org.jetbrains.kotlinx.kover:org.jetbrains.kotlinx.kover.gradle.plugin:0.7.5")
implementation("org.jlleitschuh.gradle:ktlint-gradle:11.0.0")
// spring
implementation("org.jetbrains.kotlin:kotlin-allopen:1.9.20")
implementation("org.springframework.boot:spring-boot-gradle-plugin:3.2.0")
implementation("io.spring.gradle:dependency-management-plugin:1.1.4")
}
common-libraryλ μλ΅νκ² μ΅λλ€. μμΈν μ½λλ githubλ₯Ό μ°Έκ³ λ°λλλ€.
βββ adapter
βββ inbound
βββ controller
βββ outbound
βββ repository
βββ application
βββ domain
βββ infrastructure
βββ h2
βββ server
βββ api
λ°λͺ¨ μ½λμλ μ¬λ¬κ°μ§ λͺ¨λμ΄ λ μμ§λ§ λ³Έ ν¬μ€ν μμλ sample-serviceμ μ λͺ¨λμ λν΄μλ§ μ€λͺ νκ² μ΅λλ€.
build.gradle.kts
plugins {
id("sample-kotlin-jvm")
}
domain λͺ¨λμ μμ λ§λ€μ΄λ build-plugin νλ‘μ νΈμ kotlin νλ¬κ·ΈμΈλ§ μ¬μ©ν©λλ€.
Sample
class Sample(
val id: String? = null,
name: String,
val createdAt: LocalDateTime = LocalDateTime.now(),
updatedAt: LocalDateTime = LocalDateTime.now(),
) {
// μλ΅
}
μμΌλ‘ λμ€λ μ½λλ μ΄ Sample ν΄λμ€λ₯Ό κΈ°λ°μΌλ‘ μμ±λ©λλ€.
βββ build.gradle.kts
βββ src
βββ main
βββ kotlin
βββ com
βββ sample
βββ hexagonal
βββ sample
βββ application
βββ port
β βββ inbound
β β βββ sample
β β βββ SampleDeleteInboundPort.kt
β β βββ SampleFindInboundPort.kt
β β βββ SampleSaveInboundPort.kt
β βββ outbound
β βββ sample
β βββ SampleDeleteOutboundPort.kt
β βββ SampleFindOutboundPort.kt
β βββ SampleSaveOutboundPort.kt
βββ service
β βββ sample
β βββ SampleService.kt
application λͺ¨λμ κ°λ΅ν tree ꡬ쑰λ μμ κ°μ΅λλ€. inboundμ outboundλ μμ μΈκΈνλ―μ΄ μΈλΆμμ λ΄λΆλ‘, λ΄λΆμμ μΈλΆλ‘ λκ°λ ν΅λ‘ μΈν°νμ΄μ€μ λλ€. applicationλͺ¨λμ serviceλ InboundPort μΈν°νμ΄μ€λ₯Ό ꡬννκ² λ©λλ€. μΈλΆμμ λ΄λΆλ‘ λ€μ΄μ€λ μμ²μ InboundPortλ₯Ό ν΅ν΄ λ΄λΆλ‘ λ€μ΄μ€κ³ λ΄λΆμμ μΈλΆλ‘ λκ°λ μμ²μ OutbountPort μΈν°νμ΄μ€λ₯Ό ν΅ν΄ λκ°κ² λ©λλ€.
build.gradle.kts
plugins {
id("sample-springboot")
}
dependencies {
api(project(":domain"))
implementation("com.sample.hexagonal.common:kafka-producer")
implementation("com.sample.hexagonal.common:exception")
implementation("com.sample.hexagonal.common:kafka-producer")
implementation("org.springframework.data:spring-data-commons")
implementation("org.springframework:spring-context")
implementation("org.springframework:spring-tx")
}
// μλ΅
application λͺ¨λμ domain λͺ¨λκ³Ό common-library νλ‘μ νΈμμ νμν λͺ¨λμ μμ‘΄μ±μΌλ‘ λ°μμ μ¬μ©ν©λλ€.
SampleService
@Service
class SampleService(
private val sampleDeleteOutboundPort: SampleDeleteOutboundPort,
private val sampleFindOutboundPort: SampleFindOutboundPort,
private val sampleSaveOutboundPort: SampleSaveOutboundPort,
) : SampleDeleteInboundPort, SampleSaveInboundPort, SampleFindInboundPort {
@Transactional
override fun saveSample(name: String): Sample {
return sampleSaveOutboundPort.save(
Sample.create(name),
)
}
// μλ΅
}
SampleServiceλ InbountPort μΈν°νμ΄μ€λ₯Ό ꡬννκ² λκ³ μΈλΆλ‘μ μμ²μ OutbountPort μΈν°νμ΄μ€λ₯Ό μ¬μ©νμ¬ μ²λ¦¬ν©λλ€.
λ°λͺ¨ μ½λμλ μ¬λ¬ inbound λͺ¨λμ΄ μμ§λ§ controller λͺ¨λλ§ μ΄ν΄λ³΄κ² μ΅λλ€.
build.gradle.kts
plugins {
id("sample-springboot")
}
dependencies {
implementation(project(":application"))
implementation("com.sample.hexagonal.common:json")
implementation("com.sample.hexagonal.common:utils")
implementation("org.springframework.boot:spring-boot-starter-web")
}
// μλ΅
application λͺ¨λκ³Ό common-library νλ‘μ νΈμμ νμν λͺ¨λμ μμ‘΄μ±μΌλ‘ λ°μ΅λλ€.
SampleController
@RestController
class SampleController(
private val sampleFindInboundPort: SampleFindInboundPort,
private val sampleSaveInboundPort: SampleSaveInboundPort,
private val sampleDeleteInboundPort: SampleDeleteInboundPort,
) {
@PostMapping("/v1/sample")
fun saveSample(@RequestBody sampleSaveRequest: SampleSaveRequest): SampleResponse {
val sample = sampleSaveInboundPort.saveSample(sampleSaveRequest.name)
return SampleResponse.from(sample)
}
// μλ΅
}
controllerλ λ΄λΆμ ν΅μ νκΈ° μν΄ InbountPort μΈν°νμ΄μ€λ₯Ό μ¬μ©νμ¬ μμ²ν©λλ€.
λ°λͺ¨ μ½λμλ μ¬λ¬ outbound λͺ¨λμ΄ μμ§λ§ repository λͺ¨λλ§ μ΄ν΄λ³΄κ² μ΅λλ€.
build.gradle.kts
plugins {
id("sample-springboot")
}
dependencies {
implementation(project(":application"))
implementation(project(":infrastructure:h2"))
// implementation(project(":infrastructure:mongo"))
}
// μλ΅
Repositoryλͺ¨λμ applicationκ³Ό infraμ h2λͺ¨λμ μμ‘΄μ±μΌλ‘ μ¬μ©ν©λλ€.
SampleRepository
@Repository
class SampleRepository(
private val sampleDao: SampleEntityDao,
) : SampleDeleteOutboundPort, SampleFindOutboundPort, SampleSaveOutboundPort {
override fun save(sample: Sample): Sample {
return sampleDao.save(SampleMapper.mapDomainToEntity(sample))
.let { SampleMapper.mapEntityToDomain(it) }
}
// μλ΅
}
Repositoryλ λ΄λΆμμ μΈλΆμ μμ²μ μ¬μ©λλ OutboundPort μΈν°νμ΄μ€μ ꡬν체λ₯Ό μμ±ν©λλ€.
build.gradle.kts
dependencies {
implementation("com.sample.hexagonal.common:utils")
implementation("com.sample.hexagonal.common:json")
api("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")
}
// μλ΅
h2 λͺ¨λμμλ common-libraryμμ νμν μμ‘΄μ±μ μΆκ°ν©λλ€.
entity & dao
@Entity
class SampleEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val name: String,
@CreatedDate
val createdAt: LocalDateTime,
@LastModifiedDate
val updatedAt: LocalDateTime,
)
@Repository
interface SampleEntityDao : JpaRepository<SampleEntity, Long>
h2 λͺ¨λμμλ Repository λͺ¨λμμ μ¬μ©ν μΈνλΌ κΈ°μ λ€μ ꡬνν©λλ€.
build.gradle.kts
plugins {
id("sample-springboot")
}
dependencies {
implementation(project(":domain"))
implementation(project(":application"))
implementation(project(":adapter:inbound:controller"))
implementation(project(":adapter:outbound:repository"))
implementation(project(":adapter:outbound:producer"))
implementation(project(":infrastructure:h2"))
implementation("com.sample.hexagonal.common:actuator")
implementation("org.springframework.boot:spring-boot-starter-web")
// λͺ
μμ μΌλ‘ νμΈνκΈ° μν΄μ μΆκ°
implementation("com.sample.hexagonal.common:utils")
implementation("com.sample.hexagonal.common:json")
implementation("com.sample.hexagonal.common:kafka-producer")
implementation("com.sample.hexagonal.common:exception")
}
// μλ΅
server-api λͺ¨λμ μλ²λ₯Ό λμ°κΈ° μν κ»λ°κΈ° λͺ¨λμ λλ€. μ»΄ν¬λνΈ μ€μΊμ μν΄ ν΄λΉ μλ²λ₯Ό λμΈλ μ¬μ©ν μμ‘΄μ±λ€μ μΆκ°ν©λλ€. μΈλΆ νλ‘μ νΈ λͺ¨λμ κ²½μ° μ΄λ―Έ λ΄λΆ λͺ¨λμ μμ‘΄μ±μΌλ‘ λ€μ΄κ° μκΈ° λλ¬Έμ μ€νλ§ λΆνΈμ μ»΄ν¬λνΈ μ€μΊ λ©μ»€λμ¦μ ν΄λμ€ν¨μ€μ μ‘΄μ¬νλ λͺ¨λ ν¨ν€μ§λ₯Ό μ€μΊν μ μκΈ° λλ¬Έμ μΈλΆ νλ‘μ νΈ λͺ¨λμ μμ‘΄μ±μΌλ‘ μΆκ°νμ§ μμλ λ©λλ€. νμ§λ§ μΈλΆ νλ‘μ νΈ λͺ¨λμ μΆκ°νμ§ μμΌλ©΄ μ»΄ν¬λνΈ μ€μΊμΌλ‘ μ§μ ν basePackageλ₯Ό λλ½ν κ°λ₯μ±μ΄ μκΈ° λλ¬Έμ λͺ μμ μΌλ‘ μμ‘΄μ±μ μΆκ°ν΄λκ³ basePackageλ₯Ό μ§μ ν λ μ°Έκ³ ν μ μλλ‘ ν΄λμμ΅λλ€.
SampleApplication
@SpringBootApplication(
scanBasePackages = [
"com.sample.hexagonal.sample.server.api",
"com.sample.hexagonal.sample.adapter",
"com.sample.hexagonal.sample.application",
"com.sample.hexagonal.sample.infrastructure",
"com.sample.hexagonal.common"
],
)
class SampleApplication
fun main(args: Array<String>) {
TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.UTC))
runApplication<SampleApplication>(*args)
}
μΈλΆ νλ‘μ νΈμ μμ‘΄μ±μ common-library νλ‘μ νΈμ λͺ¨λλ§ μ¬μ©νλ―λ‘ commonμ μΆκ°νκ³ λλ¨Έμ§λ λ΄λΆ λͺ¨λμ ν¨ν€μ§ κ²½λ‘λ₯Ό μΆκ°νμ΅λλ€. νμ¬ λ°λͺ¨ μ½λμμμλ μ¬μ€ com.sample.hexagonal λ§ λͺ μνκ±°λ com.sample.hexagonal.sampleμΌλ‘ sample-service ν¨ν€μ§ κ²½λ‘λ₯Ό μ λ ₯ν΄μ μ€μΊ λͺ©λ‘μ κ°μνν μ μμ΅λλ€. νμ§λ§ μμ κ°μ΄ ν μ΄μ λ λ§μ½ νμ¬ api μλ²μμ adapter.controller λͺ¨λμ μ¬μ©νλ adapter.controller.external ν¨ν€μ§λ§ μ¬μ©νλ―λ‘ ν΄λΉ ν¨ν€μ§λ§ μΆκ°νκ³ μΆμ μ μκ³ μ΄λ μ€μΊ λͺ©λ‘μ ν΅ν΄ 컨νΈλ‘€ν μ μλ€λ κ²μ 보μ¬λ리기 μν¨μ λλ€.
μ°Έκ³