Optivem Journal

Optivem Journal

Clean Architecture

Clean Architecture: Use Cases with Command Bus

Code Example

Valentina Jemuović's avatar
Valentina Jemuović
Oct 02, 2026
∙ Paid

🚀 Register: Why Your E2E Tests Didn’t Replace Manual QA (Oct 7)

Register for free


You’ve split your application into one class per use case.

Then you gave every use case the same interface — the Command Handler pattern:

public interface UseCase<TRequest, TResponse> {
    Result<TResponse, UseCaseError> execute(TRequest request);
}

One request in. One result out.

But you run into issues:

❗Problem #1: Cross-cutting concerns duplicated across use cases

Logging, transactions, authorization — many use cases need them, so you end up copying them into each one:

public final class PlaceOrder
        implements UseCase<PlaceOrderRequest, PlaceOrderResponse> {

    public Result<PlaceOrderResponse, UseCaseError> execute(
        PlaceOrderRequest request
    ) {
        log.info("PlaceOrder started");
        authorize(Permission.PLACE_ORDER);
        return transactional(() -> {
            // ... the actual use case logic
        });
    }
}
public final class CancelOrder
        implements UseCase<CancelOrderRequest, CancelOrderResponse> {

    public Result<CancelOrderResponse, UseCaseError> execute(
        CancelOrderRequest request
    ) {
        log.info("CancelOrder started");
        authorize(Permission.CANCEL_ORDER);
        return transactional(() -> {
            // ... the actual use case logic
        });
    }
}

The same lines repeat in many use cases, and every new use case has to remember them.

❗Problem #2 (secondary): Constructors with lots of injection

Every use case is its own class, so every controller needs each one it calls injected:

public OrderController(
    PlaceOrder placeOrder,
    ViewOrderDetails viewOrderDetails,
    CancelOrder cancelOrder,
    PublishCoupon publishCoupon,
    BrowseCoupons browseCoupons,
    // ... and more with every new use case
) { ... }

And each endpoint calls its own use case directly:

@PostMapping("/orders")
public ResponseEntity<?> placeOrder(@RequestBody PlaceOrderRequest request) {
    return mapResult(placeOrder.execute(request));
}

The constructor grows with every use case you add. The wiring becomes noise, and every controller test has to supply them all.

❌ Poor Solution: Base Classes

The tempting fix for Problem #1 is a BaseUseCase that every use case extends. But as it grows with logging, authorization, transactions, and error handling, every use case inherits all of it — whether it needs it or not.

And it does nothing for Problem #2: the controller still needs every use case injected.

✅ Effective Solution: Command Bus — in Practice

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2026 Valentina Jemuović, Optivem · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture