Microservices architecture divides a large application into independently deployable services. Each service is responsible for a well-defined business capability and communicates with other services through explicit interfaces.
The goal is not simply to create more services. The goal is to create boundaries that allow systems to evolve independently.
A monolithic application is often easier to start because everything exists inside one deployable unit.
As the system grows, however, different areas may develop different scaling requirements.
For example, an application might have:
These workloads do not necessarily need identical infrastructure.
A file-processing service may require significant CPU resources while authentication may remain relatively lightweight.
Poorly chosen service boundaries can make microservices worse than a monolith.
A service should ideally own a coherent business capability rather than simply representing a database table.
For example, creating separate services for users, user-addresses, and user-preferences may introduce unnecessary network communication.
A better boundary might be an Identity service responsible for the complete identity domain.
Node.js is well suited to many network-oriented microservices because its event-driven architecture handles large numbers of concurrent I/O operations efficiently.
It works particularly well for:
CPU-heavy workloads may require worker processes or separate services written in technologies better suited to computational workloads.
Docker packages an application together with its runtime dependencies.
A Node.js service can be represented as a container containing:
This makes deployment more predictable between development and production.
Docker Compose is particularly useful during development.
A local environment might contain:
All services can be started using one configuration.
Services can communicate through synchronous APIs or asynchronous messaging.
HTTP and gRPC are common choices for synchronous communication.
Message brokers are useful when services should communicate asynchronously.
For example, an order service could publish an OrderCreated event. A notification service could consume that event without requiring the order service to directly call the notification system.
Distributed systems fail differently from monolithic systems.
A network request can timeout. A service can restart. A message can be delivered more than once.
Therefore microservices require:
Retries should be designed carefully because blindly retrying failed requests can amplify outages.
A common microservices principle is that services should own their data.
Directly accessing another service's database creates tight coupling.
If the Payments service owns payment data, other services should communicate with Payments through its API or events rather than directly querying its tables.
Microservices are therefore primarily an architectural discipline rather than a Docker configuration. Containers make deployment easier, but good service boundaries, communication patterns, observability, and failure handling determine whether the architecture succeeds.