Anti-patterns that undo the whole exercise

Microservices · lesson 33 of 33 · 6 min read

The six mistakes that turn services back into a harder-to-run monolith.

Open this lesson in the learning hub

Key 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.