Which architecture fits?
The right architecture is rarely the most impressive one.
It’s the one whose costs match your problem.
Not every application needs microfrontends, microservices, event streaming, a modulith, CQRS, BFFs, API gateways, and three distributed teams with a Conway diagram on the wall.
But not every application stays a simple todo app.
And that’s exactly where architecture begins.
Not with the question:
“What’s modern?”
But with the much more uncomfortable question:
“What complexity do we actually have — and what complexity are we just buying out of nervousness?”

The myth: we choose architecture based on an idealized end state
Section titled “The myth: we choose architecture based on an idealized end state”A popular line in projects goes:
“Let’s just build it right from the start.”
That sounds reasonable.
Until you notice that “right” often means:
- the way it was shown at the last conference,
- the way the big tech company described it in their blog,
- the way the tooling is currently trying very hard to sell it,
- or the way the architecture would look if the product had already grown for five years.
The problem: architecture isn’t a wish list.
Architecture is a bet.
A bet on where a system will change. A bet on which parts have to stay independent. A bet on which teams can carry which responsibility. A bet on which complexity operations can genuinely handle.
And like with any bet, you can be wrong.
Except with architecture, the penalty usually isn’t a lost stake — it’s a system that, after six months, looks like someone poured enterprise patterns into a coffee machine.
Architecture addresses problems — and creates new ones
Section titled “Architecture addresses problems — and creates new ones”No architecture style is free.
A monolith isn’t automatically bad.
A modulith isn’t automatically old-fashioned.
Microfrontends aren’t automatically flexible.
Microservices aren’t automatically scalable.
Multi-tenancy isn’t automatically just a tenantId field.
Cross-platform isn’t automatically “we’ll make it responsive with CSS.”
Every architecture style addresses certain problems.
And every one brings new problems along with it.
That’s not a flaw. That’s the deal.
| Architecture decision | Typically answers | Typically creates |
|---|---|---|
| Simple monolith | fast delivery, low operational cost, easy consistency | growing coupling if boundaries are missing |
| Modular monolith / modulith | clear domain boundaries without distributed operations | discipline around module boundaries and dependencies |
| Microfrontends | independent frontend teams and deployments | runtime complexity, integration cost, UX consistency problems |
| Self-contained systems | end-to-end autonomy for business capabilities, including UI, API, data | platform, observability, and operational overhead |
| Microservices | independent services, scaling, technical decoupling | distributed data, network effects, failure modes, operations |
| Multi-tenancy | multi-tenancy, data isolation, product variants | permissions, data modeling, tests, migrations |
| Cross-platform / multi-UI | multiple clients and usage contexts | API design, state models, design systems, release coordination |
The question is not:
“Which architecture is good?”
The question is:
“Which problems do we have to solve — and which new problems can we afford?”

The simple todo app deserves simple architecture
Section titled “The simple todo app deserves simple architecture”The todo app gets misused a lot in architecture conversations.
Either as proof that everything is trivial:
“It’s just CRUD.”
Or as a playground for demonstrating microservices, event sourcing, CQRS, DDD, Kafka, Kubernetes, and three frontend layers.
Both are nonsense.
A simple application deserves simple architecture.
That’s not dismissiveness. That’s respect for the problem.
If an application has a straightforward domain, gets built by a small team, has little integration pressure, doesn’t need complex role models, and can live fine with one shared deployment, then a cleanly structured monolith is often completely sufficient.
Maybe even the best solution.
Not because monoliths are magically good.
But because they create little operational overhead, enable simple local development, don’t spread data consistency across network boundaries, and don’t force teams to build a platform before the product even exists.
A bad monolith is bad.
A simple monolith with clear structure is a strength.
The difference isn’t in the name.
The difference is in the boundaries.
When the application grows
Section titled “When the application grows”At some point, “we’ll just squeeze that in there too” stops being enough.
That rarely happens on one dramatic day.
It happens gradually.
First a feature gets spread across three components. Then a dialog hangs off a store that’s already serving five other use cases. Then the template suddenly knows API details. Then permissions get checked in places nobody expected anymore. Then a change to “edit customer” needs adjustments in five areas that, according to Jira, supposedly have nothing to do with each other.
Welcome to architecture’s everyday reality.
From this point on, a bigger architecture isn’t automatically necessary.
But a more deliberate one.
What matters more then:
- domain-aligned feature boundaries,
- clear modules,
- facades,
- explicit boundaries,
- stable ViewModels,
- clean stores,
- tests in the right places,
- dependency rules,
- traceable data flows,
- and clear domain ownership.
That’s when architecture stops looking like folder structure and starts looking like a system designed for change.

