Here is an architecture diagram that turns up more often than it should. Fourteen services, a message broker, a Kubernetes cluster, and a team of four developers. The product has not launched yet. Six months later the team spends more time debugging calls between services than building features. Microservices are a good answer to a specific set of problems, mostly problems of large organisations. If you do not have those problems yet, you pay the costs without getting the benefits.
What Each One Means in Practice
A monolith is one application, deployed as one unit, usually with one database. A Laravel or Django app with modules for orders, inventory, and billing is a monolith. Microservices split those modules into separate applications. Each one has its own codebase, its own deployment, ideally its own database, and they talk over the network through HTTP, gRPC, or a message queue.
The appeal is easy to understand. Teams can deploy independently, a slow part can scale on its own, and a failure in one service does not have to take down everything. All true. The question is what you pay for it.
The Bill Nobody Shows in the Diagram
- Network failures. A function call inside a monolith does not time out. A call to another service can, so every call needs timeouts, retries, and idempotent handling so a retried payment is not charged twice.
- Data consistency. An order and its stock deduction used to be one database transaction. Across two services you need patterns such as the outbox or a saga, and you have to accept that data is briefly out of sync.
- Observability. When a request touches five services, logs alone are not enough. You need distributed tracing with something like OpenTelemetry, central logging, and metrics per service.
- Operations. Each service needs its own CI/CD pipeline, configuration, secrets, health checks, and on-call ownership. In practice this usually means Kubernetes and someone who knows it well.
- Local development. Running the whole system on a laptop gets hard. Testing a change end to end often needs a shared staging environment, which becomes a queue.
For a small team, this overhead can eat a third of the engineering time. Even large companies revisit the choice. In 2023 the Amazon Prime Video team wrote about moving one monitoring system from distributed components back into a single process and cutting its infrastructure cost by about 90 percent.
The Modular Monolith as the Default
For most companies we work with, the right starting point is a modular monolith. It is one deployable application, but the code is split into modules with clear boundaries. Orders, payments, and inventory each live in their own folder or package, expose a small public interface, and do not reach into each other's tables. Shopify famously runs one of the largest Rails codebases in the world this way.
You get most of the design benefit of microservices, which is clean boundaries and code that a team can own, without the network in between. And if one module later needs to become a service, the boundary is already there. Extracting a well-separated module takes weeks. Untangling a messy monolith takes months.
| Aspect | Modular monolith | Microservices |
|---|---|---|
| Deployment | One pipeline, one release | One pipeline per service, independent releases |
| Data consistency | Database transactions | Eventual consistency, outbox, sagas |
| Debugging | One log stream, one stack trace | Distributed tracing across services |
| Infrastructure | A few servers or containers | Usually Kubernetes, service discovery, a message broker |
| Scaling | Scale the whole app, which is fine for most loads | Scale each service on its own |
| Team fit | One to about five teams | Many teams that need to ship independently |
| Typical running cost | Lower | Often two to three times higher in infrastructure and ops time |
Signs You Actually Need to Split
- Team size. Once you have several teams, say 30 to 50 engineers or more, and they keep blocking each other on the same release, separate deployments start paying off.
- Very different scaling needs. Image processing, search indexing, or a notification sender that needs ten times the resources of the rest of the app is a good candidate.
- Different release cadence. A pricing engine that changes daily next to a billing core that changes quarterly and needs heavy review.
- Isolation requirements. A component that handles card data or other regulated data, where keeping it separate shrinks the audit scope.
- A different technology fits better. For example, a Go service for a high-throughput gateway next to a PHP business app.
Note what is missing from that list. Wanting to use modern technology, expecting to be big one day, and a monolith that is messy are all poor reasons. A messy monolith split into services becomes a messy distributed system, which is worse.
How to Extract a Service Safely
The approach that works is the strangler fig pattern, named by Martin Fowler. You do not rewrite the system. You route one piece of functionality at a time to a new service while the old code keeps running, and you remove the old code once the new path has proven itself.
- 1Pick one module with a clear boundary and a real reason to move, such as notifications or reporting. Avoid the core order flow as your first attempt.
- 2Clean up the boundary inside the monolith first. All access goes through one interface, and no other module reads its tables directly.
- 3Build the new service behind that same interface, with tracing, monitoring, and alerts from day one.
- 4Send a small share of traffic to the new service, compare results with the old path, then increase step by step.
- 5Move the data so the service owns it, and remove the old code and tables once nothing depends on them.
Data Ownership Is the Real Boundary
Each service should own its data. Other services ask through an API or listen to events. They do not query its database. Several services sharing one database with writes from all sides is the worst of both worlds: you have the network overhead of microservices and the coupling of a monolith. If two services always need to change the same tables together, they are probably one service.
Architecture Follows the Org Chart
Conway's law says that systems end up mirroring the communication structure of the organisation that builds them. It cuts both ways. Ten services owned by one team of five means every developer has to hold the whole distributed system in their head. One giant monolith shared by eight teams means constant merge conflicts and release coordination. Draw the team boundaries first, then let the service boundaries follow.
Start with a monolith you can split, and split it when a team, not a diagram, asks for it.
At Salamun Teknologi we design and build backends for growing companies, from modular monoliths in Laravel or Go to services on Kubernetes when the load and the team size call for it. If you are weighing a split or a rewrite, we can review your current system with you before any code changes.
Key takeaways
- Microservices solve problems of team scale and independent deployment, and they bring network failures, data consistency work, and heavier operations with them.
- A modular monolith with clear module boundaries is the right default for most companies and makes a later split much cheaper.
- Split when teams block each other, a part needs very different scaling or release cadence, or regulated data needs isolation.
- Extract services one at a time with the strangler fig pattern, starting from a module with a clear boundary.
- Each service must own its data, and service boundaries should follow team boundaries.


