Skip to content
This repository was archived by the owner on Nov 21, 2023. It is now read-only.

Commit 06b2ca5

Browse files
authored
[#22] application event 발행 시 spring-integration 활용 (#26)
1 parent fb6f1f8 commit 06b2ca5

File tree

20 files changed

+161
-74
lines changed

20 files changed

+161
-74
lines changed

.github/workflows/gradle-build-pr.yml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
name: Gradle Build
2-
on:
2+
on:
33
pull_request:
4-
branches :
4+
branches:
55
- main
66
jobs:
77
gradle:
88
strategy:
99
matrix:
10-
os: [ubuntu-latest]
10+
os: [ ubuntu-latest ]
1111
runs-on: ${{ matrix.os }}
1212
steps:
13-
- name: Check out
14-
uses: actions/checkout@v2
15-
- name: Setup java
16-
uses: actions/setup-java@v1
17-
with:
18-
java-version: 11
19-
- name: Build USER module
20-
uses: gradle/gradle-build-action@v2
21-
with:
22-
build-root-directory: user/
23-
arguments: build
24-
gradle-version : 7.2
13+
- name: Check out
14+
uses: actions/checkout@v2
15+
- name: Setup java
16+
uses: actions/setup-java@v1
17+
with:
18+
java-version: 11
19+
- name: Build USER module
20+
uses: gradle/gradle-build-action@v2
21+
with:
22+
build-root-directory: /
23+
arguments: build
24+
gradle-version: 7.2

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
----
1313

14+
- 기본 환경 구성 - [ENVIRONMENT](./docs/ENVIRONMENT.md)
15+
- 코딩 컨벤션 - [CONVENTION](./docs/CONVENTION.md)
16+
1417
# 프로젝트 세팅
1518

1619
- spring boot 2.5.5

build.gradle.kts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ plugins {
88
id("org.jlleitschuh.gradle.ktlint") version "10.2.0"
99
}
1010

11-
12-
1311
allprojects {
1412
group = "com.sns"
1513
version = "1.0.0"
@@ -37,9 +35,9 @@ subprojects {
3735

3836
// test
3937
testImplementation("org.springframework.boot:spring-boot-starter-test")
40-
testImplementation("io.mockk:mockk:1.12.0")
41-
testImplementation("com.ninja-squad:springmockk:3.0.1")
42-
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
38+
testImplementation("io.mockk:mockk:${property("mockkVersion")}")
39+
testImplementation("com.ninja-squad:springmockk:${property("springMockkVersion")}")
40+
testImplementation("org.junit.jupiter:junit-jupiter:${property("jupiterVersion")}")
4341
}
4442

4543
tasks.withType<KotlinCompile> {
@@ -56,6 +54,8 @@ subprojects {
5654

5755
project(":user-api") {
5856
dependencies {
57+
implementation(project(":submodules:commons"))
58+
5959
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
6060
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
6161
implementation("io.springfox:springfox-boot-starter:3.0.0")
@@ -71,3 +71,11 @@ project(":front") {
7171
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
7272
}
7373
}
74+
75+
project(":submodules:commons") {
76+
dependencies {
77+
implementation(project(":submodules:commons"))
78+
79+
api("org.springframework.boot:spring-boot-starter-integration")
80+
}
81+
}

docs/CONVENTION.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030

3131
kotlin 공식 룰을 따름 - https://kotlinlang.org/docs/coding-conventions.html
3232

33+
- commmit 전 ktlint 체크하도록 설정
34+
```shell
35+
./gradlew addKtlintFormatGitPreCommitHook
36+
```
37+
3338
## 구성
3439

3540
- Event 는 Spring 에서 제공하는 것으로 사용한다 (`ApplicationEventPublisher`)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.sns.front.utils
2+
3+
import org.slf4j.Logger
4+
import org.slf4j.LoggerFactory
5+
6+
inline fun <reified T : Any> T.log(): Logger = LoggerFactory.getLogger(T::class.java)

gradle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mockkVersion=1.12.0
2+
springMockkVersion=3.0.1
3+
jupiterVersion=5.8.1

settings.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
rootProject.name = "juniors-ddd"
22
include("user-api", "front")
3-
3+
include(":submodules:commons")
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.sns.commons
2+
3+
interface DomainEvent {
4+
val eventId: String
5+
6+
val channel: String
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.sns.commons.annotation
2+
3+
import org.springframework.stereotype.Component
4+
5+
@Target(AnnotationTarget.CLASS)
6+
@Retention(AnnotationRetention.RUNTIME)
7+
@MustBeDocumented
8+
@Component
9+
annotation class CustomEventListener
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.sns.commons.config
2+
3+
import com.sns.commons.service.EventPublisher
4+
import org.springframework.context.ApplicationContext
5+
import org.springframework.context.annotation.Bean
6+
7+
class IntegrationEventBaseConfig {
8+
9+
@Bean
10+
fun eventPublisher(applicationContext: ApplicationContext) = EventPublisher(applicationContext)
11+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.sns.commons.service
2+
3+
import com.sns.commons.DomainEvent
4+
import com.sns.commons.utils.log
5+
import org.springframework.context.ApplicationContext
6+
import org.springframework.messaging.MessageChannel
7+
import org.springframework.messaging.support.GenericMessage
8+
9+
class EventPublisher(private val applicationContext: ApplicationContext) {
10+
private val log = log()
11+
12+
fun <T : DomainEvent> publish(domainEvent: T) {
13+
val channel = applicationContext.getBean(domainEvent.channel) as MessageChannel
14+
15+
log.info("publish ${domainEvent.eventId}")
16+
17+
channel.send(GenericMessage(domainEvent))
18+
}
19+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.sns.commons.utils
2+
3+
import org.slf4j.Logger
4+
import org.slf4j.LoggerFactory
5+
6+
inline fun <reified T : Any> T.log(): Logger = LoggerFactory.getLogger(T::class.java)

user-api/src/main/kotlin/com/sns/user/component/test/domains/TestUser.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
package com.sns.user.component.test.domains
22

3+
import com.sns.commons.DomainEvent
34
import com.sns.user.component.test.dtos.LaughingEvent
45
import org.springframework.data.annotation.Id
5-
import org.springframework.data.domain.AbstractAggregateRoot
66
import org.springframework.jdbc.core.BeanPropertyRowMapper
77
import org.springframework.jdbc.core.RowMapper
88

99
data class TestUser(
1010
val nickName: String = "anonymous"
11-
) : AbstractAggregateRoot<TestUser>() {
11+
) {
1212
@Id
1313
private var id: Int? = null
1414

15-
fun happy() {
16-
registerEvent(LaughingEvent(nickName))
15+
fun happy(publish: (DomainEvent) -> Unit = { _ -> }) {
16+
publish(LaughingEvent(nickName))
1717
}
1818

1919
companion object {
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
package com.sns.user.component.test.dtos
22

3-
import com.sns.user.core.supports.DomainEvent
3+
import com.sns.commons.DomainEvent
4+
import com.sns.user.core.config.IntegrationConfig
45

5-
class LaughingEvent(val who: String) : DomainEvent
6+
class LaughingEvent(val who: String) : DomainEvent {
7+
// FIMXE 예제용 임시 포맷
8+
override val eventId: String
9+
get() = "$channel-$who-${System.currentTimeMillis()}"
10+
11+
override val channel = IntegrationConfig.Channels.EMOTION
12+
}
Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
11
package com.sns.user.component.test.listeners
22

3+
import com.sns.commons.annotation.CustomEventListener
4+
import com.sns.commons.utils.log
35
import com.sns.user.component.test.dtos.LaughingEvent
4-
import org.slf4j.LoggerFactory
5-
import org.springframework.context.event.EventListener
6-
import org.springframework.core.annotation.Order
7-
import org.springframework.stereotype.Component
86

9-
@Component
7+
@CustomEventListener
108
class EmotionListener {
11-
val log = LoggerFactory.getLogger(javaClass)
9+
private val log = log()
1210

13-
@EventListener(LaughingEvent::class)
14-
@Order(1)
1511
fun onLaughing(event: LaughingEvent) {
1612
log.error("${event.who} is laughing")
1713
}
18-
19-
@EventListener
20-
@Order(2)
21-
fun actionLaughing(event: LaughingEvent) {
22-
log.error("하 하 하")
23-
}
2414
}
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
package com.sns.user.component.test.repositories
22

3+
import com.sns.commons.utils.log
34
import com.sns.user.component.test.domains.TestUser
4-
import org.slf4j.LoggerFactory
5+
import org.springframework.data.repository.CrudRepository
56
import org.springframework.jdbc.core.JdbcTemplate
67
import org.springframework.stereotype.Repository
78

89
@Repository
9-
class DefaultTestUserRepository(val jdbcTemplate: JdbcTemplate) : TestUserRepository {
10-
private val log = LoggerFactory.getLogger(javaClass)
10+
class DefaultTestUserRepository(
11+
private val jdbcTemplate: JdbcTemplate,
12+
private val testUserCRUDRepository: TestUserCRUDRepository
13+
) : TestUserRepository,
14+
CrudRepository<TestUser, Int> by testUserCRUDRepository {
15+
private val log = log()
1116

1217
override fun save(nickName: String): Int {
1318
return jdbcTemplate.update("INSERT INTO test_user (`nick_name`) VALUES(?)", nickName)
1419
}
1520

16-
override fun save(user: TestUser): Int {
17-
return jdbcTemplate.update("INSERT INTO test_user (`nick_name`) VALUES(?)", user.nickName)
18-
}
19-
2021
override fun findByNickName(nickName: String): TestUser {
2122
return jdbcTemplate.queryForObject(
2223
"""
@@ -25,7 +26,7 @@ class DefaultTestUserRepository(val jdbcTemplate: JdbcTemplate) : TestUserReposi
2526
WHERE nick_name = ?
2627
LIMIT 1
2728
""",
28-
TestUser.MAPPER, nickName
29+
TestUser.MAPPER, nickName,
2930
) ?: TestUser()
3031
}
3132
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.sns.user.component.test.repositories
22

33
import com.sns.user.component.test.domains.TestUser
4+
import org.springframework.data.repository.CrudRepository
5+
import org.springframework.data.repository.NoRepositoryBean
46

5-
interface TestUserRepository {
7+
@NoRepositoryBean
8+
interface TestUserRepository : CrudRepository<TestUser, Int> {
69
fun save(nickName: String): Int
7-
fun save(user: TestUser): Int
810
fun findByNickName(nickName: String): TestUser
911
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.sns.user.core.config
2+
3+
import com.sns.commons.config.IntegrationEventBaseConfig
4+
import com.sns.user.component.test.dtos.LaughingEvent
5+
import com.sns.user.component.test.listeners.EmotionListener
6+
import org.springframework.context.annotation.Bean
7+
import org.springframework.context.annotation.Configuration
8+
import org.springframework.context.annotation.Import
9+
import org.springframework.integration.dsl.integrationFlow
10+
11+
@Import(IntegrationEventBaseConfig::class)
12+
@Configuration
13+
class IntegrationConfig {
14+
15+
@Bean
16+
fun emotionFlow(emotionListener: EmotionListener) = integrationFlow {
17+
channel { publishSubscribe(Channels.EMOTION) }
18+
handle<LaughingEvent> { event, _ ->
19+
emotionListener.onLaughing(event)
20+
}
21+
}
22+
23+
object Channels {
24+
const val EMOTION = "EMOTION_CHANNEL"
25+
}
26+
}

user-api/src/main/kotlin/com/sns/user/core/supports/DomainEvent.kt

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
11
package com.sns.user.components.test
22

33
import com.ninjasquad.springmockk.SpykBean
4+
import com.sns.commons.service.EventPublisher
45
import com.sns.user.component.test.domains.TestUser
56
import com.sns.user.component.test.listeners.EmotionListener
6-
import com.sns.user.component.test.repositories.TestUserCRUDRepository
77
import com.sns.user.component.test.repositories.TestUserRepository
88
import io.mockk.verify
99
import org.assertj.core.api.Assertions.assertThat
1010
import org.assertj.core.api.Assumptions
1111
import org.junit.jupiter.api.DisplayName
1212
import org.junit.jupiter.api.Tag
1313
import org.junit.jupiter.api.Test
14-
import org.junit.jupiter.api.extension.ExtendWith
1514
import org.junit.jupiter.params.ParameterizedTest
1615
import org.junit.jupiter.params.provider.ValueSource
1716
import org.springframework.beans.factory.annotation.Autowired
1817
import org.springframework.boot.test.context.SpringBootTest
1918
import org.springframework.test.annotation.Rollback
20-
import org.springframework.test.context.junit.jupiter.SpringExtension
2119

2220
@Tag("init-test")
23-
@ExtendWith(SpringExtension::class)
2421
@Rollback
2522
@SpringBootTest
2623
class TestUserRepositoryTest @Autowired constructor(
2724
val repository: TestUserRepository,
28-
val crudRepository: TestUserCRUDRepository,
25+
val eventPublisher: EventPublisher
2926
) {
3027

3128
@SpykBean
@@ -40,24 +37,14 @@ class TestUserRepositoryTest @Autowired constructor(
4037
}
4138

4239
@Test
43-
@DisplayName("CRUDRepository.save 호출시 이벤트 리스너 동작.")
40+
@DisplayName("repository.save 호출시 이벤트 리스너 동작.")
4441
fun laughing_on_crud_repo() {
4542
val user = TestUser(nickName = "NEW_USER")
46-
user.happy()
47-
crudRepository.save(user)
48-
49-
verify(exactly = 1) { emotionListener.actionLaughing(any()) }
50-
verify(exactly = 1) { emotionListener.onLaughing(any()) }
51-
}
52-
53-
@Test
54-
@DisplayName("일반 @Repository.save 호출시 이벤트 리스너 미동작.")
55-
fun laughing_on_normal_repo() {
56-
val user = TestUser(nickName = "NEW_USER")
57-
user.happy()
43+
user.happy() {
44+
eventPublisher.publish(it)
45+
}
5846
repository.save(user)
5947

60-
verify(exactly = 0) { emotionListener.actionLaughing(any()) }
61-
verify(exactly = 0) { emotionListener.onLaughing(any()) }
48+
verify(exactly = 1) { emotionListener.onLaughing(any()) }
6249
}
6350
}

0 commit comments

Comments
 (0)