Software Architecture & Design Principles
Use-Case-Driven Design
A codebase's top-level structure should announce what the system does, not which framework renders it — "screaming architecture." Organizing around use cases rather than technical layers keeps intent visible and framework choices genuinely swappable.
- Screaming architecture: the top-level package layout should scream "shopping cart" or "loan origination," not "controllers/services/repositories"
- A use case (interactor) is a first-class object: one class, one application-specific business operation, orchestrating entities to fulfill it
- Package-by-feature over package-by-layer keeps everything one use case touches physically close together (raising cohesion, see Coupling And Cohesion)
- Frameworks are details — a web framework is a delivery mechanism plugged into use cases, not the thing the architecture is organized around
- This is what makes a system's intent legible to a new developer from the folder structure alone, before reading a line of logic
public interface RegisterUser {
Result execute(RegisterUserRequest request);
}
public class RegisterUserInteractor implements RegisterUser {
private final UserRepository users;
private final Notifier notifier;
RegisterUserInteractor(UserRepository users, Notifier notifier) {
this.users = users;
this.notifier = notifier;
}
public Result execute(RegisterUserRequest request) {
// application-specific business rule, orchestrating entities —
// no HTTP, no SQL, no framework import in sight
if (users.findByEmail(request.email()).isPresent()) return Result.duplicateEmail();
User user = User.register(request.email(), request.password());
users.save(user);
notifier.send(user.welcomeMessage());
return Result.success(user.id());
}
}