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


🔒 Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders & Senior Software Developers apply TDD in Legacy Code.


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