SLIDE 9: Section Intro
Section 2: Monolith to Microservices
The biggest mistake engineers make with microservices is treating them as the default architecture. In reality, microservices sit at one end of a spectrum, and understanding the full spectrum is what separates thoughtful design from cargo-cult architecture.
This section walks through the four points on that spectrum: traditional monolith, modular monolith, distributed monolith (the anti-pattern), and true microservices. By the end, you will know when each architecture is the right choice, how to migrate between them, and how to avoid the traps that turn a well-intentioned microservices adoption into a distributed mess.
What We Will Cover:
- The traditional monolith and why it works longer than most people think
- The modular monolith as the most underused architecture in the industry
- The distributed monolith anti-pattern and how to detect it
- True microservices and how they differ from everything else
- A comparison framework for choosing between architectures
- The migration path from monolith to microservices
- The Strangler Fig pattern for incremental migration
- Common anti-patterns that derail decomposition
Where This Appears in Real Systems:
Every major platform you use today started as a monolith. A platform like Shopify scaled to billions in GMV on a Rails monolith. A platform like GitHub ran as a single Rails application for years before selectively extracting services. The question is never “should we use microservices?” The question is “when is the right time to move, and how do we do it without breaking everything?”
SLIDE 10: The Traditional Monolith
A monolith is a single deployable unit where all features, business logic, and data access live in one codebase and deploy as one artifact.

All four modules deploy together, scale together, and share one database. A function call from the Order Module to the Inventory Module is an in-process call. It takes nanoseconds. It never fails because of a network timeout. It participates in a single database transaction that guarantees consistency.
This is not a weakness. This is an enormous advantage that you give up the moment you distribute your system across a network.
What deployment looks like:
One team runs git push, the CI pipeline builds one artifact, tests run against one application, and one deployment goes to production. If something breaks, one rollback fixes everything. There is no question of “which service caused the outage?” because there is only one service.
Interview Relevance: Interviewers respect candidates who can explain monolith strengths with the same depth they explain microservices. It shows you choose architectures based on reasoning, not trends.
SLIDE 11: Monolith Strengths and When It Works
Monoliths work well for far longer than most engineers assume. Here is why.
Simplicity of operations: One deployment pipeline. One monitoring dashboard. One on-call rotation. One log stream to search when something breaks at 3am. For a team of 5-15 engineers, this operational simplicity is not a small benefit. It is a massive productivity advantage.
Full ACID transactions: When a user places an order, you can debit inventory, create the order record, and charge the payment in a single database transaction. If any step fails, everything rolls back automatically. In microservices, achieving this same guarantee requires sagas, compensating transactions, and idempotency keys, which you will learn about in Section 6.
Fast iteration speed: Change the order logic, the payment logic, and the notification logic in one pull request. Deploy it all at once. See the results immediately. No need to coordinate deployments across teams, no backward-compatibility concerns between service versions, no integration testing across network boundaries.
Real-world proof: A platform like Shopify processed over $200 billion in GMV in 2023 running primarily on a Rails monolith. They have extracted some services over time, but the core remains monolithic. A platform like Stack Overflow serves millions of developers from a monolith running on remarkably few servers.
Interview Tip: If an interviewer asks you to design a system for a 5-person startup, proposing microservices is the wrong answer. The right answer is a well-structured monolith that can be modularized later. Say this explicitly and the interviewer will know you have real-world judgment.
Interview Relevance: “When would you not use microservices?” is a common interview question. Having concrete examples of successful monoliths at scale makes your answer credible.
SLIDE 12: The Modular Monolith
A modular monolith is a monolith with enforced internal boundaries. It deploys as a single unit but is organized into distinct modules that communicate through well-defined interfaces.

