Skip to main content

Requests & Handlers

A request is a message that expects exactly one handler and one response. It models both queries (read data) and commands (perform an action, optionally return a result).


Defining a request

Implement Request<TResponse> to declare what a request returns:

// Query — returns data
data class GetUserQuery(val id: String) : Request<User>

// Command with result
data class CreateOrderCommand(val cartId: String) : Request<Order>

// Command with no result — use Request.Unit instead of Request<Unit>
data class DeleteAccountCommand(val userId: String) : Request.Unit

Request.Unit is a built-in nested interface that extends Request<Unit>, giving commands with no return value a cleaner declaration.


Implementing a handler

class GetUserHandler(private val db: UserRepository) : RequestHandler<GetUserQuery, User> {
override suspend fun handle(
mediator: Mediator,
requestContext: RequestContext,
request: GetUserQuery,
): User = db.findById(request.id) ?: error("User ${request.id} not found")
}

The mediator parameter lets a handler dispatch secondary requests or publish notifications without creating direct dependencies on other handlers.


Registering a handler

Use HandlerRegistry inside a MediatorRegistrar:

class AppRegistrar(
private val db: UserRepository,
) : MediatorRegistrar {
override fun register(registry: HandlerRegistry) {
registry register GetUserHandler(db)
registry register CreateOrderHandler(db)
registry register DeleteAccountHandler(db)
}
}

The registry register handler infix call is the standard way to register a handler. The +handler operator inside a scope { } block is a shorthand alias for the same thing.


Dispatching a request

val user: User = mediator.send(GetUserQuery("user-1"))
val order: Order = mediator.send(CreateOrderCommand("cart-42"))
mediator.send(DeleteAccountCommand("user-1")) // returns Unit

// trySend — wraps the result in Result instead of throwing
val result: Result<User> = mediator.trySend(GetUserQuery("user-1"))
result.onSuccess { user -> /* handle */ }
result.onFailure { error -> /* handle */ }

send is a suspend function; call it from a coroutine or another suspend context.


Rules

RuleDetail
One handler per typeRegistering a second handler silently replaces the first
Missing handler throwsMissingHandlerException is thrown if no handler is registered
Exactly one responseUse Notification when you need fan-out with no response

Next

Notifications: broadcast events to one-or-many handlers