Today we’re cooking the perfect Use Case — and CQRS will be our recipe.
Ingredients
- Action — fire and forget (logout, clear cache)
- Query — read data (list products)
- Command — write data (update profile)
- Exchange — transform data (login)
All four types extend a sealed interface:
sealed interface UseCase<Input, Output> {
class Action : UseCase<Unit, Unit>
class Query<Output> : UseCase<Unit, Output>
class Command<Input> : UseCase<Input, Unit>
class Exchange<Input, Output> : UseCase<Input, Output>
}
Three Ways to Season It
Arrow (Typed Errors) — the chef’s choice:
class GenerateSeed(
private val seedService: SeedService
) : UseCase.Action {
override suspend fun Raise<Throwable>.action() =
seedService.generateSeed().bind()
}
Result (Standard Wrapper) — zero dependencies:
class GenerateSeed(private val service: SeedService) : UseCase.Action {
override suspend fun action() = service.generateSeed().getOrThrow()
}
Raw (Direct Execution) — zero overhead:
class GenerateSeed(private val service: SeedService) : UseCase.Action {
override suspend fun action() = service.generateSeed()
}
Why This Recipe Works
- No more
UseCase<Unit, Unit>noise - Every use case follows the same structure
-
QueryorCommandmakes intent obvious
Full implementation in the GitHub repository.

Top comments (0)