Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

Architectural Styles: Monoliths, Microservices, and Decoupling

Recommended resources

Welcome back! In our previous lesson, we established the core principles of scalability—horizontal and vertical scaling, load balancing, and data partitioning—which are essential for handling growth in any large-scale system. Now, we'll build on that foundation to address a fundamental architectural choice that directly impacts how we apply those scaling strategies.

Today's lesson focuses on a classic system design trade-off: monolithic versus microservice architectures. You'll learn to compare these two dominant architectural patterns and understand their respective strengths and weaknesses, particularly in the context of ML applications. We will also explore the critical communication mechanisms that enable microservices to function, specifically comparing synchronous REST APIs with asynchronous message queues for decoupling components. Mastering this topic is crucial, as it's a frequent point of discussion in ML system design interviews and a foundational decision in building real-world production systems.

The Two Architectural Paradigms: Monolith vs. Microservices

At a high level, the choice is between building your application as a single, large, interconnected unit (a monolith) or as a collection of small, independent, and collaborating services (microservices). This decision has profound implications for development speed, scalability, and operational complexity.

Let's begin by understanding these two styles using some helpful analogies.

Most Tricky Interview - How do you choose between Monolith vs Microservices architecture?

This video from the "IT k Funde" channel provides excellent, easy-to-grasp analogies for both architectures, making the abstract concepts very concrete.

Please watch the section explaining the monolith architecture using the "big kitchen" analogy. Then, continue with the section that explains the microservices architecture using the "food court" analogy. Pay close attention to how each analogy highlights the core issues of coupling, failure, and scaling.

As the video illustrates:

  • Monolithic Architecture: Like a single large kitchen, all components (UI, business logic, data access) are bundled into one large codebase and deployed as a single unit. Communication between components happens through simple function calls within the same process, which is very fast.
  • Microservices Architecture: Like a food court with independent stalls, the application is broken down into a suite of small, focused services. Each service is self-contained, handles a specific business capability, and can be developed, deployed, and scaled independently.

To see how this applies to a modern ML system, consider the following diagram which contrasts the two architectures for an LLM inference pipeline.

This diagram shows an LLM inference pipeline built with both architectures. In the monolith, LLM logic and business logic (like pre- and post-processing) are tightly coupled in one unit. In the microservices version, they are separated into distinct services that communicate with each other, allowing them to be managed and scaled independently.

The Trade-offs: When to Choose Which?

Neither architecture is universally superior; the choice depends heavily on the specific context of your project. Let's formalize the pros and cons that were hinted at in the analogies.

Feature Monolithic Architecture Microservices Architecture
Development Simpler to start. A single codebase is easier to set up and reason about initially. More complex initially. Requires setting up inter-service communication, service discovery, and a more robust CI/CD pipeline.
Deployment Simpler, but risky. The entire application is deployed as one unit. A small change requires a full redeployment, increasing the risk of failure. Faster and safer. Individual services can be deployed independently, allowing for quicker updates and reducing the blast radius of a bad deployment.
Scalability Difficult. You must scale the entire application, even if only one small component is the bottleneck. This is inefficient. Efficient and granular. You can scale individual services based on their specific needs, which is a direct application of the horizontal scaling we discussed.
Technology Stack Homogeneous. You are locked into a single technology stack for the entire application. Polyglot. Different services can be built with different programming languages and databases, choosing the best tool for the job.
Fault Tolerance Low. An unhandled error in any component can bring down the entire application (single point of failure). High (if designed well). Failure in one service can be isolated and won't necessarily cascade to the rest of the system.
Operational Overhead Low. One application to deploy and monitor. High. Managing a distributed system of many services is complex, requiring sophisticated monitoring, logging, and orchestration tools.

Given your experience with rapidly evolving ML models, you can likely see the appeal of microservices. If you need to update a pre-processing step or swap out an embedding model, you'd only need to redeploy the specific service responsible for that task, not the entire application.


Inter-Service Communication: The Glue for Microservices

Breaking a monolith into microservices solves the scaling and deployment problem but introduces a new one: how do these independent services communicate with each other? This communication can be broadly categorized into two patterns: synchronous and asynchronous.

This image visually contrasts synchronous communication via a REST API with asynchronous, event-driven communication using a message broker. Notice the difference in coupling and communication flow.

For a concise, interview-oriented summary of communication patterns, let's turn to a resource from CodeSignal.

Monolithic vs. Microservices Architectures

This resource is framed for interview preparation and provides clear, direct answers to common questions.

