Database per Service

The Rule

Each service owns its data. Only product-service reads and writes the products table. If order-service needs product data, it asks product-service through its API (or keeps its own copy from events). It never connects to product_db.

order-service                       product-service
        |                                    |
        |   GET /api/products/1   ------>    |
        |   <------ {"id":1,"price":1499}    |
        |                                    |
    order_db                            product_db
  (orders, order_items)              (products, categories)

   order-service  ---X--->  product_db     NOT allowed

Why It Matters

  • Independent changes: product-service can rename a column or move to MongoDB without breaking anyone.
  • Independent scaling: the product database can get read replicas; the order database can be tuned for writes.
  • Clear ownership: one team is responsible for the data quality of each table.
  • Right tool for the job: catalogue in MySQL, search in Elasticsearch, carts in Redis.
OptionDescriptionVerdict
Database per serviceSeparate DB server or instance for each serviceBest isolation
Schema per serviceOne DB server, separate schema/user per serviceFine for starting, cheaper
Shared tablesServices read each other's tablesAvoid: creates a distributed monolith

Example: No Shared Tables

Example

Two in-process "services", each with a private store. Order service can only reach product data through the public API method, never the map itself:

import java.math.BigDecimal;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        ProductService products = new ProductService();
        OrderService orders = new OrderService(products);

        System.out.println(orders.place(1L, 2));
        System.out.println(orders.place(2L, 5));
        System.out.println(orders.place(1L, 200));
        System.out.println("Stock of product 1 now: " + products.get(1L).stock());
    }
}

record ProductDto(Long id, String name, BigDecimal price, int stock) {}

class ProductService {
    // product_db: private, nobody else can touch it
    private final Map<Long, ProductDto> productDb = new TreeMap<>(Map.of(
            1L, new ProductDto(1L, "boAt Rockerz 450", new BigDecimal("1499.00"), 120),
            2L, new ProductDto(2L, "Prestige Kettle", new BigDecimal("899.00"), 3)));

    ProductDto get(Long id) { return productDb.get(id); }

    boolean reserve(Long id, int qty) {
        ProductDto p = productDb.get(id);
        if (p == null || p.stock() < qty) return false;
        productDb.put(id, new ProductDto(p.id(), p.name(), p.price(), p.stock() - qty));
        return true;
    }
}

class OrderService {
    private final List<String> orderDb = new ArrayList<>(); // order_db
    private final ProductService productApi;                // only the API, not the map

    OrderService(ProductService productApi) { this.productApi = productApi; }

    String place(Long productId, int qty) {
        ProductDto p = productApi.get(productId);
        if (!productApi.reserve(productId, qty)) return "REJECTED: not enough stock of " + p.name();
        String order = "ORDER-" + (1001 + orderDb.size()) + " " + qty + " x " + p.name()
                + " @ Rs " + p.price();
        orderDb.add(order);
        return "CREATED: " + order;
    }
}

Output

CREATED: ORDER-1001 2 x boAt Rockerz 450 @ Rs 1499.00
REJECTED: not enough stock of Prestige Kettle
REJECTED: not enough stock of boAt Rockerz 450
Stock of product 1 now: 118

Note: No join across services means "show order history with product images" is done by the order service storing what it needs (name, price) or by calling product-service. The Hibernate & JPA material covers joins inside one service.

Practice

Exercise: The reporting team wants a daily sales report that joins orders, products and users. Each lives in a different database. Name two ways to build it without breaking "database per service".

Show answer
1. API composition: a report job calls order-service, product-service and
   user-service APIs and combines the results in code.
2. Events into a reporting store: services publish events (OrderPlaced,
   ProductUpdated, UserRegistered); a reporting service consumes them into its
   own read-only database (or a data warehouse such as BigQuery) built for
   reports.
Never let the reporting job connect directly to every service's database.