The mediatorK DSL
mediatorK { } is the smoothest way to build a mediator — one expressive block, no registrar or handler
classes required. It is sugar over MediatorFactory.create: everything the factory accepts is
available in the block, plus inline lambda registration.
val mediator = mediatorK {
handle<CreateOrderCommand, Order> { request ->
val order = db.save(Order(request.id, request.amount))
publish(OrderCreatedEvent(order.id))
order
}
on<OrderCreatedEvent> { event -> emailService.send(event.orderId) }
}
val order = mediator.send(CreateOrderCommand("ORD-1", 150.0))
Lambda handlers
handle<TRequest, TResult> registers a lambda as the single handler for a request type — the inline
equivalent of implementing RequestHandler:
handle<GetTodoQuery, Todo?> { request -> db.find(request.id) }
The lambda runs with a HandlerScope receiver, which is the mediator (by delegation) and also exposes
the per-request context:
handle<CreateOrderCommand, Order> { request ->
val traceId: String? = context.getMetadata("traceId") // RequestContext
publish(OrderCreatedEvent(request.id)) // Mediator, directly
send(ReserveStockCommand(request.id)) // nested sends too
db.save(Order(request.id, request.amount))
}
Like HandlerRegistry.register, registering a second lambda for the same request type replaces the first.
Lambda notification handlers
on<T> registers a lambda for a notification type. Multiple handlers per type are allowed; the optional
order parameter controls their relative execution order (lower runs first):
on<OrderCreatedEvent> { event -> emailService.send(event.orderId) }
on<OrderCreatedEvent>(order = 10) { event -> analytics.track(event) }
Lambda validators
validate<TRequest> registers a validator that runs before the handler, exactly like a class-based
RequestValidator:
validate<CreateOrderCommand> { request ->
rules<String> {
check(request.amount > 0) { "Amount must be positive" }
check(request.id.isNotBlank()) { "Order id required" }
}
}
Lambda stream handlers
handleStream<TRequest, T> registers a cold-Flow handler for a stream request:
handleStream<WatchOrdersQuery, Order> { request -> db.observeOrders(request.filter) }
Behaviors and configuration
All MediatorFactory.create parameters are available in the block:
val mediator = mediatorK {
behaviors(
LoggingPipelineBehavior(),
TimeoutPipelineBehavior(timeoutMillis = 5_000),
)
streamBehaviors(StreamLoggingBehavior())
notificationPublisher = NotificationPublishStrategy.SequentialNotificationPublisher()
verifyHandlers = false
}
Mixing styles
Lambdas are perfect for small slices and prototypes; as a slice grows, promote it to a class-based handler. Both styles — plus existing registrars, including the KSP-generated one — mix freely in the same block:
val mediator = mediatorK {
registrars(OrderRegistrar(db), GeneratedMediatorRegistrar())
register(CreateOrderHandler(db)) // class-based
+CancelOrderHandler(db) // `+` shorthand
handle<PingQuery, String> { "pong" } // lambda
}
The same lambda extensions (handle, on, validate, handleStream) are also available on
HandlerRegistry, so they work inside a classic MediatorRegistrar.register implementation too.
See also: MediatorFactory for the parameter-by-parameter reference.