Please read the answer to the follow-up question, "How do you handle inter-service communication?". This section introduces the two main approaches we'll discuss: HTTP/REST and messaging systems.

1. Synchronous Communication: REST APIs

The most common synchronous pattern is using REST (Representational State Transfer) APIs over HTTP. In this model:

  • A client service sends a request to a server service (e.g., GET /user/123).
  • The client then blocks and waits until the server processes the request and sends back a response.

This request-response cycle is simple and familiar. However, it creates tight coupling in terms of time and availability. If the server service is down or slow, the client service is stuck waiting, which can lead to cascading failures across the system. This is suitable for queries that require an immediate answer, like fetching a user's profile before rendering a webpage.

2. Asynchronous Communication: Message Queues

To break this tight coupling, we can use asynchronous communication, typically implemented with a message broker (e.g., RabbitMQ, Apache Kafka). In this pattern:

  • A service, called a producer (or publisher), sends a message to a channel (a queue or topic) on the message broker. The producer does not wait for a response.
  • The message sits in the queue until another service, the consumer (or subscriber), is ready to pull it and process it.

This decouples the producer and consumer. The producer doesn't need to know who the consumer is, or even if it's currently online. The message broker acts as a durable buffer, increasing the system's resilience and scalability. This pattern is ideal for tasks that can be processed later, like sending a confirmation email, kicking off a batch prediction job, or updating a search index.


Case Study: From Monolith to Microservices in Fraud Prevention

Let's look at a concrete example of how these architectural choices play out in a real-world ML system. The following video describes how the payment company Payoneer transitioned from a slow, batch-oriented system to a real-time, microservices-based architecture for fraud prevention.

Productionizing Machine Learning with a Microservices Architecture

This video from Databricks presents a powerful case study demonstrating the business value of adopting a microservices architecture for an ML-driven task.

Please watch the section describing the Payoneer deployment example. As you watch, note the following: The business problem with their original architecture (40-minute delay in fraud detection). How the new architecture uses streaming and serverless functions (a form of microservices) to achieve near real-time results. The role of asynchronous processing in their new pipeline.

Payoneer's story is a classic illustration of the power of microservices and asynchronous communication. By breaking down their monolithic batch process into a series of decoupled services communicating via streams (a type of message queue), they reduced their fraud detection time from 40 minutes to just 12 seconds—a massive improvement that directly translates to business value.


A Framework for Choosing

So, how do you decide which path to take in a system design interview or a real-world project? It's about asking the right questions and understanding the trade-offs. The "IT k Funde" video provides an excellent decision-making framework.

Most Tricky Interview - How do you choose between Monolith vs Microservices architecture?

This final clip provides a structured, question-based framework to guide your decision-making process between monolith and microservices.

Watch the section on the decision tree matrix. Focus on the key probing questions related to: Business Domain Complexity: Is it a simple app or a complex, multi-faceted system? Scaling Patterns: Does the whole app need to scale together, or do different parts have different loads? Data Consistency: Does the system require immediate, strong consistency (like a bank transaction) or can it tolerate eventual consistency? Release Frequency & Team Structure: How often do you need to deploy, and how is your engineering team organized? DevOps Maturity: Does the team have the skills and tools to manage a complex distributed system?

This framework is an invaluable tool for your system design toolkit. Starting with a monolith is often a pragmatic choice for new products or small teams ("monolith-first"). As the application grows in complexity and scale, and the team's DevOps maturity increases, you can strategically break it apart into microservices, a process known as "strangling the monolith."


Conclusion

In this lesson, we dissected the two primary architectural patterns for modern applications. You've seen how the choice between a unified monolith and distributed microservices involves a fundamental trade-off between simplicity and scalability.

Here are the key takeaways:

  • Monolithic architecture is a single, unified unit. It's simple to start with but becomes brittle, risky to deploy, and hard to scale as it grows.
  • Microservices architecture breaks an application into small, independent services. This provides granular scalability, technological flexibility, and fault isolation but introduces the complexity of a distributed system.
  • Services communicate either synchronously (e.g., via REST APIs), which is simple but creates tight coupling, or asynchronously (e.g., via message queues), which decouples services and improves resilience at the cost of added complexity.
  • The right choice is not absolute; it depends on a pragmatic evaluation of your project's domain complexity, scaling needs, data consistency requirements, and team maturity.

We have now discussed how to scale compute resources and how to organize application logic. But what about the data? In our next lesson, we will focus on the foundational data layer, exploring how to select appropriate storage solutions by comparing SQL, NoSQL, and caching strategies for the unique access patterns found in machine learning systems.