Skip to main content

Spring Boot Example

A complete CRUD API using MediatorK with Spring Boot (WebFlux + coroutines).

1. Add dependencies

// build.gradle.kts
dependencies {
implementation("io.github.fajrbahr:mediatork:0.6.2")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.10.2")
}

2. Define the domain model

data class Todo(val id: Int, val title: String, val completed: Boolean)

3. Define requests

data class GetTodosQuery : Request<List<Todo>>
data class GetTodoQuery(val id: Int) : Request<Todo>
data class CreateTodoCommand(val title: String) : Request<Todo>
data class CompleteTodoCommand(val id: Int) : Request.Unit
data class DeleteTodoCommand(val id: Int) : Request.Unit

4. Implement handlers

@Service
class GetTodosHandler(private val repo: TodoRepository) : RequestHandler<GetTodosQuery, List<Todo>> {
override suspend fun handle(mediator: Mediator, ctx: RequestContext, request: GetTodosQuery) =
repo.findAll()
}

@Service
class GetTodoHandler(private val repo: TodoRepository) : RequestHandler<GetTodoQuery, Todo> {
override suspend fun handle(mediator: Mediator, ctx: RequestContext, request: GetTodoQuery) =
repo.findById(request.id) ?: error("Todo ${request.id} not found")
}

@Service
class CreateTodoHandler(private val repo: TodoRepository) : RequestHandler<CreateTodoCommand, Todo> {
override suspend fun handle(mediator: Mediator, ctx: RequestContext, request: CreateTodoCommand) =
repo.save(Todo(id = 0, title = request.title, completed = false))
}

@Service
class CompleteTodoHandler(private val repo: TodoRepository) : RequestHandler<CompleteTodoCommand, Unit> {
override suspend fun handle(mediator: Mediator, ctx: RequestContext, request: CompleteTodoCommand) {
val todo = repo.findById(request.id) ?: return
repo.save(todo.copy(completed = true))
}
}

@Service
class DeleteTodoHandler(private val repo: TodoRepository) : RequestHandler<DeleteTodoCommand, Unit> {
override suspend fun handle(mediator: Mediator, ctx: RequestContext, request: DeleteTodoCommand) {
repo.deleteById(request.id)
}
}

5. Register handlers

@Component
class TodoRegistrar(
private val getTodos: GetTodosHandler,
private val getTodo: GetTodoHandler,
private val create: CreateTodoHandler,
private val complete: CompleteTodoHandler,
private val delete: DeleteTodoHandler,
) : MediatorRegistrar {
override fun register(registry: HandlerRegistry) {
registry.scope {
+getTodos
+getTodo
+create
+complete
+delete
}
}
}

6. Create the Mediator bean

@Configuration
class MediatorConfig(private val registrars: List<MediatorRegistrar>) {
@Bean
fun mediator(): Mediator = MediatorFactory.create(registrars = registrars)
}
tip

Spring injects all MediatorRegistrar beans automatically — no manual wiring needed.


7. Controller

@RestController
@RequestMapping("/todos")
class TodoController(private val mediator: Mediator) {

@GetMapping
suspend fun getAll() = mediator.send(GetTodosQuery())

@GetMapping("/{id}")
suspend fun get(@PathVariable id: Int) = mediator.send(GetTodoQuery(id))

@PostMapping
suspend fun create(@RequestBody body: CreateTodoCommand) = mediator.send(body)

@PatchMapping("/{id}/complete")
suspend fun complete(@PathVariable id: Int) = mediator.send(CompleteTodoCommand(id))

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
suspend fun delete(@PathVariable id: Int) = mediator.send(DeleteTodoCommand(id))
}

8. Run

./gradlew bootRun

Test it:

# Create
curl -X POST http://localhost:8080/todos -H 'Content-Type: application/json' -d '{"title":"Buy milk"}'

# List
curl http://localhost:8080/todos

# Complete
curl -X PATCH http://localhost:8080/todos/1/complete

# Delete
curl -X DELETE http://localhost:8080/todos/1