Skip to main content

Microservices Foundations

Before writing code, we must understand why we are building microservices.

1. Monolith vs Microservices

The Monolith

A single deployment unit containing all business logic, UI, and data access. Pros:
  • Simple to develop, test, and deploy (initially).
  • No network latency between calls.
  • Easy to debug (single stack trace).
Cons:
  • Scaling: Can only scale the whole app (X-axis scaling), not specific bottlenecks.
  • Technology Lock-in: Hard to switch languages/frameworks.
  • Complexity: Over time, “Big Ball of Mud” anti-pattern emerges.
  • Reliability: If one module causes an OOM error, the whole system crashes.

Microservices

Architectural style where an application is a collection of loosely coupled services. Pros:
  • Technological Freedom: Each service can use the best tool for the job.
  • Independent Deployment: Deploy User Service without redeploying Order Service.
  • Fault Isolation: If Payment Service fails, users can still browse products.
  • Scaling: Scale only the popular services independently.
Cons:
  • Distributed Complexity: Network failures, latency, consistency issues (CAP Theorem).
  • Operational Overhead: Need sophisticated monitoring, logging, and deployment pipelines.

2. Domain Driven Design (DDD) Basics

DDD is crucial for identifying service boundaries. A microservice should correspond to a Bounded Context.
  • Ubiquitous Language: Common language shared by developers and domain experts.
  • Bounded Context: A boundary within which a particular domain model is defined and applicable.
    • Example: “User” in the Sales Context might refer to a customer with credit limits. “User” in the Support Context might refer to a ticket opener. These should be different Microservices.

3. Communication Patterns

Services need to talk to each other.

Synchronous (Request/Response)

The client waits for a response.
  • HTTP/REST: Standard, easy to debug over JSON.
  • gRPC: High performance, strictly typed (Protobuf).
Drawback: Coupling. If Service B is down, Service A might fail (Cascading Failure).

Asynchronous (Event-Driven)

The client sends a message and forgets.
  • Message Queues: RabbitMQ, ActiveMQ.
  • Event Streaming: Apache Kafka.
Advantage: Decoupling. If Service B is down, the message stays in the queue until B recovers.

4. The 12-Factor App

A methodology for building cloud-native apps:
  1. Codebase: One codebase tracked in revision control, many deploys.
  2. Dependencies: Explicitly declare and isolate dependencies.
  3. Config: Store config in the environment.
  4. Backing services: Treat backing services as attached resources.
  5. Build, release, run: Strictly separate build and run stages.
  6. Processes: Execute the app as one or more stateless processes.
  7. Port binding: Export services via port binding.
  8. Concurrency: Scale out via the process model.
  9. Disposability: Maximize robustness with fast startup and graceful shutdown.
  10. Dev/prod parity: Keep development, staging, and production as similar as possible.
  11. Logs: Treat logs as event streams.
  12. Admin processes: Run admin/management tasks as one-off processes.

5. CAP Theorem (The Trade-offs)

In any distributed data store, you can only provide two of the following three guarantees:
  1. Consistency (C): Every read receives the most recent write or an error.
  2. Availability (A): Every request receives a (non-error) response, without the guarantee that it contains the most recent write.
  3. Partition Tolerance (P): The system continues to operate despite an arbitrary number of messages being dropped/delayed by the network.
Reality: Network Partitions (P) will happen. So you have to choose between CP (Consistency) and AP (Availability).
  • CP (Banking): If the network breaks, stop accepting updates until it’s fixed. Don’t show wrong balance.
  • AP (Social Media): If the network breaks, show the old feed. It’s better than showing an error.