I hope this will be a series of stories, you can find the second part of the stories here.
Let’s look at the following roadmap :
On top of our roadmap, we have Bob, he is our cool user, and we provide three services for Bob(Look at these services as microservices), we need a log aggregator that Scratches the edge for our services.
Let’s scratch the edge using some mouthful techs, we will have the following techs and framework for our way:
- Spring WebFlux (reactive web framework)
- Apache Kafka (popular message broker)
- MongoDB (a charm database)
Part 1 (create a simple service using WebFlux)
In this story, we look at the first layer and start it right now. so go to Spring Initializer and create a project with the following config:
- Choose Gradle
- Choose lovely Kotlin
- Java 11
You could choose anything you want, these were my favorites and you’ll see the codes base on them.
And the following dependencies:
- Spring Reactive Web (Build reactive web applications with Spring WebFlux and Netty)
- Spring for Apache Kafka (Publish, subscribe, store, and process streams of records)
- Spring Security (Highly customizable authentication)
These are enough so far, generate and extract then open the extracted folder with your favorite IDE (I proudly using Intellij Idea, and it’s my suggestion for you), let it to resolve your dependencies and indexes your project and libraries.
Let’s start coding, as a convention, we’ll create each file and directory that mention, if it didn’t exist before, so in the following path kotlin>com>{something you know}>{project name, that also you know}>controller create a “FileController” class as the follow:
@RestController
@RequestMapping("/file")
class FileController(private val fileService: FileService) {
@GetMapping("/{fileName}")
fun getFile(@PathVariable fileName: String): Mono<FileDetail> {
return fileService.getFileDetail(fileName)
}
@GetMapping("/files")
fun getFiles() : Flux<FileDetail>{
return fileService.getFiles()
}
}
It is a regular Spring controller except, their return types!
Flux and Mono are two types of data flow (data type) introduced in Project Reactor. Project Reactor is an implementation of Reactive-Stream used by WebFlux as default. If you’re confused, it’s my bad, please attention to the following diagram:
As you can see WebFlux needs somebody to handle Reactive-Stream and there is some implementation out of the box like Project Reactor or ReactiveX, but WebFlux’s default choice is Project Reactor. You can choose another one if you want.
Mono represents a singular object (singular flow is correct) and Flux represents a plural object (1 — N Object).
Create a “FileService” in kotlin>com>{something you know}>{project name, that also you know}>service with the following contents:
@Service
class FileService {
@Value("\${filing.path.root}")
private lateinit var fileDirectory: String 1️⃣
private val pwd = Paths.get("").toAbsolutePath()2️⃣
private val absoluteFileDirectory by lazy { 3️⃣
Path.of(pwd.toString() + fileDirectory)
}
fun getFileDetail(name: String): Mono<FileDetail> {
return Flux.fromStream(provideStream()).filter 4️⃣ { path ->
path.fileName.toString() == name
}.map 5️⃣ { path ->
val size = FileChannel.open(path).size()
val fileName = path.fileName.toString()
FileDetail(fileName, size)
}.toMono() 6️⃣
}
fun getFiles(): Flux<FileDetail> {
return Flux.fromStream(provideStream()).map { path ->
val size = FileChannel.open(path).size()
val fileName = path.fileName.toString()
FileDetail(fileName, size)
}
}
/**
* Provide stream : Regenerate file's path stream by call
*
* @return Stream<Path>
*/
private fun provideStream() : Stream<Path> = Files.list(absoluteFileDirectory).filter { path ->
!path.fileName.toString().startsWith(".")7️⃣
}
}
- This value set on startup by Spring and you can declare this in `application. properties`
- It simply finds the current path of the application
- Assemble the app directory and file’s folder together (Declaration using lazy instantiation because we need to be sure of injection the value of `fileDirectory` at assemble time)
- `filter` will choose the data that meet its conditions and passes them only
- `map` will convert a specific type to another one (Path — > FileDetail)
- Because we need one element, convert a Flux to Mono (If there is more than one element `.toMono()` get the first element only)
- Excludes the hidden files
We have another class in project that you can create in the following path kotlin>com>{something you know}>{project name, that also you know}>domain
data class FileDetail(
val name : String,
val size : Long
)
It is the magic of Kotlin Data Class, we have a full model just by a few line of code
So far so good, if you run the application and request to `http://localhost:8080/file/files` you could see its contents 👌🏻
# If you see a login page in this stage you can just comment the following line in the `build.gradle.kts`:
implementation("org.springframework.boot:spring-boot-starter-security")
# You can find source code on Github