The modulith: the underrated middle ground
Section titled “The modulith: the underrated middle ground”Between “everything in one pot” and “distribute everything across network boundaries” sits an area that gets too little attention in a lot of projects:
The modulith.
A modulith isn’t a monolith with prettier folder names.
The idea is: a system stays deployable as one unit, but is clearly modularized internally. Domain areas have defined boundaries, dependencies are controlled, and technical and domain responsibilities are visible.
That can pay off significantly.
You get many of the benefits of clear architecture boundaries without immediately buying into the full cost of distributed systems.
No service mesh. No distributed transactions. No ten deployments for one small domain change. No debugging safari through five containers, three logs, and a dashboard that just says: “Something’s red.”
A modulith is especially interesting when:
- the domain is growing,
- one team or several teams need clear domain areas,
- shared deployments are still acceptable,
- data consistency stays important,
- operations should stay lean,
- and you still want to prevent the system from turning to mush internally.
The modulith, then, is often not a step backward.
It’s often the architecture a lot of systems actually needed, before someone said “microservices.”
A dedicated deep-dive article on this is worthwhile.
Microfrontends: flexible sounds expensive
Section titled “Microfrontends: flexible sounds expensive”Microfrontends sound wonderful at first.
Independent teams. Independent deployments. Their own release cycles. Clear product areas. Technological freedom. Less coordination.
In the right context, that’s valuable.
For example, when several teams work on clearly separated domain areas, different product lines exist, deployments need to happen independently, or individual frontend parts are genuinely owned independently at the organizational level.
But microfrontends don’t fix bad boundaries.
They just make bad boundaries more visible.
Before, the coupling was uncomfortable in the code. Afterward, it’s additionally visible in runtime, routing, shared dependencies, auth, styling, the design system, API contracts, build pipelines, and deployment.
Congratulations. The problem now has more places where it can catch fire.
Microfrontends aren’t a repair kit for an unclear domain.
If it’s not clear who owns a feature, which data is allowed to be shared, which UI states are shared, and how domain responsibility is divided, then Module Federation doesn’t help either.
Then you don’t gain any architecture.
Then you have distributed ambiguity.