Notice the difference from the traditional monolith. Each module still lives in the same database, but each module only accesses its own tables. The Order Module cannot directly query the User tables. It must call the User Module’s public interface to get user data.
Why this matters:
The modular monolith gives you the boundary discipline of microservices without the operational cost of distribution. You practice data ownership, define clean interfaces, and discover where the real domain boundaries are, all while keeping the simplicity of a single deployment.
When you eventually extract a module into a microservice, the interface already exists. You replace the in-process function call with a network call to the new service. The contract stays the same.
Interview Relevance: Mentioning the modular monolith as a deliberate architectural choice shows the interviewer you understand the spectrum of options between monolith and microservices, not just the two extremes.
SLIDE 13: Modular Monolith, Enforcing Boundaries in Code
The difference between a modular monolith and a messy monolith is enforcement. Without enforcement, modules slowly reach into each other’s internals, and the boundaries erode within months.
How boundaries are enforced:
class OrderModule:
def __init__(self, user_module, inventory_module):
self.user_module = user_module
self.inventory_module = inventory_module
def place_order(self, user_id, product_id, quantity):
user = self.user_module.get_user(user_id)
available = self.inventory_module.check_availability(product_id, quantity)
if not available:
raise InsufficientInventoryError(product_id, quantity)
order = Order(user_id=user_id, product_id=product_id, quantity=quantity)
self.order_repository.save(order)
return order
The Order Module calls user_module.get_user() and inventory_module.check_availability(). It does not import the User model directly. It does not write SQL queries against the inventory tables. It goes through the public interface of each module.
Enforcement mechanisms:
- Package-level access control - In Java, use separate packages with package-private classes. In Python, use explicit
__init__.pyexports. - Architecture tests - Tools like ArchUnit (Java) or custom linting rules that fail the build if a module imports from another module’s internals.
- Code review discipline - Reviewers reject any PR that crosses module boundaries without going through the public interface.
The critical rule: If the Order Module can reach into the inventory database and run SELECT * FROM inventory WHERE product_id = ? directly, you do not have a modular monolith. You have a monolith with folders.
Interview Relevance: Interviewers testing for senior-level thinking may ask how you would prepare a monolith for future microservices extraction. The modular monolith with enforced boundaries is the textbook answer.
SLIDE 14: The Distributed Monolith Anti-Pattern
A distributed monolith is the worst of both worlds. You pay the full cost of a distributed system but get none of the benefits.
How it happens:
A team splits their monolith into 5 services, but all 5 services still share the same database. Deploying Service A requires deploying Service B at the same time because they depend on each other’s internal data formats. Every feature requires changes across 3 or 4 services simultaneously. There is a “release train” where all services must be deployed together on Thursday at 2pm.
You now have all the network latency, all the partial failure scenarios, all the debugging complexity of microservices. But you have none of the independent deployment, none of the independent scaling, none of the independent ownership.
How to detect a distributed monolith:
- Shared database - Multiple services read from and write to the same tables
- Coordinated deployments - “We need to deploy services A, B, and C together”
- Circular dependencies - Service A calls B, B calls C, C calls A
- Chatty communication - 10+ network calls between services for a single user request
- Cross-service ownership - One team owns multiple services, or one feature requires changes in 4 services
- Shared data models - Services import the same ORM models or share protobuf definitions with business logic embedded
The key test: Ask this question about each service: “Can this service be deployed independently, right now, without any other service also deploying?” If the answer is no for most services, you have a distributed monolith.
Interview Relevance: If an interviewer suspects your design is a distributed monolith (shared database, coordinated deploys, circular dependencies), they will push back hard. Being able to identify and name this anti-pattern is a senior-level skill.
SLIDE 14B: Interview Practice
Question:
An interviewer shows you this architecture and asks: “What is wrong with this design?”
The design has 4 services: User Service, Order Service, Payment Service, and Notification Service. All four services connect to a single shared PostgreSQL database. The Order Service calls the Payment Service synchronously for every order. The Payment Service calls back to the Order Service to update order status. The Notification Service queries the orders table directly to find orders that need notifications.
Take 60 seconds to identify the problems before reading below.
Weak Answer:
“It needs more services. I would also add an Inventory Service and a Shipping Service.”
Why this is weak: Adding more services makes the problem worse, not better. The candidate missed the fundamental issues and jumped to adding complexity.
Strong Answer:
“This is a distributed monolith. There are three specific problems. First, all four services share a single database, so a schema migration to the orders table could break the Order Service, Payment Service, and Notification Service simultaneously. There is no true data ownership. Second, the Order Service and Payment Service have a circular dependency. Order calls Payment, Payment calls back to Order. This creates tight coupling that prevents independent deployment. Third, the Notification Service directly queries the orders table instead of consuming events or calling an API, which means it is coupled to the Order Service’s internal schema. To fix this, each service needs its own database, the circular dependency needs to be broken with asynchronous events, and the Notification Service should consume order events from a message queue instead of querying the orders table.”
What makes this strong: The candidate names the anti-pattern, identifies three specific structural problems, explains why each is harmful, and proposes concrete fixes.
SLIDE 15: True Microservices Architecture
A true microservices architecture looks fundamentally different from a distributed monolith. The key difference is not the number of services. It is the independence between them.

