25.9. Choosing an Architecture
Table of Contents
Big Picture: What “Architecture” Really Means
When people say “backend architecture,” they are talking about how you organize your code and services so that:
- It is easy to change.
- It can handle more users later.
- It is safe and reliable.
- It is understandable to humans.
You have already seen different styles like monoliths, modular monoliths, microservices, layered architecture, service layer, repository pattern, and domain driven design basics. This chapter is about how to choose between these options for a real project.
Architecture is never “one size fits all.” It always depends on your project context.
Important rule: There is no “best architecture” in general. There is only “best architecture for this specific problem, with these constraints, at this time.”
In this chapter you will learn how to think about those constraints and turn them into a clear decision.
Key Criteria When Choosing an Architecture
The main dimensions to evaluate
When you choose an architecture, you are trading between several qualities:
| Quality | What it means in practice |
|---|---|
| Simplicity | Easy to understand, develop, and debug |
| Development speed | How fast you can build and ship features |
| Scalability | How well the system handles more users and data |
| Reliability | How well the system handles failures |
| Maintainability | How easy it is to change code over time |
| Testability | How easy it is to write and run automated tests |
| Performance | Speed of responses and resource usage |
| Team fit | Fits team skills, size, and experience |
| Operational overhead | How much effort it takes to deploy, monitor, and operate in production |
| Flexibility | How easily you can add new features or change existing ones |
| Cost | Money and time required to build and run the system |
You cannot maximize everything at once. For a small project, simplicity and speed usually win. For a big company with millions of users, scalability and reliability become more important.
Matching Architecture to Project Size and Complexity
A simple rule of thumb
You can often start with a rule like this:
| Project size / complexity | Recommended starting architecture |
|---|---|
| Small toy apps, prototypes, personal tools | Simple monolith, minimal layers |
| Small startup product, early MVP | Monolith, light layering |
| Growing product, many features, multiple devs | Modular monolith with clear boundaries |
| Very large product, many independent teams | Microservices or hybrid architecture |
Heuristic: Start simple. Only move to more complex architectures when the pain of your current design is clearly higher than the cost of moving.
Example: Personal to-do list app
- 1 developer.
- Few features.
- Low traffic.
- Goal: Learn and ship something.
Best fit: Monolithic app with a simple layered structure. Microservices would only add complexity without benefit.
Example: Growing B2B SaaS with 10 developers
- Many features: billing, projects, teams, reporting.
- 10 developers, several squads.
- Medium traffic.
Best fit: Modular monolith at first, with clear modules like billing, projects, users. Maybe split out 1 or 2 services later if needed.
Example: Global marketplace with hundreds of developers
- Many independent areas: search, payments, logistics, reviews.
- Many teams, different deployment cadences.
- High traffic, strict reliability and compliance needs.
Best fit: Microservices or service oriented architecture, because team boundaries and scale dominate the decision.
When to Use a Monolithic Architecture
Characteristics of a monolith
A monolith is usually:
- One deployable application.
- One codebase.
- One database (often).
- Clear layers inside, but not split into separate deployable services.
Monoliths are often the best starting point.
Advantages
- Simpler development setup.
- Easier local testing.
- Simpler deployment.
- Easy refactoring across the whole codebase.
- Less operational overhead (no service discovery, message buses, etc.).
Disadvantages
- Can grow into a “big ball of mud” if not structured.
- Teams may step on each other’s toes in the same codebase.
- Harder to scale different parts independently if needed.
- Long build / test times as codebase grows.
When a monolith is a good choice
Use a monolith when:
- You are building an MVP or early product.
- You have a small team.
- Requirements are likely to change a lot.
- You do not yet know the true domain boundaries.
- You do not have strong DevOps capacity.
Example:
- A startup building a new SaaS app. 3 developers. They need to validate the idea quickly. A monolith with a layered structure is perfect.
When to Use a Modular Monolith
What makes a monolith “modular”?
A modular monolith is still:
- One deployable application.
But inside, you enforce strong module boundaries. For example:
usersbillingprojectsnotifications
Each module has its own:
- Domain models.
- Services or use cases.
- Persistence layer adapters.
The rest of the app can only use these through public interfaces.
Advantages
- Many benefits of a monolith (simpler deployment, testing).
- Better separation of concerns.
- Preparation for future microservices if needed.
- Easier to reason about ownership: “Team A owns the billing module.”
Disadvantages
- Requires discipline to keep module boundaries clean.
- Still one deployment artifact. A single bug can impact everything.
- Cannot independently scale modules at the infrastructure level.
When a modular monolith is a good choice
Use a modular monolith when:
- The product has several distinct domains but traffic is not extreme.
- You have multiple developers or teams working on different areas.
- You want to delay microservices until they are really needed.
- You care about keeping a clean architecture over time.
Example:
- A project management SaaS with features like tasks, billing, reporting, teams. A modular monolith lets you organize by domain without microservices complexity.
When to Use Microservices
What microservices really buy you
Microservices turn your system into:
- Many independent deployable services.
- Often each has its own database.
- Communication through HTTP or messaging.
Microservices give you:
- Strong separation of deployment and failure. One service can fail without everything dying.
- Independent scaling for hot services.
- Independent technology choices per service (sometimes).
- Better alignment with large independent teams.
But this comes with cost.
Costs of microservices
- Operational complexity: service discovery, configuration, logging, tracing, monitoring across many services.
- Data consistency becomes harder. You cannot use simple multi table local transactions across services.
- Distributed failures: network issues, timeouts, partial failures.
- Coordination across services for new features.
Rule: Do not choose microservices to fix bad code organization. Fix your design in a monolith first. Microservices amplify both good and bad design.
When microservices are a good choice
Use microservices when:
- You have many teams that need to work independently.
- Different parts of the system have very different scaling or reliability needs.
- Your existing monolith has become painful even after modularization.
- You have strong DevOps / SRE capabilities.
Example:
- A large e commerce platform:
catalog,search,orders,payments,inventory,shipping. Each is owned by a separate team, scaled differently, and has its own uptime and latency needs.
Incremental Migration: From Monolith to Microservices
Why incremental change matters
Most systems do not start as microservices. They evolve.
Common path:
- Simple monolith.
- Modular monolith with clearer boundaries.
- Selected modules extracted into microservices.
This migration should be gradual, not “big bang”.
Example migration path
Imagine your modular monolith has modules:
usersbillingprojectsnotifications
You notice that:
notificationshandles email, SMS, and in app messages.- It is CPU and network heavy.
- It causes long response times for unrelated APIs.
A reasonable plan:
- Keep the core as a modular monolith.
- Create a Notification Service microservice.
- Extract message sending logic into this service.
- Let the monolith call the service through HTTP or publish messages into a queue.
Over time, you might also extract billing into its own service if it needs special compliance or scaling.
Alignment with Teams and Organization
Conway’s Law
Conway’s Law says:
“Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure.”
In practice:
- If you have one small team, a monolith is natural.
- If you have many teams, each responsible for a domain, modular or microservice boundaries often follow team boundaries.
Good alignment
- One team per module or service.
- Clear ownership: a team owns code, database tables, APIs.
- Minimal dependencies between teams for most changes.
Bad alignment
- Many teams all edit the same areas of a monolith.
- Changes need coordination across several teams for minor features.
- No clear ownership of modules, everyone touches everything.
When choosing architecture, always ask:
- How many teams will work on this?
- How do they communicate?
- How fast do they need to deploy changes independently?
Non-Functional Requirements and Architecture
Translating requirements into choices
Non functional requirements are things like:
- Maximum response time.
- Uptime targets, such as $99.9\%$.
- Data consistency requirements.
- Compliance or regulatory needs.
- Expected traffic growth.
These influence architecture heavily.
Examples of requirement driven choices
- High uptime and isolation
- A payment subsystem must be very reliable and certified.
- You may isolate it as a separate service with its own database and stricter controls.
- Read heavy analytics
- You might add separate read models or even separate services dedicated to reporting, so that heavy analytics queries do not affect the main transactional database.
- Low latency realtime features
- Chat or trading systems might use WebSockets and specialized services for state and messaging, instead of putting everything into the same REST API monolith.
Whenever you see a strong non functional requirement, ask:
- Does this justify separating this part of the system?
- Or can I still meet this requirement inside a modular monolith with simpler changes?
Technology and Team Skills
Do not ignore what your team can actually do
The best theoretical architecture fails if:
- No one on the team knows how to operate it.
- The tools are too complex.
Consider:
- Programming languages your team knows.
- Cloud and DevOps experience.
- Database knowledge.
- Monitoring and observability skills.
Choosing microservices with Kubernetes, advanced messaging, and complex observability for a 2 person team that just learned Docker is usually a bad idea.
Rule of thumb:
Choose the simplest architecture that your team can operate well, given the business needs.
Decision Framework: Simple Step-by-Step Process
A concrete checklist
When picking an architecture for a new backend project, you can follow a small process.
Step 1: Understand the context
Write down:
- Business goals.
- Expected number of users in the next 6 to 12 months.
- Key non functional requirements (latency, uptime, compliance).
- Team size and skills.
Example:
- 3 devs, launch in 3 months, target 1000 daily active users, no strong compliance.
Step 2: Choose base style
Using the earlier table:
- Small team, early product → Monolith with clear layers.
- Medium team, growing product → Modular monolith.
- Large organization, many teams, strict SLAs → Microservices or hybrid.
Step 3: Identify main domains
List core domains, for example:
users,billing,projects,notifications.
Keep them as modules inside a monolith at first. Name them clearly in your project structure.
Step 4: Evaluate special cases
Ask:
- Is there any domain with special compliance or uptime needs?
- Any domain with very different scaling characteristics?
- Any domain that should be built and deployed independently for business reasons?
These might be candidates for early extraction into separate services.
Step 5: Validate against constraints
Check:
- Can your team reliably run this in production?
- Are you over engineering for the current scale?
- Do you have a migration path if the product grows?
If you are unsure, lean toward simpler.
Step 6: Reevaluate over time
Architecture is not a one time decision. Every few months:
- Look at pain points: build times, coupling, deployment risks, scaling.
- Improve module boundaries.
- Only extract services when clear benefits outweigh the cost.
Example Scenarios and Decisions
Scenario 1: Early stage SaaS
- 2 developers.
- Features: user auth, simple billing, dashboard.
- Expected traffic: low to medium.
- No strict compliance.
Decision:
- Monolith with simple layers: API, services, repository.
- Maybe a bit of modular structure by feature folders.
- Focus on shipping quickly, tests, and clean code.
Scenario 2: Growing product with 3 squads
- 12 developers in 3 teams.
- Features: subscription billing, projects, reporting, notifications.
- Traffic is growing.
Decision:
- Modular monolith.
- Clear domains:
billing,projects,reporting,notifications. - Each team owns one or two modules.
- Strong internal APIs between modules, no direct database cross access.
- Prepare for possible future extraction of heavy or isolated modules.
Scenario 3: Large marketplace
- 100+ developers.
- Many subdomains: product catalog, search, payments, orders, delivery.
- High traffic, global users, strict uptime and compliance.
Decision:
- Microservices or service oriented architecture.
- Each major domain is a separate service or set of services.
- Strong DevOps, observability, and platform engineering support.
- Contract first API design between services.
- Use asynchronous messaging for decoupling where appropriate.
How to Avoid Common Architecture Mistakes
Overengineering
- Using microservices for a weekend project.
- Introducing message queues, complex event buses, and multiple databases when you only have a few endpoints.
Avoid by:
- Starting as simple as possible.
- Adding complexity only when a clear problem appears.
Underengineering
- One huge file for everything.
- No separation between API, business logic, and data access.
- Directly coupling external APIs deep inside core logic.
Avoid by:
- At least using a layered structure inside even the smallest monolith.
- Introducing basic patterns like service and repository layers to keep boundaries clear.
Ignoring evolution
- Designing as if the first version will never change.
- Not planning any path to modularization or extraction.
Avoid by:
- Structuring your monolith so that domains are clear.
- Keeping dependencies one directional, from outer layers to inner ones.
Putting It All Together
Choosing an architecture is about matching:
- The problem domain.
- The non functional requirements.
- The team and organization.
- The expected evolution of the product.
You have tools:
- Monolith for simplicity and speed.
- Modular monolith for growing products and multiple teams.
- Microservices for large scale, many teams, and strong isolation.
For an absolute beginner backend developer, the most important habits are:
- Start with a simple monolith and learn to apply clean layering and separation of concerns.
- Practice organizing code by domain inside a monolith, as if each domain could one day become its own service.
- Learn to ask: “What real problem am I solving with this architectural choice?”
If you consistently choose the simplest architecture that satisfies your current needs, and you keep refactoring as your system grows, your architecture decisions will age well.
Views: 9
KAHIBARO