Self-contained systems: autonomy is more than having your own build pipeline
Section titled “Self-contained systems: autonomy is more than having your own build pipeline”Self-contained systems go one step further.
A domain area doesn’t just own a piece of frontend — typically also an API, data storage, business logic, deployment, and operations. The idea is strong domain autonomy.
That can make sense when domain areas genuinely need to deliver, change, and operate independently.
Not just on paper.
But with real teams, real responsibility, real operations, and real boundaries.
A self-contained system is attractive when:
- domain areas are clearly separated,
- teams carry end-to-end responsibility,
- releases have to happen independently,
- data ownership per area matters,
- different requirements exist for operations or scaling,
- and integration is meant to run deliberately through defined contracts.
But adopted too early, it quickly turns into a platform circus.
Then you’re no longer building a product — you’re dealing with shells, gateways, auth concepts, observability, deployment matrices, contract versioning, local development environments, and the question of why one area works in preview and the other only runs on Marco’s machine.
Self-contained systems are strong when the organization and the domain fit them.
They’re painful when you build them only because “autonomous teams” looked good on a slide.
Multi-tenancy isn’t just tenantId
Section titled “Multi-tenancy isn’t just tenantId”Another classic:
“Multi-tenant? We’ll just handle that with
tenantId.”
That’s one of those sentences where architecture briefly glances out the window and wonders whether it’s still needed today.
Of course a tenantId can be part of the solution.
But multi-tenancy is rarely just a field.
It’s about questions like:
- Does data have to be strictly isolated?
- Are there different role and permission models per tenant?
- Is there tenant-specific configuration?
- Different workflows?
- Different designs?
- Different data retention?
- Different integrations?
- Different release approvals?
- Different regulatory requirements?
- Shared or separate databases?
- How do migrations work?
- How do you test tenant-specific behavior?
A login with roles isn’t a multi-tenant architecture yet.
An application where different customer groups happen to use the same code isn’t a viable multi-tenant platform yet either.
Multi-tenancy becomes an architecture driver once data isolation, configuration, permissions, variants, and operations can no longer happen on the side.
And then you have to take it seriously.
Not in a panic.
But seriously.
Cross-platform doesn’t mean “responsive UI”
Section titled “Cross-platform doesn’t mean “responsive UI””It’s similar with cross-platform.
A web app that doesn’t completely fall apart on a phone isn’t automatically a cross-platform strategy.
Multiple clients change a system.
A desktop web client has different interactions than a mobile app. A public consumer app has different security requirements than an internal admin tool. A kiosk mode, a native app, a portal, and a back office aren’t simply four screen sizes.
Multiple UIs affect:
- API design,
- ViewModels,
- permissions,
- error handling,
- offline capability,
- caching,
- navigation,
- testing,
- release cycles,
- the design system,
- support,
- monitoring,
- product ownership.
Cross-platform becomes an architecture driver once different clients don’t just look different, but have different usage models.
Multi-UI frameworks aren’t an end in themselves either.
Angular, React, native apps, Web Components, and shell architectures don’t automatically answer how domain behavior stays consistent, which UI elements are shared, where variants are allowed, or how teams roll out changes safely.
Technology can help.
But technology doesn’t replace a decision.

“We’re designing for scale from day one” often means “We’re making it complicated from day one”
Section titled ““We’re designing for scale from day one” often means “We’re making it complicated from day one””Scalability matters.
But a lot of systems don’t die because they don’t scale horizontally enough.
They die because nobody understands anymore where a domain change belongs.
Or because the team needs three days just to get everything running locally.
Or because every small tweak has to go through five repositories, two pipelines, three reviews, and one round of coordination.
Or because operations look professional in theory, but in practice nobody knows why login in preview only sometimes works.
“Scalable” isn’t a free pass for maximal architecture.
Scalable for what?
For user load? For teams? For features? For tenants? For releases? For integrations? For regulatory requirements? For ten years of maintenance?
Those are different kinds of scaling.
And they need different answers.
An architecture that scales for team autonomy can be unnecessarily heavy for small teams. An architecture that scales for user load can still be a swamp of tangled domain logic. An architecture that scales for multiple product lines can be absurdly expensive for an internal business application.
Scalability without a concrete context is just a fancier word for gut feeling.
Architecture isn’t a purely technical decision
Section titled “Architecture isn’t a purely technical decision”Another myth:
“Architecture is a technical decision.”
No.
Architecture is always also a team, product, operations, and management decision.
Of course technical factors play a role.
Frameworks, runtimes, APIs, databases, build systems, deployment, security, observability — all of it matters.
But architecture also decides:
- which teams depend on each other,
- who has to approve changes,
- which parts are owned jointly,
- where product variants are allowed to emerge,
- how fast new features can be delivered,
- which failures stay isolated,
- how expensive operations get,
- and how long new developers need before they can meaningfully contribute.
If management wants an architecture that enables independent teams, those teams actually have to exist.
If a company wants microservices but only has one team handling operations on the side, that’s not a modern architecture.
That’s a hobby with a pager.
If a product wants to sell multi-tenancy, it has to understand multi-tenancy as a product capability — not as an afterthought database column.
Architecture can’t magic away organizational problems.
It can make them visible.
Sometimes that’s uncomfortable enough on its own.

