Microservices Re-Explained 101 : Understanding Microservices for What They Really Are
Without Losing Your Mind Series: Microservices Re-Explained
Because architecture is about clarity, not fashion.
1. The Real Reason Microservices Exist
Most teams don’t start with microservices. They migrate because their monolith stopped scaling — organizationally or technically.
It always begins the same way:
A single codebase.
Fast deployments.
A tight feedback loop.
Then, as your product grows:
A UI change breaks billing logic.
Builds take 30 minutes.
Scaling one API endpoint means scaling everything.
Deployment day becomes an all-hands event.
That’s when the word microservices starts floating around.
But let’s be clear: microservices aren’t a trend. They’re a response to structural friction — when your system and your teams have both outgrown their original shape.
2. Monolith vs. Microservices — The Real Trade-Off
A monolith is one deployable unit containing everything — user logic, business rules, background jobs, and database access.
A microservice architecture breaks that single artifact into multiple deployable services that communicate via APIs or events.
Monoliths are great when:
Your team is small and co-located.
You need to ship features fast.
You rely on shared data and transactions.
They start breaking down when:
Multiple teams modify the same codebase.
Release velocity slows.
Scaling one component means scaling all.
Microservices shine when:
Teams need autonomy.
Domains are well understood.
Independent scaling and deployment matter.
Think of it like this:
A monolith is a single fortress — secure, simple, but rigid.
Microservices are a city — distributed, scalable, but chaotic if unplanned.
3. Core Design Principles
3.1 Bounded Context — Define Your Borders
A bounded context defines what a service owns — its models, logic, and vocabulary.
For example, “Order” in the OrderService isn’t the same as “Order” in the BillingService. Each service defines its own meaning.
Once multiple services share a database or data model, boundaries collapse. You’ve built a distributed monolith.
Each service must own:
Its data schema
Its rules and logic
Its public contract (API or events)
Bounded context ensures autonomy — the foundation of scalability.
3.2 Loose Coupling — Integrate by Contract, Not Code
Loose coupling allows services to change independently.
You know you’re tightly coupled when:
Services must deploy together.
You share internal classes or libraries.
A small change in one service breaks another.
Instead, design for independence:
Communicate through APIs or events, not shared code.
Version your APIs.
Use backward-compatible schemas.
Prefer asynchronous communication where possible.
Coupling is inevitable — but control its direction. Services should depend only on stable, explicit contracts.
3.3 Independent Scalability — Scale by Behavior
Microservices let you scale per domain, not per codebase.
Different services have different load patterns:
Authentication spikes during logins → horizontal auto-scaling.
Analytics runs heavy computations → async workers.
Notifications are I/O bound → queue-based worker pools.
Orders stay steady → simple load-balanced replicas.
In a monolith, scaling one means scaling all.
In microservices, each scales as business behavior demands.
4. The Big Question — How Many Microservices Should You Create?
Knowing “microservices are good” isn’t enough. The real challenge is:
How many? And where do we draw the lines?
Here’s the pragmatic way to think about it.
4.1 Start from Business Capabilities
Your first decomposition should mirror business domains, not technical layers.
If your organization has teams for Orders, Payments, and Users — that’s your natural segmentation.
Architecture should follow accountability:
One team. One domain. One service.
Start coarse-grained. Don’t over-split in the beginning.
4.2 Validate Boundaries with Three Questions
Before splitting, ask:
Can this part deploy on its own?
Can it change without breaking others?
Is there one clear owner?
If the answer to any of these is “no,” it’s not ready to be its own service yet.
4.3 Watch Communication Density
High inter-service chatter means bad decomposition.
If OrderService must call InventoryService for every single item in a cart, they belong together — at least for now.
As a rule:
High-frequency, low-complexity communication → keep together.
Low-frequency, high-impact communication → separate.
4.4 Let Scalability and Release Cadence Drive Splits
Split services when they need to:
Scale differently.
Deploy on different schedules.
Be owned by different teams.
Don’t split for aesthetics — split for autonomy.
4.5 Iterate Intelligently
No one gets the boundaries right the first time.
Start with 3–5 major services.
Monitor communication, scaling, and ownership patterns.
Split only when necessary.
When you reach 10–12 well-bounded services with mature pipelines and observability, you’ve likely found your architectural rhythm.
4.6 A Real-World Example
A fintech startup began with a monolith. Within six months, growth forced their hand.
They started by extracting four clear domains:
User Service — authentication, profiles, sessions
Payment Service — transaction processing
Notification Service — async email and SMS
Audit Service — compliance logs
That’s it. Four clean, purposeful services.
No over-splitting, no theoretical design — just pragmatic evolution driven by pain.
5. Building Your First Microservice
Start with something simple but self-contained, like Notifications or Audit Logging.
Avoid core transactional flows (e.g., Orders, Payments) until your tooling and team maturity improve.
5.1 Define the Contract First
Design the API before touching code. That contract is your integration boundary.
Example:
Notification Service (simplified API)
POST /notifications
Request: { “userId”: “123”, “message”: “Your order has shipped” }
Response: 202 Accepted
This contract is stable and language-agnostic. The implementation behind it can change freely.
5.2 Implement the Service
Example (Node.js + Express):
import express from ‘express’;
const app = express();
app.use(express.json());
app.post(’/notifications’, (req, res) => {
const { userId, message } = req.body;
console.log(`Notification sent to ${userId}: ${message}`);
res.status(202).json({ status: ‘accepted’ });
});
app.listen(3000, () => console.log(’Notification service running on port 3000’));
Each service should live in its own repo, build pipeline, and deployment lifecycle.
5.3 Folder Structure
A simple structure aligned with hexagonal principles:
/notification-service
├── src/
│ ├── api/
│ ├── domain/
│ ├── infrastructure/
│ └── app.js
├── tests/
├── Dockerfile
├── package.json
└── README.md
Keep boundaries explicit between business logic, infrastructure, and I/O.
5.4 CI/CD Best Practices
Automate everything.
A minimal workflow includes:
Run linting and unit tests.
Build and tag Docker image.
Deploy using CI/CD (GitHub Actions, GitLab CI, or ArgoCD).
Health check and auto-rollback on failure.
Automation builds trust. Manual deployment kills confidence.
6. Testing Microservices — A Layered Approach
Testing must evolve with architecture. You’re not testing one app anymore; you’re testing a system of contracts.
Types of tests you need:
Unit Tests: For internal logic.
Integration Tests: For database or external API calls.
Contract Tests: To validate request/response compatibility.
Consumer-Driven Contracts: To ensure your service doesn’t break others.
End-to-End Tests: To verify entire business flows.
Start small: contract tests give you 80% of confidence with 20% of effort.
7. Observability — The Non-Negotiable Layer
Microservices without observability are just distributed confusion.
Establish visibility early:
Centralized logging (ELK, Loki, Datadog)
Metrics (Prometheus + Grafana)
Tracing (OpenTelemetry + Jaeger)
Service discovery (Consul, Kubernetes DNS)
If you can’t trace a single request end-to-end, you’re flying blind.
8. Common Anti-Patterns
Avoid these traps early:
Shared Database: One schema for multiple services — kills autonomy.
Too Many Services: Over-engineering kills velocity.
Shared Libraries for Business Logic: Forces synchronized deployments.
Synchronous Chains: When Service A → B → C for every request, you’ve built latency, not scalability.
No Observability: You can’t debug what you can’t see.
Microservices fail not because of the tools — but because of poor discipline and misplaced boundaries.
9. Closing Thoughts — Architecture Mirrors Organization
Conway’s Law always wins:
Your architecture will reflect how your teams communicate.
If your teams aren’t autonomous, your services won’t be either.
Microservices are not a destination — they’re an evolution.
Start small, automate relentlessly, and refine based on real friction.
Good architecture grows with the organization, not ahead of it.



Great insight👍🏻