Skip to content

Commit 58fd4ec

Browse files
committed
Part4: refactoring to coroutines
1 parent db7e821 commit 58fd4ec

File tree

10 files changed

+51
-71
lines changed

10 files changed

+51
-71
lines changed

build.gradle.kts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,23 @@ repositories {
2020
dependencies {
2121
implementation("org.springframework.boot:spring-boot-starter-actuator")
2222
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
23-
implementation("org.springframework.boot:spring-boot-starter-web")
23+
implementation("org.springframework.boot:spring-boot-starter-webflux")
24+
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
25+
implementation("io.r2dbc:r2dbc-h2")
2426

2527
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
2628
implementation("com.github.javafaker:javafaker:1.0.2")
2729

2830
implementation("org.jetbrains.kotlin:kotlin-reflect")
2931
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
32+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
33+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactive")
34+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
3035

3136
testImplementation("org.springframework.boot:spring-boot-starter-test") {
3237
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
3338
}
3439

35-
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
3640
runtimeOnly("com.h2database:h2")
3741

3842
implementation("org.jetbrains:markdown:0.1.45")
Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
11
package com.example.kotlin.chat
22

3+
import io.r2dbc.spi.ConnectionFactory
34
import org.springframework.boot.autoconfigure.SpringBootApplication
45
import org.springframework.boot.runApplication
6+
import org.springframework.context.annotation.Bean
7+
import org.springframework.core.io.ClassPathResource
8+
import org.springframework.r2dbc.connection.init.CompositeDatabasePopulator
9+
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer
10+
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator
511

612
@SpringBootApplication
713
class ChatKotlinApplication
814

915
fun main(args: Array<String>) {
1016
runApplication<ChatKotlinApplication>(*args)
11-
}
17+
}
18+
19+
@Bean
20+
fun initializer(connectionFactory: ConnectionFactory): ConnectionFactoryInitializer {
21+
val initializer = ConnectionFactoryInitializer()
22+
initializer.setConnectionFactory(connectionFactory)
23+
val populator = CompositeDatabasePopulator()
24+
populator.addPopulators(ResourceDatabasePopulator(ClassPathResource("./sql/schema.sql")))
25+
initializer.setDatabasePopulator(populator)
26+
return initializer
27+
}

src/main/kotlin/com/example/kotlin/chat/controller/HtmlController.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.example.kotlin.chat.controller
22

33
import com.example.kotlin.chat.service.MessageService
44
import com.example.kotlin.chat.service.MessageVM
5+
import kotlinx.coroutines.flow.toList
56
import org.springframework.stereotype.Controller
67
import org.springframework.ui.Model
78
import org.springframework.ui.set
@@ -11,7 +12,7 @@ import org.springframework.web.bind.annotation.GetMapping
1112
class HtmlController(val messageService: MessageService) {
1213

1314
@GetMapping("/")
14-
fun index(model: Model): String {
15+
suspend fun index(model: Model): String {
1516
val messages: List<MessageVM> = messageService.latest()
1617

1718
model["messages"] = messages

src/main/kotlin/com/example/kotlin/chat/controller/MessageResource.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package com.example.kotlin.chat.controller
22

33
import com.example.kotlin.chat.service.MessageService
44
import com.example.kotlin.chat.service.MessageVM
5+
import kotlinx.coroutines.flow.Flow
6+
import kotlinx.coroutines.flow.emitAll
7+
import kotlinx.coroutines.flow.onStart
8+
import org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE
59
import org.springframework.http.ResponseEntity
610
import org.springframework.web.bind.annotation.*
711

@@ -10,7 +14,7 @@ import org.springframework.web.bind.annotation.*
1014
class MessageResource(val messageService: MessageService) {
1115

1216
@GetMapping
13-
fun latest(@RequestParam(value = "lastMessageId", defaultValue = "") lastMessageId: String): ResponseEntity<List<MessageVM>> {
17+
suspend fun latest(@RequestParam(value = "lastMessageId", defaultValue = "") lastMessageId: String): ResponseEntity<List<MessageVM>> {
1418
val messages = if (lastMessageId.isNotEmpty()) {
1519
messageService.after(lastMessageId)
1620
} else {
@@ -31,7 +35,7 @@ class MessageResource(val messageService: MessageService) {
3135
}
3236

3337
@PostMapping
34-
fun post(@RequestBody message: MessageVM) {
38+
suspend fun post(@RequestBody message: MessageVM) {
3539
messageService.post(message)
3640
}
3741
}
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package com.example.kotlin.chat.repository
22

3-
import org.springframework.data.jdbc.repository.query.Query
4-
import org.springframework.data.repository.CrudRepository
3+
import kotlinx.coroutines.flow.Flow
4+
import org.springframework.data.r2dbc.repository.Query
5+
import org.springframework.data.repository.kotlin.CoroutineCrudRepository
56
import org.springframework.data.repository.query.Param
67

7-
interface MessageRepository : CrudRepository<Message, String> {
8+
interface MessageRepository : CoroutineCrudRepository<Message, String> {
89

910
// language=SQL
1011
@Query("""
@@ -14,7 +15,7 @@ interface MessageRepository : CrudRepository<Message, String> {
1415
LIMIT 10
1516
) ORDER BY "SENT"
1617
""")
17-
fun findLatest(): List<Message>
18+
suspend fun findLatest(): List<Message>
1819

1920
// language=SQL
2021
@Query("""
@@ -24,5 +25,5 @@ interface MessageRepository : CrudRepository<Message, String> {
2425
ORDER BY "SENT" DESC
2526
) ORDER BY "SENT"
2627
""")
27-
fun findLatest(@Param("id") id: String): List<Message>
28+
suspend fun findLatest(@Param("id") id: String): List<Message>
2829
}

src/main/kotlin/com/example/kotlin/chat/service/FakeMessageService.kt

Lines changed: 0 additions & 41 deletions
This file was deleted.

src/main/kotlin/com/example/kotlin/chat/service/MessageService.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package com.example.kotlin.chat.service
22

33
interface MessageService {
44

5-
fun latest(): List<MessageVM>
5+
suspend fun latest(): List<MessageVM>
66

7-
fun after(messageId: String): List<MessageVM>
7+
suspend fun after(messageId: String): List<MessageVM>
88

9-
fun post(message: MessageVM)
9+
suspend fun post(message: MessageVM)
1010
}

src/main/kotlin/com/example/kotlin/chat/service/PersistentMessageService.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,20 @@ package com.example.kotlin.chat.service
33
import com.example.kotlin.chat.asDomainObject
44
import com.example.kotlin.chat.mapToViewModel
55
import com.example.kotlin.chat.repository.MessageRepository
6-
import org.springframework.context.annotation.Primary
76
import org.springframework.stereotype.Service
87

98
@Service
10-
@Primary
119
class PersistentMessageService(val messageRepository: MessageRepository) : MessageService {
1210

13-
override fun latest(): List<MessageVM> =
11+
override suspend fun latest(): List<MessageVM> =
1412
messageRepository.findLatest()
1513
.mapToViewModel()
1614

17-
override fun after(messageId: String): List<MessageVM> =
15+
override suspend fun after(messageId: String): List<MessageVM> =
1816
messageRepository.findLatest(messageId)
1917
.mapToViewModel()
2018

21-
override fun post(message: MessageVM) {
19+
override suspend fun post(message: MessageVM) {
2220
messageRepository.save(message.asDomainObject())
2321
}
2422
}
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
spring.datasource.schema=classpath:sql/schema.sql
2-
spring.datasource.url=jdbc:h2:file:./build/data/testdb
3-
spring.datasource.driverClassName=org.h2.Driver
4-
spring.datasource.username=sa
5-
spring.datasource.password=password
6-
spring.batch.initialize-schema=always
1+
spring.r2dbc.url=r2dbc:h2:file:///./build/data/testdb;USER=sa;PASSWORD=password

src/test/kotlin/com/example/kotlin/chat/ChatKotlinApplicationTests.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import com.example.kotlin.chat.repository.Message
55
import com.example.kotlin.chat.repository.MessageRepository
66
import com.example.kotlin.chat.service.MessageVM
77
import com.example.kotlin.chat.service.UserVM
8+
import kotlinx.coroutines.flow.first
9+
import kotlinx.coroutines.runBlocking
810
import org.assertj.core.api.Assertions.assertThat
911
import org.junit.jupiter.api.AfterEach
1012
import org.junit.jupiter.api.BeforeEach
@@ -26,7 +28,7 @@ import java.time.temporal.ChronoUnit.MILLIS
2628
@SpringBootTest(
2729
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
2830
properties = [
29-
"spring.datasource.url=jdbc:h2:mem:testdb"
31+
"spring.r2dbc.url=r2dbc:h2:mem:///testdb;USER=sa;PASSWORD=password"
3032
]
3133
)
3234
class ChatKotlinApplicationTests {
@@ -42,7 +44,7 @@ class ChatKotlinApplicationTests {
4244
val now: Instant = Instant.now()
4345

4446
@BeforeEach
45-
fun setUp() {
47+
fun setUp() = runBlocking {
4648
val secondBeforeNow = now.minusSeconds(1)
4749
val twoSecondBeforeNow = now.minusSeconds(2)
4850
val savedMessages = messageRepository.saveAll(
@@ -74,13 +76,13 @@ class ChatKotlinApplicationTests {
7476
}
7577

7678
@AfterEach
77-
fun tearDown() {
79+
fun tearDown() = runBlocking {
7880
messageRepository.deleteAll()
7981
}
8082

8183
@ParameterizedTest
8284
@ValueSource(booleans = [true, false])
83-
fun `test that messages API returns latest messages`(withLastMessageId: Boolean) {
85+
fun `test that messages API returns latest messages`(withLastMessageId: Boolean) = runBlocking{
8486
val messages: List<MessageVM>? = client.exchange(
8587
RequestEntity<Any>(
8688
HttpMethod.GET,
@@ -117,7 +119,7 @@ class ChatKotlinApplicationTests {
117119

118120

119121
@Test
120-
fun `test that messages posted to the API is stored`() {
122+
fun `test that messages posted to the API is stored`() = runBlocking {
121123
client.postForEntity<Any>(
122124
URI("/api/v1/messages"),
123125
MessageVM(

0 commit comments

Comments
 (0)