A decision map
Section titled “A decision map”Instead of choosing architecture by buzzword, a sober map helps.
Not perfect. Not final. But better than “that’s what everyone’s doing right now.”
1. Business complexity
Section titled “1. Business complexity”How much real domain complexity does the system contain?
Is this about simple data maintenance? Or about rules, variants, workflows, roles, status transitions, calculations, checks, and exceptions?
The higher the domain complexity, the more important clear models, boundaries, use cases, and tests become.
But domain complexity alone doesn’t mean microservices yet.
It often first means: better modularization.
2. Frequency of change
Section titled “2. Frequency of change”Which parts change often?
Do UI flows change constantly? Do domain rules change? Do integrations change? Do tenant requirements change? Do product lines change independently of each other?
Architecture should carry the foreseeable directions of change.
Not the fantasies of some future platform empire.
3. Team size and team autonomy
Section titled “3. Team size and team autonomy”How many teams actually work on the system?
A small team rarely needs the same architecture as five independent product teams.
If teams are supposed to deliver independently, boundaries, responsibilities, and deployments have to match that.
But if every change goes through the same three people anyway, maximum distribution often just produces maximum coordination.
4. Number of UIs and clients
Section titled “4. Number of UIs and clients”Is there one UI?
Or several?
Admin, consumer, back office, mobile, kiosk, partner portal, internal tools?
Multiple clients are a strong architecture driver when they have different usage contexts, permissions, data needs, and release cycles.
Then BFFs, API boundaries, design systems, and shared model boundaries become interesting.
Not because they sound nice.
But because they can reduce real friction.
5. Multi-tenancy
Section titled “5. Multi-tenancy”Are there really tenants?
Or just users with roles?
Does data need to be isolated? Do workflows need to vary? Does configuration need to be possible per customer? Do releases or features need to be controlled per tenant?
The more multi-tenancy shapes the product, the earlier it has to be modeled architecturally.
Adding tenantId later, “just like that,” is about as relaxed as changing an axle while driving.
6. Integration pressure
Section titled “6. Integration pressure”How many external systems hang off this?
Are these systems stable? Or do they change often? Are the data models foreign? Are there legacy systems? Are there asynchronous processes? Is there domain translation at system boundaries?
High integration pressure argues for clear adapters, anti-corruption layers, robust interfaces, and good observability.
Not automatically for maximum distribution.
But definitely against “we’ll just pass the DTOs straight through into the template.”
7. Deployment independence
Section titled “7. Deployment independence”Do parts need to be independently deployable?
Really?
Not “would be kind of cool.”
But: are there domain, organizational, or regulatory reasons why area A has to go live while area B stays unchanged?
If yes, microfrontends, self-contained systems, or microservices become more interesting.
If no, a shared deployment is often simpler, safer, and cheaper.
8. Operational and observability requirements
Section titled “8. Operational and observability requirements”Can the team operate the architecture?
Logging, tracing, metrics, alerts, failure analysis, rollbacks, data migrations, local development, preview environments, security patches.
Distributed systems without observability aren’t a modern architecture.
They’re a smoke machine.
The more distributed a system is, the more operations has to be considered from the very start.
Not later, once the first failure only shows up in the browser as a “504.”
9. Application lifespan
Section titled “9. Application lifespan”Is this a short-lived tool?
An MVP?
An internal form?
A platform meant to last ten years?
Long-term systems need different investments in structure, tests, modularity, documentation, and ownership.
But the same rule applies here too: being long-term doesn’t justify every complexity right away.
You can build architecture to be developable without simulating the end state on day one.
10. Regulation, permissions, and data isolation
Section titled “10. Regulation, permissions, and data isolation”Are there high requirements around data protection, auditability, permissions, or data isolation?
Then architecture isn’t just a question of code quality.
Then it’s about trust, traceability, access control, and containing failures.
That affects data models, APIs, logging, tests, role models, tenant boundaries, and deployment strategies.
And yes: that’s more uncomfortable than “we’ll do security later.”
11. Risk from overengineering
Section titled “11. Risk from overengineering”The last question is the most uncomfortable one:
What architectural complexity can we actually carry today?
Not theoretically.
Today.
With this team. With these skills. With this budget. With this operational maturity. With this product uncertainty. With this time pressure.
Overengineering feels professional at first.
Later it feels like a toll booth in front of every change.
A rough orientation
Section titled “A rough orientation”This table isn’t a rule.
It’s a map.
And like any map, it doesn’t replace looking out the window.
| Context | Often suitable direction |
|---|---|
| Small app, small team, limited domain complexity | simple, cleanly structured monolith |
| Growing domain complexity, one or few teams, shared deployment acceptable | modular monolith / modulith |
| Multiple domain areas, clear team ownership, independent UI releases needed | consider microfrontends |
| Domain areas need to be end-to-end autonomous, including UI, API, data, and operations | consider self-contained systems |
| Services need to scale, deploy, or own data independently along domain lines | consider microservices |
| Multiple tenants with isolation, configuration, variants, and permissions | deliberately design a multi-tenant architecture |
| Multiple clients with different usage contexts | consider a cross-platform strategy, BFFs, design system |
| Many teams, shared UI language, recurring patterns | a design system / UI platform makes sense |
| High integration pressure toward external systems | adapters, ACLs, stable API boundaries, observability |
The important word is “consider.”
Not “adopt.”
Architecture isn’t something you order from a menu.
A better alternative: choose architecture for the pressures it must handle
Section titled “A better alternative: choose architecture for the pressures it must handle”A better architecture decision doesn’t start with:
“Do we want microfrontends?”
But with questions like:
- What needs to be changeable independently?
- What needs to be deployable independently?
- What needs to stay separated along domain lines?
- What needs to stay together?
- Which data genuinely belongs together?
- Which parts change most often?
- Which teams own which area?
- Which UI parts need to stay consistent?
- Which variants are a product capability, and which are just edge cases?
- Which complexity can we actually operate today?
- Which complexity do we actually need?
- What happens if we’re wrong?
- Can we grow sensibly later without prebuilding everything today?
These questions are less glamorous than architecture diagrams with a lot of boxes.
But they keep you from building an architecture that says more about what the team wanted than about what the product needs.

