From 73a571e395cf174dfa44e6355d6301bfc6a614dc Mon Sep 17 00:00:00 2001 From: maryarm <45322329+maryarm@users.noreply.github.com> Date: Sun, 22 Mar 2020 07:00:43 +0430 Subject: [PATCH] KTLN-18: Kotlin Microservice With Spring Boot (#8913) --- spring-reactive-kotlin/pom.xml | 87 +++++++++++++++++++ .../HealthTrackerApplication.kt | 11 +++ .../config/DBConfiguration.kt | 28 ++++++ .../controller/HealthRecordController.kt | 44 ++++++++++ .../controller/ProfileController.kt | 15 ++++ .../model/AverageHealthStatus.kt | 3 + .../bootmicroservice/model/HealthRecord.kt | 8 ++ .../bootmicroservice/model/Profile.kt | 8 ++ .../repository/HealthRecordRepository.kt | 13 +++ .../repository/ProfileRepository.kt | 8 ++ .../src/main/resources/application.yml | 1 + .../controller/ProfileControllerTest.kt | 51 +++++++++++ 12 files changed, 277 insertions(+) create mode 100644 spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/HealthTrackerApplication.kt create mode 100644 spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/config/DBConfiguration.kt create mode 100644 spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/controller/HealthRecordController.kt create mode 100644 spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/controller/ProfileController.kt create mode 100644 spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/AverageHealthStatus.kt create mode 100644 spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/HealthRecord.kt create mode 100644 spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/Profile.kt create mode 100644 spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/repository/HealthRecordRepository.kt create mode 100644 spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/repository/ProfileRepository.kt create mode 100644 spring-reactive-kotlin/src/main/resources/application.yml create mode 100644 spring-reactive-kotlin/src/test/kotlin/com/baeldung/bootmicroservice/controller/ProfileControllerTest.kt diff --git a/spring-reactive-kotlin/pom.xml b/spring-reactive-kotlin/pom.xml index 3a693837df..cbb143f6ec 100644 --- a/spring-reactive-kotlin/pom.xml +++ b/spring-reactive-kotlin/pom.xml @@ -5,6 +5,7 @@ spring-reactive-kotlin spring-reactive-kotlin Demo project for Spring Boot + jar @@ -23,6 +24,18 @@ org.springframework.boot spring-boot-starter-webflux + + org.springframework.boot.experimental + spring-boot-starter-data-r2dbc + + + org.springframework.boot + spring-boot-starter-actuator + + + io.r2dbc + r2dbc-h2 + com.fasterxml.jackson.module jackson-module-kotlin @@ -38,21 +51,95 @@ reactor-test test + + + org.springframework.boot.experimental + spring-boot-test-autoconfigure-r2dbc + test + + + io.projectreactor + reactor-test + test + + + + + + org.springframework.boot.experimental + spring-boot-bom-r2dbc + 0.1.0.M3 + pom + import + + + + + src/main/kotlin + src/test/kotlin kotlin-maven-plugin + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + org.jetbrains.kotlin ${kotlin.version} -Xjsr305=strict + 1.8 + + spring + jpa + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-maven-noarg + ${kotlin.version} + + + + 1.8 + 1.3.70 + 2.2.5.RELEASE + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + diff --git a/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/HealthTrackerApplication.kt b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/HealthTrackerApplication.kt new file mode 100644 index 0000000000..c70057b5de --- /dev/null +++ b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/HealthTrackerApplication.kt @@ -0,0 +1,11 @@ +package com.baeldung.bootmicroservice + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class HealthTrackerApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/config/DBConfiguration.kt b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/config/DBConfiguration.kt new file mode 100644 index 0000000000..b14682cc5c --- /dev/null +++ b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/config/DBConfiguration.kt @@ -0,0 +1,28 @@ +package com.baeldung.bootmicroservice.config; + +import org.springframework.context.annotation.Configuration +import org.springframework.data.r2dbc.core.DatabaseClient + +@Configuration +class DBConfiguration(db: DatabaseClient) { + init { + val initDb = db.execute { + """ CREATE TABLE IF NOT EXISTS profile ( + id SERIAL PRIMARY KEY, + first_name VARCHAR(20) NOT NULL, + last_name VARCHAR(20) NOT NULL, + birth_date DATE NOT NULL + ); + CREATE TABLE IF NOT EXISTS health_record( + id SERIAL PRIMARY KEY, + profile_id LONG NOT NULL, + temperature DECIMAL NOT NULL, + blood_pressure DECIMAL NOT NULL, + heart_rate DECIMAL, + date DATE NOT NULL + ); + """ + } + initDb.then().subscribe() + } +} diff --git a/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/controller/HealthRecordController.kt b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/controller/HealthRecordController.kt new file mode 100644 index 0000000000..620f187b7b --- /dev/null +++ b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/controller/HealthRecordController.kt @@ -0,0 +1,44 @@ +package com.baeldung.bootmicroservice.controller + +import com.baeldung.bootmicroservice.model.AverageHealthStatus +import com.baeldung.bootmicroservice.model.HealthRecord +import com.baeldung.bootmicroservice.repository.HealthRecordRepository +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController +import reactor.core.publisher.Mono + +@RestController +class HealthRecordController(val repository: HealthRecordRepository) { + + @PostMapping("/health/{profileId}/record") + fun storeHealthRecord(@PathVariable("profileId") profileId: Long, @RequestBody record: HealthRecord): Mono = + repository.save(HealthRecord(null + , profileId + , record.temperature + , record.bloodPressure + , record.heartRate + , record.date)) + + @GetMapping("/health/{profileId}/avg") + fun fetchHealthRecordAverage(@PathVariable("profileId") profileId: Long): Mono = + repository.findByProfileId(profileId) + .reduce( + AverageHealthStatus(0, 0.0, 0.0, 0.0) + , { s, r -> + AverageHealthStatus(s.cnt + 1 + , s.temperature + r.temperature + , s.bloodPressure + r.bloodPressure + , s.heartRate + r.heartRate + ) + } + ).map { s -> + AverageHealthStatus(s.cnt + , if (s.cnt != 0) s.temperature / s.cnt else 0.0 + , if (s.cnt != 0) s.bloodPressure / s.cnt else 0.0 + , if (s.cnt != 0) s.heartRate / s.cnt else 0.0) + } + +} \ No newline at end of file diff --git a/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/controller/ProfileController.kt b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/controller/ProfileController.kt new file mode 100644 index 0000000000..1dc3bcdc50 --- /dev/null +++ b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/controller/ProfileController.kt @@ -0,0 +1,15 @@ +package com.baeldung.bootmicroservice.controller + +import com.baeldung.bootmicroservice.model.Profile +import com.baeldung.bootmicroservice.repository.ProfileRepository +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController +import reactor.core.publisher.Mono + +@RestController +class ProfileController(val repository: ProfileRepository) { + + @PostMapping("/profile") + fun save(@RequestBody profile: Profile): Mono = repository.save(profile) +} \ No newline at end of file diff --git a/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/AverageHealthStatus.kt b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/AverageHealthStatus.kt new file mode 100644 index 0000000000..3141146b9c --- /dev/null +++ b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/AverageHealthStatus.kt @@ -0,0 +1,3 @@ +package com.baeldung.bootmicroservice.model; + +class AverageHealthStatus(var cnt: Int, var temperature: Double, var bloodPressure: Double, var heartRate: Double) diff --git a/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/HealthRecord.kt b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/HealthRecord.kt new file mode 100644 index 0000000000..71c534027f --- /dev/null +++ b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/HealthRecord.kt @@ -0,0 +1,8 @@ +package com.baeldung.bootmicroservice.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDate + +@Table +data class HealthRecord(@Id var id: Long?, var profileId: Long?, var temperature: Double, var bloodPressure: Double, var heartRate: Double, var date: LocalDate) \ No newline at end of file diff --git a/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/Profile.kt b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/Profile.kt new file mode 100644 index 0000000000..cbb7e675ea --- /dev/null +++ b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/model/Profile.kt @@ -0,0 +1,8 @@ +package com.baeldung.bootmicroservice.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import java.time.LocalDateTime + +@Table +data class Profile(@Id var id:Long?, var firstName : String, var lastName : String, var birthDate: LocalDateTime) \ No newline at end of file diff --git a/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/repository/HealthRecordRepository.kt b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/repository/HealthRecordRepository.kt new file mode 100644 index 0000000000..8cc91f06e4 --- /dev/null +++ b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/repository/HealthRecordRepository.kt @@ -0,0 +1,13 @@ +package com.baeldung.bootmicroservice.repository + +import com.baeldung.bootmicroservice.model.HealthRecord +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux + +@Repository +interface HealthRecordRepository: ReactiveCrudRepository { + @Query("select p.* from health_record p where p.profile_id = :profileId ") + fun findByProfileId(profileId: Long): Flux +} \ No newline at end of file diff --git a/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/repository/ProfileRepository.kt b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/repository/ProfileRepository.kt new file mode 100644 index 0000000000..eee8c5fcbe --- /dev/null +++ b/spring-reactive-kotlin/src/main/kotlin/com/baeldung/bootmicroservice/repository/ProfileRepository.kt @@ -0,0 +1,8 @@ +package com.baeldung.bootmicroservice.repository + +import com.baeldung.bootmicroservice.model.Profile +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository + +@Repository +interface ProfileRepository: ReactiveCrudRepository \ No newline at end of file diff --git a/spring-reactive-kotlin/src/main/resources/application.yml b/spring-reactive-kotlin/src/main/resources/application.yml new file mode 100644 index 0000000000..d75683f905 --- /dev/null +++ b/spring-reactive-kotlin/src/main/resources/application.yml @@ -0,0 +1 @@ +management.endpoints.web.exposure.include: health,metrics \ No newline at end of file diff --git a/spring-reactive-kotlin/src/test/kotlin/com/baeldung/bootmicroservice/controller/ProfileControllerTest.kt b/spring-reactive-kotlin/src/test/kotlin/com/baeldung/bootmicroservice/controller/ProfileControllerTest.kt new file mode 100644 index 0000000000..51481af3d7 --- /dev/null +++ b/spring-reactive-kotlin/src/test/kotlin/com/baeldung/bootmicroservice/controller/ProfileControllerTest.kt @@ -0,0 +1,51 @@ +package com.baeldung.bootmicroservice.controller; + +import com.baeldung.bootmicroservice.model.Profile +import com.fasterxml.jackson.databind.ObjectMapper +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.test.web.reactive.server.WebTestClient +import java.time.LocalDateTime + +@SpringBootTest +class ProfileControllerTest { + @Autowired + lateinit var controller: ProfileController + + @Autowired + lateinit var mapper: ObjectMapper ; + + lateinit var client: WebTestClient + lateinit var profile: String + + @BeforeEach + fun setup() { + client = WebTestClient.bindToController(controller).build() + profile = mapper.writeValueAsString(Profile(null, "kotlin", "reactive", LocalDateTime.now())) + } + + @Test + fun whenRequestProfile_thenStatusShouldBeOk() { + client.post() + .uri("/profile") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(profile) + .exchange() + .expectStatus().isOk + } + + @Test + fun whenRequestProfile_thenIdShouldBeNotNull() { + client.post() + .uri("/profile") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(profile) + .exchange() + .expectBody() + .jsonPath("$.id") + .isNotEmpty + } +}