What makes this different from the distributed monolith:
- Each service has its own database. The Order Service cannot query the User DB directly. It calls the User Service API or holds a cached copy of the user data it needs.
- Communication between services is primarily asynchronous. The Order Service publishes an “order created” event. The Payment Service and Notification Service consume that event independently.
- No circular dependencies. Data flows in one direction for any given workflow.
- Each service can be deployed independently. A new version of the Payment Service does not require the Order Service to deploy at the same time.
The independence test (revisiting Section 1):
Apply the four properties from Slide 2 to each service. If every service passes all four tests (independently deployable, owns its data, aligned to a business capability, owned by one team), you have true microservices.
Interview Tip: When you draw a microservices diagram in an interview, always show separate databases for each service. This immediately signals to the interviewer that you understand data ownership. Drawing multiple services pointing to one database is one of the fastest ways to lose credibility.
Interview Relevance: The visual difference between a true microservices diagram and a distributed monolith diagram is something interviewers look for in the first minute of your whiteboard drawing.
SLIDE 16: Monolith vs Modular Monolith vs Microservices
Note that the distributed monolith is intentionally excluded from this table. It is not a valid architectural choice. It is an anti-pattern that happens when microservices are adopted without the discipline of data ownership and independent deployment.
Interview Relevance: This comparison framework is one of the highest-value tools in the entire course. Interviewers expect you to reason about when each architecture is appropriate, not to default to microservices every time.
SLIDE 17: When Monolith Beats Microservices
There are specific, concrete situations where a monolith is the better architecture. These are not compromises. These are the correct engineering decision.
Situation 1: Small team, early product
You have 3-10 engineers. The product is pre-product-market-fit. Requirements change weekly. In this environment, the ability to change anything and deploy everything in minutes is worth more than independent scaling. The coordination overhead of microservices would slow this team down, not speed it up.
Situation 2: Unclear domain boundaries
You are building a new product and you do not yet know where the real boundaries are. Is “pricing” part of the “catalog” domain or the “checkout” domain? Should “user preferences” live with “user profiles” or with “recommendations”? Getting these boundaries wrong in a microservices architecture is expensive to fix. Getting them wrong in a monolith is a refactor.
Situation 3: Strong consistency requirements everywhere
If nearly every operation in your system requires ACID transactions across multiple data entities, microservices make this dramatically harder. A banking ledger system where every transaction must be immediately consistent across accounts, balances, and audit logs may be simpler and safer as a monolith with a single relational database.
Situation 4: Uniform scaling profile
If every part of your system has the same load pattern and scales the same way, independent scaling per service adds infrastructure complexity without benefit. You can scale a monolith horizontally by running multiple instances behind a load balancer.
Interview Relevance: Answering “when would you not use microservices?” with specific scenarios and concrete reasoning is one of the strongest signals of architectural maturity an interviewer can see.
SLIDE 18: When Microservices Are Justified
Microservices earn their complexity when specific organizational and technical conditions are met. These conditions, not trends or blog posts, should drive the decision.
Condition 1: Multiple teams need to ship independently
You have 30+ engineers across 4-8 teams. Each team has its own roadmap and release cadence. Waiting for a shared release train slows everyone down. Independent deployment becomes a genuine productivity multiplier.
Condition 2: Different parts of the system have different scaling needs
In a platform like YouTube, the video upload pipeline handles 500 hours of video per minute but can tolerate latency. The video playback service handles millions of concurrent streams and needs sub-100ms latency. The recommendation engine is compute-intensive and bursty. Scaling these behind one monolith wastes resources. Separate services let you allocate infrastructure precisely.
Condition 3: Domain boundaries are well understood and stable
The team has been working on this product for 1-2 years. The domain model is mature. Boundaries between “orders,” “payments,” “inventory,” and “shipping” are clear and rarely change. Extracting these into services carries low risk of getting the boundaries wrong.
Condition 4: Fault isolation is critical
A bug in the recommendation engine should not take down the checkout flow. A surge in notification sending should not slow down order processing. When failures in one domain must not cascade to unrelated domains, service boundaries provide natural fault isolation.
Interview Tip: In an interview, state these conditions explicitly before proposing microservices. “I am choosing microservices for this design because we have multiple teams, different scaling profiles for search vs checkout, and well-understood domain boundaries.” This shows the interviewer you are reasoning from first principles, not defaults.
Interview Relevance: The gap between a junior and senior answer is justification. Junior candidates say “microservices.” Senior candidates say “microservices because these specific conditions are met.”
SLIDE 18B: Interview Practice
Question:
You are designing a system for a startup that just raised its Series A. They have 8 engineers, one product, and about 10,000 daily active users. The CEO asks you to design the backend architecture. The CTO mentions they want to “start with microservices so we do not have to rewrite later.” What do you recommend?
Take 60 seconds to form your answer before reading below.
Weak Answer:
“The CTO is right. Starting with microservices avoids technical debt and prevents a painful rewrite later. I would create separate services for users, orders, payments, and notifications.”
Why this is weak: It agrees with the premise without examining it. With 8 engineers and 10,000 DAU, microservices add operational overhead (4+ deployment pipelines, distributed debugging, eventual consistency) that the team is not staffed to handle. The “avoid rewrite” argument ignores that wrong service boundaries chosen now will require a more painful rewrite than refactoring a monolith.
Strong Answer:
“I would recommend starting with a modular monolith. With 8 engineers, the team does not have the capacity to operate multiple independent services, maintain separate deployment pipelines, and handle distributed debugging. More importantly, at 10,000 DAU with a product still finding its market, the domain boundaries are not yet stable. If we split into microservices now, we are likely to draw the boundaries in the wrong places, and re-drawing service boundaries is far more expensive than refactoring modules within a monolith. I would structure the monolith with clear internal modules for users, orders, payments, and notifications, each with enforced interfaces and module-scoped database access. When the team grows to 20-30 engineers and the domain stabilizes, we can extract the modules that need independent scaling or deployment into services. The modular monolith makes that future extraction straightforward because the interfaces already exist.”
What makes this strong: The candidate respectfully disagrees with the CTO using concrete reasoning (team size, operational capacity, domain maturity). They propose a specific alternative (modular monolith) and explain how it sets up a future migration path. They demonstrate that “avoiding a rewrite” is better achieved by modular design now than by premature distribution.
SLIDE 19: The Migration Path
Moving from monolith to microservices is not a binary switch. It is a staged migration that should happen incrementally over months or years.
The three stages:
Stage 1: Monolith to Modular Monolith
Identify the major business domains in your codebase. Draw boundaries around them. Enforce those boundaries with package structure, interface definitions, and architecture tests. Each module gets its own set of database tables, and no module directly accesses another module’s tables. This stage involves zero infrastructure changes. It is purely a code organization exercise.
Stage 2: Modular Monolith to First Extraction
Pick the module with the strongest case for extraction. This is usually the module with the most distinct scaling profile or the one owned by a team that is most bottlenecked by shared deployments. Extract it into a separate service with its own database. Replace the in-process function calls with API calls or event consumption. Keep everything else in the monolith.
Stage 3: Incremental Extraction
Extract additional modules one at a time, each time following the same pattern: separate database, separate deployment, replace in-process calls with network calls. Stop extracting when the remaining modules do not have a strong case for independence. Not everything needs to be a microservice.
Critical principle: Extract the module with the highest pain, not the easiest one. The easiest module to extract teaches you the least. The one causing deployment bottlenecks or scaling problems teaches you the most and delivers the most value.
Interview Relevance: Describing a staged migration path shows the interviewer you have practical experience with system evolution, not just greenfield design.
SLIDE 20: The Strangler Fig Pattern
The Strangler Fig pattern is the standard technique for incrementally migrating a monolith to microservices without a big-bang rewrite. It is named after the strangler fig tree, which grows around an existing tree and gradually replaces it.
How it works:

