Modular Monolith: The Architecture Between Monolith and Microservices

Modular Monolith: The Architecture Between Monolith and Microservices

Microservices solve organizational scaling problems but introduce distributed systems complexity. A modular monolith gives you clean boundaries without the operational overhead.

What Makes It Modular

Each module owns its data, exposes a public API, and communicates with other modules only through defined interfaces. No shared database tables, no reaching into another module's internals.

// Module boundary — only this interface is public
public interface OrderModule {
    OrderDTO createOrder(CreateOrderRequest request);
    OrderDTO getOrder(String orderId);
    List<OrderDTO> getOrdersByCustomer(String customerId);
}

// Internal implementation is package-private
class OrderService implements OrderModule {
    private final OrderRepository orderRepo; // Module's own tables
    private final PaymentModule paymentModule; // Interface dependency
}

Module Communication

Use in-process events instead of direct method calls for cross-module communication. Spring's ApplicationEventPublisher works perfectly. When you eventually extract a module into a service, replace in-process events with Kafka — the code barely changes.

The Migration Path

Start monolithic. When a module needs independent scaling or a different deployment cadence, extract it. You have the boundaries already defined — extraction is mechanical, not architectural.

Scroll to Top