Monolith vs Microservices

The Monolith

A monolith is one application that contains every feature: products, cart, orders, users and payments live in one codebase, are built into one JAR, and usually share one database.

+------------------------------------------+
|            shopease.jar  (:8080)          |
|                                          |
|  ProductController   OrderController     |
|  ProductService  --> OrderService        |   method calls
|  UserService         PaymentService      |   (in memory, fast)
+--------------------+---------------------+
                     |
               shopease_db (one database, all tables)

A monolith is not bad. It is simple to build, test, debug and deploy. Most successful products started as a monolith.

Side-by-Side Comparison

MonolithMicroservices
CodebaseOne repository, one appMany small apps (often one repo each)
DeploymentDeploy everything togetherDeploy each service on its own
ScalingScale the whole appScale only the busy service
DatabaseUsually one shared databaseOne database per service
Calls between partsIn-memory method calls (nanoseconds)Network calls (milliseconds, can fail)
TransactionsSimple ACID transactionsDistributed; needs Saga/events (advance level)
TechnologyOne stackEach service can choose its own
DebuggingOne log, one stack traceNeeds tracing across services
Team size fitSmall teamsMany teams working in parallel

Example: The Cost of a Network Hop

Example

Placing one order touches the user, product, payment and order code. In a monolith those are method calls; in microservices each is a network call. This program counts the calls and adds a typical cost per call (fixed numbers, so the output is the same every run):

import java.util.*;

public class Main {
    static int calls = 0;
    static int latencyMicros = 0;

    // In a monolith, a call is a plain method call (~0.01 microseconds, we count it as 0)
    // Between services, a call goes over HTTP inside a data centre (~2 ms = 2000 microseconds)
    static void call(String from, String to, boolean network) {
        calls++;
        int cost = network ? 2000 : 0;
        latencyMicros += cost;
        System.out.printf("  %-14s -> %-16s %s%n", from, to, network ? "HTTP  +2 ms" : "method call");
    }

    static void placeOrder(boolean network) {
        calls = 0;
        latencyMicros = 0;
        call("OrderService", "UserService", network);       // is the user valid?
        call("OrderService", "ProductService", network);    // price and stock of item 1
        call("OrderService", "ProductService", network);    // price and stock of item 2
        call("OrderService", "PaymentService", network);    // charge the card
        call("OrderService", "ProductService", network);    // reduce stock
    }

    public static void main(String[] args) {
        System.out.println("Monolith:");
        placeOrder(false);
        System.out.println("  calls = " + calls + ", network time = " + latencyMicros / 1000 + " ms");

        System.out.println("Microservices:");
        placeOrder(true);
        System.out.println("  calls = " + calls + ", network time = " + latencyMicros / 1000 + " ms");
        System.out.println("Each network call can also time out or fail.");
    }
}

Output

Monolith:
  OrderService   -> UserService      method call
  OrderService   -> ProductService   method call
  OrderService   -> ProductService   method call
  OrderService   -> PaymentService   method call
  OrderService   -> ProductService   method call
  calls = 5, network time = 0 ms
Microservices:
  OrderService   -> UserService      HTTP  +2 ms
  OrderService   -> ProductService   HTTP  +2 ms
  OrderService   -> ProductService   HTTP  +2 ms
  OrderService   -> PaymentService   HTTP  +2 ms
  OrderService   -> ProductService   HTTP  +2 ms
  calls = 5, network time = 10 ms
Each network call can also time out or fail.

Tip: This is why a good split keeps things that talk to each other a lot inside the same service. A chatty design (dozens of calls per request) makes microservices slow.

Practice

Exercise: ShopEase gets 50,000 product page views per minute but only 300 orders per minute. In which architecture can you run 10 copies of the product code and only 2 copies of the order code? Why?

Show answer
Microservices. Product and Order are separate services, so each is scaled on its own:
  product-service  x 10 instances  (heavy read traffic)
  order-service    x 2 instances   (light traffic)
In a monolith you must run 10 copies of the WHOLE app, including order,
user and payment code that does not need the extra capacity.