If the domain is moderate and the team is small
A layered or modular monolith usually gives the best clarity-to-complexity ratio.
Teams usually do not fail because they chose the wrong fashionable label. They fail because they ignored the forces acting on the system: rate of change, infrastructure volatility, operational complexity, team structure, domain complexity, and testing needs.
A payment-heavy, integration-rich commerce platform has different forces than an internal catalog management app. The first might need stronger boundary isolation and asynchronous flows. The second might be better served by a modular monolith with disciplined packages. The architecture choice should match those forces.
The best architecture choice usually emerges from a sequence of questions. How volatile is the infrastructure? How complex is the domain? How many teams will work in the codebase? Are deployments independent or coordinated? Do you need synchronous request-response most of the time, or is asynchronous collaboration central?
This page gives a practical guide: start simple, choose only the extra structure you can justify, and prefer internal modularity before external distribution unless the system clearly needs separate deployment and operational independence.
A layered or modular monolith usually gives the best clarity-to-complexity ratio.
Hexagonal, onion, or clean architecture can help keep the core insulated from frameworks and transport mechanisms.
Event-driven architecture becomes more useful when events are a real coordination mechanism rather than decoration.
| Situation | Usually start with | Why |
|---|---|---|
| Internal business app, one team, moderate domain | Layered or modular monolith | Fastest path to clarity without operational overreach |
| Business core must survive framework churn | Hexagonal / Onion / Clean | Protect dependency direction and testability |
| Heavy asynchronous flows and loose coupling needs | Event-driven | Events become the coordination model, not just notifications |
| Streaming transformation pipeline | Pipes and Filters | Processing stages are naturally sequential and composable |
| Multiple teams need independent deployment | Modular monolith first, then microservices if justified | Internal boundaries should prove themselves before distribution |
package org.javaomnibus.ecommerce.architecture;
public final class ArchitectureForces {
private final boolean manyIndependentTeams;
private final boolean highInfrastructureVolatility;
private final boolean eventDrivenCollaboration;
public ArchitectureForces(
boolean manyIndependentTeams,
boolean highInfrastructureVolatility,
boolean eventDrivenCollaboration
) {
this.manyIndependentTeams = manyIndependentTeams;
this.highInfrastructureVolatility = highInfrastructureVolatility;
this.eventDrivenCollaboration = eventDrivenCollaboration;
}
public String recommend() {
if (manyIndependentTeams) return "Start modular, distribute only when boundaries prove stable";
if (highInfrastructureVolatility) return "Protect the core with ports and inward dependencies";
if (eventDrivenCollaboration) return "Design events as real business contracts";
return "Use the simplest style that keeps dependencies honest";
}
}