A routing proxy sits in front of the monolith. Initially, all traffic goes to the monolith. When you extract the Payment Service, you update the proxy to route /payments/* requests to the new service. The monolith still handles users, orders, and inventory. Over time, more routes shift to new services until the monolith handles nothing and can be retired.
Why this works:
- Zero downtime during migration
- Each extraction is independently testable and reversible
- The monolith continues working for everything that has not been extracted yet
- Risk is contained to one service at a time
Interview Relevance: When an interviewer asks “how would you migrate from a monolith to microservices?”, the Strangler Fig pattern is the expected answer. Proposing a big-bang rewrite is a red flag.
SLIDE 20B: Strangler Fig, Worked Example
Scenario: An e-commerce platform like Shopify has a Rails monolith handling 50,000 requests per second. The payments module processes $500M in monthly transactions. The team wants to extract payments into a separate service.
Step 1: Add the routing proxy
Place an API gateway (or reverse proxy like Nginx) in front of the monolith. Configure it to forward all traffic to the monolith unchanged. Verify that adding the proxy does not affect latency or reliability. This step changes zero behavior.
Step 2: Build the new Payment Service
Build the Payment Service as a standalone application with its own database. Implement the same payment API that the monolith currently exposes. Deploy it alongside the monolith but do not route any traffic to it yet.
Step 3: Shadow traffic
Route a copy of payment requests to both the monolith and the new service. The monolith’s response goes to the client. The new service’s response is logged and compared. This catches any discrepancies before real traffic moves over.
Step 4: Canary rollout
Route 1% of payment traffic to the new service. Monitor error rates, latency, and transaction success rates. If metrics are healthy, increase to 5%, then 25%, then 50%, then 100%.
Step 5: Decommission the old payment code
Once 100% of payment traffic runs through the new service, remove the payment module from the monolith. Remove the payment tables from the monolith database. The monolith is now smaller, and the Payment Service is fully independent.
Timeline: This process typically takes 2-4 months for a critical service like payments. Rushing it is not worth the risk.
Interview Tip: Walking through these specific steps in an interview demonstrates that you have done this before, or at least understand it at a practical level. Most candidates stop at “use the Strangler Fig pattern” without explaining the actual mechanics.
Interview Relevance: The level of detail you provide when describing a migration separates “I read about this” from “I have done this.”
SLIDE 21: Common Decomposition Anti-Patterns
These are the mistakes that turn a well-intentioned microservices migration into a distributed monolith. Each one violates the principles from Section 1.
Anti-pattern 1: Splitting by technical layer
Creating a “Frontend Service,” “Business Logic Service,” and “Database Service.” This creates three tightly coupled services that must change together for every feature. Service boundaries should follow business capabilities (orders, payments, inventory), not technical layers.
Anti-pattern 2: One service per database table
Creating a User Service, Address Service, Phone Number Service, and Email Service because each has its own table. These are not independent business capabilities. They are fragments of a single “User Profile” capability that should be one service.
Anti-pattern 3: Premature extraction
Extracting services before the domain is understood. The team splits “catalog” and “pricing” into separate services, then discovers that every pricing change requires a catalog change. They now have two services that must always deploy together, which is a distributed monolith.
Anti-pattern 4: The “god service”
One service accumulates business logic that should belong elsewhere. The Order Service starts handling payment validation, inventory checks, shipping calculations, and notification formatting. It becomes a monolith with a microservice label.
Anti-pattern 5: Shared database backdoor
Services officially communicate through APIs, but three of them still read from a shared “reference data” database table. Any schema change to that table breaks all three services. This is a hidden coupling that defeats the purpose of separate services.
Interview Relevance: Being able to name these anti-patterns and explain why they are harmful shows the interviewer you understand the principles behind microservices, not just the structure.
SLIDE 21B: Note It Down
Five decomposition anti-patterns to memorize:
- Splitting by technical layer - Services should follow business capabilities, not frontend/backend/database layers
- One service per table - A database table is not a business capability. Group related data under one service.
- Premature extraction - Extract services only when domain boundaries are stable and the pain of coupling is real
- God service - If one service does everything, you have a monolith with extra network calls
- Shared database backdoor - If services share a database table, they are not truly independent
The test to apply:
For each service in your design, ask: “Can this service be developed, deployed, and operated by one team without coordinating with other teams?” If the answer is no, you have one of these anti-patterns.
Interview Relevance: Interviewers may describe a flawed architecture and ask you to identify problems. Being able to name these anti-patterns by name and explain the fix is a strong signal of experience.
SLIDE 22: Section Summary and Interview Tips
Section 2 Summary:
Common Interview Mistakes:
Key Numbers to Remember:
How This Connects to Other Sections:
Section 3 (Service Decomposition) takes the “business capability alignment” principle from this section and turns it into a practical methodology using bounded contexts, aggregates, and domain-driven design. The anti-patterns from Slide 21 will reappear there as concrete examples of what happens when decomposition goes wrong. Section 4 (Communication Patterns) addresses how services talk to each other once they are extracted, directly building on the “replace in-process calls with network calls” step from the migration path. Section 5 (Data Ownership) expands on the “each service owns its database” principle and tackles the hard question: what happens when Service A needs data that Service B owns?