We can generate the project structure from sprint initializer website like https://start.spring.io/ .
In Kotlin , Maven and Java 8 and Spring-Boot version 3.0.6
Main Class or Entry Class :
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class MainClaApplication
fun main(args: Array<String>) {
runApplication<Chapter4Application>(*args)
}
Customer.kt:
data class Customer(var id: Int = 0, val name: String = "", val telephone: Telephone? = null) {
data class Telephone(var countryCode: String = "", var telephoneNumber: String = "")
}
CustomerController.kt :
@RestController
class CustomerController {
@Autowired
private lateinit var customerService: CustomerService
@GetMapping(value = "/customer/{id}")
fun getCustomer(@PathVariable id: Int) =
ResponseEntity(customerService.getCustomer(id), HttpStatus.OK)
@GetMapping(value = "/customers")
fun getCustomers(@RequestParam(required = false, defaultValue = "") nameFilter: String) =
ResponseEntity(customerService.searchCustomers(nameFilter), HttpStatus.OK)
@PostMapping(value = "/customer/")
fun createCustomer(@RequestBody customerMono: Mono<Customer>) =
ResponseEntity(customerService.createCustomer(customerMono), HttpStatus.CREATED)
}
CustomerExistException.kt :
class CustomerExistException(override val message: String) : Exception(message)
CustomerHandler.kt :
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.BodyInserters.fromObject
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse.*
import org.springframework.web.reactive.function.server.bodyToMono
import reactor.core.publisher.onErrorResume
import java.net.URI
@Component
class CustomerHandler(val customerService: CustomerService) {
fun get(serverRequest: ServerRequest) =
customerService.getCustomer(serverRequest.pathVariable("id").toInt())
.flatMap { ok().body(fromObject(it)) }
.switchIfEmpty(status(HttpStatus.NOT_FOUND).build())
fun search(serverRequest: ServerRequest) =
ok().body(customerService.searchCustomers(serverRequest.queryParam("nameFilter")
.orElse("")), Customer::class.java)
fun create(serverRequest: ServerRequest) =
customerService.createCustomer(serverRequest.bodyToMono()).flatMap {
created(URI.create("/functional/customer/${it.id}")).build()
}.onErrorResume(Exception::class) {
badRequest().body(fromObject(ErrorResponse("error creating customer",
it.message ?: "error")))
}
}
CustomerRouter.kt :
@Component
class CustomerRouter(private val customerHandler: CustomerHandler) {
@Bean
fun customerRoutes() = router {
"/functional".nest {
"/customer".nest {
GET("/{id}", customerHandler::get)
POST("/", customerHandler::create)
}
"/customers".nest {
GET("/", customerHandler::search)
}
}
}
}
CustomerService.kt:
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
interface CustomerService {
fun getCustomer(id: Int): Mono<Customer>
fun searchCustomers(nameFilter: String): Flux<Customer>
fun createCustomer(customerMono: Mono<Customer>): Mono<Customer>
}
CustomerServiceImpl.kt :
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono
import reactor.core.publisher.toFlux
import reactor.core.publisher.toMono
import java.util.concurrent.ConcurrentHashMap
@Component
class CustomerServiceImpl : CustomerService {
companion object {
val initialCustomers = arrayOf(Customer(1, "Kotlin"),
Customer(2, "Spring"),
Customer(3, "Microservice", Telephone("+91", "7123456789")))
}
val customers = ConcurrentHashMap<Int, Customer>(initialCustomers.associateBy(Customer::id))
override fun getCustomer(id: Int) = customers[id]?.toMono() ?: Mono.empty()
override fun searchCustomers(nameFilter: String) = customers.filter {
it.value.name.contains(nameFilter, true)
}.map(Map.Entry<Int, Customer>::value).toFlux()
override fun createCustomer(customerMono: Mono<Customer>) =
customerMono.flatMap {
if (customers[it.id] == null) {
customers[it.id] = it
it.toMono()
} else {
Mono.error(CustomerExistException("Customer ${it.id} already exist"))
}
}
}
ErrorHandler.kt :
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
@ControllerAdvice
class ErrorHandler {
@ExceptionHandler(Exception::class)
fun JsonParseExceptionHandler(exception: Exception): ResponseEntity<ErrorResponse> {
return ResponseEntity(ErrorResponse("JSON Error", exception.message ?: "invalid json"),
HttpStatus.BAD_REQUEST)
}
}
ErrorResponse.kt :
data class ErrorResponse(val error: String, val message: String)
in resource folder :
create one HTML file like
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello World</title>
</head>
<body>
Reactive Static Content
</body>
</html>
And,
application.yml:
spring.jackson.default-property-inclusion: NON_NULL
pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.microservices</groupId>
<artifactId>chapter4</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>chapter4</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.M7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jre8</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<configuration>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
This should be the ideal cloud native structure for cloud reactive microservice. In our coming blog we will further optimize it to the next level. I have used reactive frameworks like Webflux and Mono.
No comments:
Post a Comment