🚀 Register: Why Your E2E Tests Didn’t Replace Manual QA (Oct 7)
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.

