Your architecture choice is the most important decision you'll make
Most architecture discussions end up around a whiteboard with boxes and arrows. The boxes look clean. The arrows look intentional. Then you hit production and find out the arrows don't match reality. I've seen this happen more times than I'd like to admit.
Ralph Johnson and Martin Fowler describe architecture as the shared understanding of a system's most important elements. That's the right framing. It's not the framework you picked. It's the decisions that feel permanent after six months - the ones you'd need to blow up half the codebase to reverse.
So what actually is architecture?
It's not Java vs Go. It's not REST vs GraphQL. Those are implementation details you can swap out.
Architecture is about managing trade-offs consciously instead of stumbling into them. It's about knowing that your code structure will eventually mirror your org structure (Conway's Law is real, and it will find you). And it's about resisting the urge to over-engineer before you understand the problem - because every layer of abstraction you add today is a layer someone has to debug at midnight six months from now.
The real failure mode is not choosing the wrong architecture. It's choosing one without understanding what you're giving up.
The monolith - still underrated
The monolith got a bad name during the microservices hype cycle, and it mostly didn't deserve it. A monolith puts your UI, business logic, and data access in one codebase and one deployment unit. That sounds old-fashioned. It also means in-process calls that take microseconds, shared transactions, and a single stack trace when something goes wrong.
For a new product, a monolith is almost always the right starting point. You can move fast without thinking about network contracts, service discovery, or distributed consistency. The problems come later, when the codebase grows and nobody quite owns any of it. A bug in one corner can take down the whole thing. Deploying a one-line fix means deploying everything. At that point, you have to decide whether to refactor or extract.
Layered architecture - order with a cost
One way to bring discipline to a monolith is to split it into layers: presentation, application, business logic, data. Think of it as a corporate org chart. The CEO doesn't call the intern directly. Everything goes through the proper chain.
The upside is predictability. The downside people don't talk about enough is what I call lasagne code. Rigid layers that you have to punch through for every change. And when the business logic layer is too closely married to the data layer, unit tests stop being unit tests. You end up mocking five interfaces just to test a conditional.
The modular monolith - the option most teams should actually consider
If the monolith feels too chaotic and microservices feel like overcommitting, a modular monolith is worth serious attention. One running process, but with internal module boundaries that are actually enforced.
The core idea comes from Domain-Driven Design's Bounded Context. Each module owns its own data. It doesn't reach into another module's tables. It communicates through explicit interfaces or in-process events. The rule sounds simple and it is - but enforcing it requires discipline every single day.
The payoff? If you ever need to extract a module into its own service, you can. The interfaces are already there. You're not untangling a decade of shared database joins.
SOA - a cautionary tale
Before microservices, we had SOA: Service-Oriented Architecture. The defining piece was the ESB, an Enterprise Service Bus that was supposed to sit in the middle and intelligently route and transform messages between systems.
In practice, the bus became the most fragile and most critical piece of infrastructure in the building. Every team's traffic ran through it. Every integration got embedded in it. When it went down, everything went down. When you needed to change something, you needed the ESB team.
The lesson the industry took from SOA: don't put logic in the plumbing. Keep the pipes dumb and put the logic in the services. That realization is where microservices came from.
Microservices - real power, real costs
Microservices are now the default assumption for cloud-native systems, and for good reason. Independent deployments. Independent scaling. Team autonomy. You can rewrite the payments service in Rust without touching the billing service written in Ruby.
What the evangelists underemphasized: microservices don't remove complexity. They move it from your code into your infrastructure. Instead of a confusing call stack, you get confusing distributed traces. Instead of a slow function, you get network latency and partial failures. Cascading calls across five services for a single page load is a real performance problem, and it's easy to sleepwalk into.
Before going this route, ask yourself honestly: do we have the operational maturity to run this? Observability, distributed tracing, circuit breakers, chaos testing - these aren't optional extras. They're the cost of admission.
Event-driven and serverless
Event-driven architecture takes decoupling further. Services don't call each other - they emit events, and other services react. The payments service doesn't know or care that invoicing is listening. If invoicing goes down, payments keeps working. When invoicing comes back up, it catches up.
The tradeoff is observability. It's harder to follow a request through an event-driven system. You need good tooling and discipline around event schemas, or it becomes very hard to debug production issues.
Serverless pushes this further by removing the notion of a persistent server. You pay per invocation. You scale to zero. It's well-suited for workloads that are bursty and unpredictable. The catch is cold starts - when a function hasn't run in a while, the first request takes a visible hit while the runtime spins up. For user-facing latency-sensitive flows, that matters.
Summary
There is no architecture that is simply better than the others. Each one is a different set of things you will struggle with.
- Monolith: you struggle with coordination and coupling as you grow.
- Microservices: you struggle with distributed systems complexity from day one.
- Modular monolith: you struggle with enforcing boundaries when you're moving fast.
- Serverless: you struggle with cold starts, observability, and vendor lock-in.
Pick the struggle that fits where you are right now. Stay humble about changing your mind when the situation changes. And don't let diagrams convince you that any of this is simpler than it really is.
Happy architecting!