Anti-patterns that undo the whole exercise
The six mistakes that turn services back into a harder-to-run monolith.
Open this lesson in the learning hubKey points
- Shared database. The single most common failure. The schema becomes an undocumented API with no owner and no versioning, so a change by one team breaks another and releases must be coordinated again.
- Distributed monolith. Services that must be deployed together in a fixed order. All the operational cost of distribution, none of the deployment independence - usually caused by shared databases or synchronous chains.
- Chatty interfaces. One user action producing dozens of cross-service calls. Network latency dominates, and it is a strong signal the boundary is in the wrong place - things that talk constantly probably belong together.
- Nanoservices. Splitting until each service does one function. Coordination overhead grows faster than the benefit, and a change to one business rule now touches five deployments.
- Shared domain libraries. A common library of entities re-couples release schedules: a breaking change must be adopted by every service. Sharing technical utilities is fine; sharing the domain model is not.
- Entity services. A CustomerService that only does CRUD on the customer table is a database wrapper, not a business capability. It has no autonomy and every workflow becomes an orchestration across several of them.
Example
/*
* SHARED DATABASE - what it actually costs:
*
* Orders --+
* +--> [ one schema ] one ALTER breaks two services
* Billing --+ neither team owns it
* neither can deploy independently
*
* The tell: "we need to coordinate the release with the billing team".
* That sentence means you have a distributed monolith.
*/
// CHATTY - 1 + N calls for one page. Latency dominates.
public OrderView load(Long id) {
Order order = orderClient.get(id); // call 1
List<Line> lines = new ArrayList<>();
for (Long lineId : order.lineIds()) {
lines.add(lineClient.get(lineId)); // calls 2..N+1
}
Customer c = customerClient.get(order.customerId()); // call N+2
return new OrderView(order, lines, c);
}
// BETTER - one coarse call. The boundary now matches how the data is used.
public OrderView load(Long id) {
return orderClient.getWithLinesAndCustomer(id); // 1 call
}
/*
* A short self-test. Any "no" is a boundary problem, not a coding problem:
*
* Can this service be deployed without deploying another?
* Does it own its data outright?
* Can its team change its schema without asking anyone?
* Does it stay useful when its neighbours are down?
* Does it represent a business capability, not a table?
*/
If two services must be released together, you have a distributed monolith - and a shared database is almost always the reason.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Microservices course, and every lesson in it is listed on the Microservices contents page.