What later articles will look at more closely
Section titled “What later articles will look at more closely”This article is deliberately an overview.
Each individual architecture style deserves its own treatment, because each one has its own strengths, traps, and decision questions.
Planned deep-dive articles:
-
Monolith When a shared system is a strength — and when it tips over.
-
Modulith How clear domain boundaries can work without distributed operations.
-
Microfrontend When independent frontends make sense — and when they just produce distributed chaos.
-
Self-contained system What end-to-end responsibility looks like and what maturity it requires.
-
Microservices Why service boundaries have more to do with data, ownership, and operations than with the number of repositories.
-
Multi-tenant architecture Why multi-tenancy is a product capability — not just a database field.
-
Cross-platform frontends What multiple clients actually mean for APIs, ViewModels, the design system, and releases.
-
BFF / API gateway When client-facing APIs help and when they just become another pass-through.
-
Design system / UI platform When shared UI structure speeds teams up — and when it becomes a central bottleneck.
Conclusion
Section titled “Conclusion”Good architecture isn’t as large as possible.
It’s big enough for the problem — and small enough to still be understood.
It carries the foreseeable directions of change. It fits the team structure. It respects operational reality. It separates what needs to be separated. It holds together what belongs together.
And it has the courage to be no more impressive than necessary.
Good architecture isn’t as large as possible. Good architecture is big enough for the problem — and small enough to still be understood.