Chapter 8: Microservices
Microservices architecture enables you to build scalable, distributed systems. NestJS provides first-class support for microservices, message brokers, and event-driven design. This chapter covers TCP, Redis, RabbitMQ, message patterns, distributed tracing, and real-world deployment tips. We’ll walk through practical examples and explain how to design robust microservices.
8.1 What is a Microservice?
A microservice is a small, independent process that communicates with others over a network. Each service is responsible for a specific business capability and can be deployed, scaled, and updated independently.Microservices vs Monolith
Monolithic Architecture:- Single codebase
- Deploy as one unit
- Scale entire application
- Shared database
- Multiple independent services
- Deploy services independently
- Scale individual services
- Each service has its own database
Imagine a city’s postal system. Each microservice is a different office (tax office, passport office, vehicle registration). Each office has its own building, its own staff, and its own filing system (database). They communicate by sending letters (messages) through the postal service (message broker). If the tax office burns down, the passport office keeps working. You can hire more staff at the tax office during tax season without expanding the passport office. But managing a city full of independent offices is inherently more complex than managing one big building — you need reliable mail delivery, consistent address formats, and a way to track letters across offices. That is the fundamental trade-off of microservices: operational complexity in exchange for independent scalability and resilience.
Benefits
Scalability:- Scale only what you need
- Independent scaling per service
- Optimize resources
- One service can fail without crashing the whole system
- Isolated failures
- Better resilience
- Use the best tool for each job
- Different languages/frameworks per service
- Technology flexibility
- Deploy and update services separately
- Faster release cycles
- Reduced risk
- Teams can work independently
- Clear ownership
- Faster development
Challenges
These are not hypothetical downsides — they are the daily reality of running microservices in production. Do not adopt microservices unless these challenges are worth the benefits for your specific situation.- Complexity: More moving parts means more things to deploy, monitor, version, and debug. A monolith with 10 modules becomes 10 services with 10 deployment pipelines, 10 health checks, and 10 log streams.
- Network Latency: Every service-to-service call crosses the network. What was a 1ms in-process function call becomes a 5-50ms network round trip. Chained calls compound this — Service A calls B calls C means 3x latency.
- Data Consistency: Without distributed transactions (which are hard), you rely on eventual consistency and saga patterns. This means your system may temporarily show stale or inconsistent data.
- Testing: You cannot just run
npm test— you need the entire ecosystem running (or good contract tests). E2E testing across services requires orchestration tools like Docker Compose. - Monitoring: A single request may touch 5 services. Without distributed tracing (OpenTelemetry, Jaeger), debugging production issues is like finding a needle in a haystack.
8.2 Microservices in NestJS
NestJS supports multiple transport layers for microservices, each with different characteristics.Supported Transports
TCP:- Default, simple and fast
- Direct service-to-service communication
- Low latency
- Good for internal services
- Pub/sub messaging
- Caching support
- Simple setup
- Good for event-driven systems
- Lightweight messaging
- High performance
- Simple protocol
- Good for cloud-native apps
- Robust message broker
- Advanced routing
- Reliable delivery
- Good for complex workflows
- IoT messaging
- Lightweight protocol
- Good for IoT applications
- High-performance RPC
- Type-safe
- HTTP/2 based
- Good for inter-service communication
- Distributed streaming
- High throughput
- Event sourcing support
- Good for big data
Transport Layer Comparison
| Transport | Latency | Throughput | Persistence | Delivery Guarantee | Complexity | Best For |
|---|---|---|---|---|---|---|
| TCP | Very low (~1ms) | High | None | At-most-once | Low | Internal service-to-service calls |
| Redis | Low (~2-5ms) | High | Optional (Streams) | At-most-once (pub/sub) | Low | Event broadcasting, simple pub/sub |
| NATS | Very low (~1ms) | Very high | Optional (JetStream) | Configurable | Low | Cloud-native, high-frequency events |
| RabbitMQ | Low (~5-10ms) | High | Yes (durable queues) | At-least-once | Medium | Reliable task queues, complex routing |
| Kafka | Medium (~10-50ms) | Very high | Yes (log-based) | At-least-once | High | Event streaming, big data pipelines |
| gRPC | Very low (~1ms) | Very high | None | At-most-once | Medium | High-performance RPC, polyglot systems |
| MQTT | Low | Moderate | Configurable | Configurable (QoS) | Low | IoT devices, low-bandwidth clients |
| Factor | Stay Monolith | Consider Microservices |
|---|---|---|
| Team Size | 1-5 developers | 10+ developers in independent teams |
| Deployment Frequency | Weekly or less | Multiple deploys per day by different teams |
| Scaling Needs | Uniform load across features | One feature has 100x the load of others |
| Fault Isolation | Acceptable if whole app goes down briefly | Individual feature failure must not cascade |
| Data Complexity | Shared database is manageable | Features need independent data stores |
| Operational Maturity | No Kubernetes, no distributed tracing | Strong DevOps, observability in place |
8.3 Creating a TCP Microservice
TCP is the simplest transport for microservices. Let’s create a basic TCP microservice.Installation
Microservice Bootstrap
Message Pattern Handler
Message patterns are the microservice equivalent of HTTP routes. Instead of@Get('/users/:id'), you use @MessagePattern({ cmd: 'get_user' }). The pattern object is matched exactly — think of it as a topic or address for the message.
@MessagePattern is for request-response (caller waits for a reply). @EventPattern is for fire-and-forget (caller does not wait). Choosing the wrong one is a common mistake — if you use @MessagePattern but the caller uses .emit(), the handler will never fire.
Client Service
Hybrid Application
Run both HTTP and microservice in the same app:8.4 Message Patterns
Message patterns define how microservices communicate. Use consistent patterns for maintainability.Request-Response Pattern
Event Pattern (Fire and Forget)
Multiple Patterns
8.5 Using RabbitMQ
RabbitMQ is a popular message broker for event-driven systems. It enables publish/subscribe and queue-based communication.Installation
RabbitMQ Microservice
RabbitMQ Client
8.6 Using Redis
Redis provides pub/sub messaging and can be used as a transport layer.Redis Microservice
Redis Client
8.7 Event-Driven Design
Event-driven architecture decouples services and enables scalability.Principles
Decoupling:- Services don’t need to know about each other
- Communicate via events
- Loose coupling
- Publish/subscribe patterns
- Multiple consumers
- Horizontal scaling
- Message persistence
- Retries and dead-letter queues
- Event replay
Event Sourcing Integration
Correlation IDs
Track requests across services:Saga Pattern for Distributed Transactions
When an operation spans multiple services (e.g., “create order, charge payment, reserve inventory”), you cannot use a database transaction because each service has its own database. The Saga pattern coordinates these steps with compensation logic for failures.| Saga Style | Coordination | Complexity | Best For |
|---|---|---|---|
| Choreography | Decentralized (events) | Lower initially, harder to debug | Simple flows (2-3 steps) |
| Orchestration | Centralized (coordinator) | Higher initially, easier to debug | Complex flows (4+ steps) |
8.8 Distributed Tracing & Monitoring
Monitor and trace requests across microservices.OpenTelemetry Integration
Logging with Correlation IDs
8.9 Error Handling
Handle errors gracefully in microservices.Error Handling Pattern
Retry Logic
In distributed systems, transient failures (network blips, temporary service unavailability) are normal, not exceptional. Retrying with backoff is the standard response — but be careful about retrying non-idempotent operations. Retrying a “charge credit card” command without idempotency keys will double-charge your customer.opossum integrate well with NestJS.
Error Handling Decision Framework for Microservices
| Error Type | Retry? | Compensation? | Example |
|---|---|---|---|
| Transient (network timeout, temporary unavailability) | Yes, with exponential backoff | No | Connection refused, 503 from downstream |
| Permanent (invalid data, business rule violation) | No (retrying will never succeed) | No | Invalid order total, user not found |
| Partial failure (one step in a saga fails) | Depends on the failed step | Yes (undo completed steps) | Payment succeeded but inventory reservation failed |
| Poison message (message that always fails processing) | No (move to dead-letter queue) | Depends | Malformed JSON, missing required field |
8.10 Best Practices
Following best practices ensures your microservices are robust and maintainable.Service Design
- Keep services small and focused (single responsibility)
- Define clear service boundaries
- Use domain-driven design
- Document service contracts
Communication
- Use message brokers for async communication
- Use request-response for synchronous needs
- Implement circuit breakers
- Handle timeouts gracefully
Data Management
- Each service owns its data
- Avoid shared databases
- Use event sourcing for consistency
- Implement saga pattern for distributed transactions
Security
- Secure communication between services (TLS)
- Authenticate service-to-service calls
- Use API keys or mTLS
- Validate all inputs
Deployment
- Containerize services (Docker)
- Use orchestration (Kubernetes)
- Implement health checks
- Enable auto-scaling
Monitoring
- Log all important events
- Use distributed tracing
- Monitor latency and errors
- Set up alerts
Documentation
- Document message patterns
- Document service APIs
- Maintain service registry
- Keep architecture diagrams updated
8.11 Summary
You’ve learned how to build and scale microservices with NestJS: Key Concepts:- Microservices: Independent, scalable services
- Transport Layers: TCP, Redis, RabbitMQ, NATS, gRPC
- Message Patterns: Request-response and event patterns
- Event-Driven: Decoupled, scalable architecture
- Distributed Tracing: Monitor requests across services
- Keep services small and focused
- Use message brokers for communication
- Handle errors gracefully
- Secure service communication
- Monitor and trace requests
- Document service contracts