← Back to projects//Case Study

ERP System

Modular monolith architecture for production-grade enterprise workflows.

Architecture

Modular monolith

Database model

Schema isolation

Reliability

Transactional events

GoPostgreSQLOutbox PatternSchema-per-Module
01Context

The system needed to support growing ERP domains without turning every module into an independent service. The main challenge was keeping boundaries clear while preserving deployment and operational simplicity.

02Problem

A traditional layered monolith would make feature delivery fast at first, but domain boundaries would become harder to enforce as modules started sharing tables, services, and implicit assumptions.

03Architecture
1

HTTP/API Layer

Receives requests and delegates use cases without owning domain logic.

2

Module Boundary

Each module exposes explicit interfaces and hides persistence details.

3

PostgreSQL Schemas

Separate schemas make ownership and accidental coupling easier to control.

4

Outbox Worker

Dispatches domain events reliably after the transaction commits.

04Approach
  • Model each business capability as an explicit module with its own application contracts.
  • Use PostgreSQL schema-per-module isolation so database ownership is visible and enforceable.
  • Keep module communication in-process, but only through interfaces rather than direct database access.
  • Publish cross-module events through an outbox table written in the same transaction as business data.
05Key Decisions

Choose modular monolith before microservices

The system needed strong boundaries, not distributed systems overhead. A single deployable kept operations simpler while still allowing modules to evolve independently.

Use schema-per-module instead of shared public tables

Database ownership is one of the easiest boundaries to accidentally break. Separate schemas make ownership visible during development and review.

Use outbox for cross-module events

Publishing events outside the database transaction risks losing messages. The outbox pattern keeps state changes and event intent consistent.

06Trade-offs

Less runtime isolation than microservices

A module bug can still affect the single process, but the tradeoff keeps deployment, tracing, and local development far simpler.

More discipline required in code review

The architecture only works if boundaries are actively maintained. Interface contracts and schema ownership need to be reviewed consistently.

07Lessons Learned
  • Boundaries should be visible in code and in the database, not only in diagrams.
  • A monolith can stay maintainable if module ownership is explicit from the beginning.
  • Reliable event publishing is a data consistency problem before it is an infrastructure problem.