ADR 0001: Adopt a modular monolith
- Status: Accepted
- Date: 2026-08-06
Context
The backend is a single Go application whose business code is grouped into large core, tenant, and lookup domains. Within core and tenant, code is organized primarily into global DTO, handler, route, service, mapper, and repository packages.
This permits unrelated capabilities to share implementation types and reach into one another's repositories. The existing deployment does not require the operational overhead or consistency trade-offs of microservices, but the code needs stronger ownership boundaries.
Decision
Ovahol will remain a modular monolith.
Business capabilities will move incrementally into modules under backend/internal/modules. Each module owns its vertical slice, including transport DTOs, handlers, routes, services, domain types, repositories, queries, mappers, events, and tests where applicable.
Executable composition will live under backend/internal/app. API, worker, and scheduler may be separate processes built from the same repository and modules.
Modules communicate through narrow in-process interfaces or versioned events. They do not import another module's internal DTO, handler, service, repository, mapper, or infrastructure packages.
Consequences
Positive
- Business ownership becomes visible in the package structure.
- Capabilities can evolve with fewer unrelated changes.
- Infrastructure can be replaced behind ports.
- Tests can be organized around business modules.
- A module may later be extracted into a service if operational evidence justifies it.
Costs
- Some small contract types may be duplicated at boundaries.
- Existing cross-domain dependencies require transitional adapters.
- Boundary enforcement and module-level testing must be added to CI.
- Database ownership must be documented even while one PostgreSQL deployment remains shared.
Alternatives considered
Preserve the current layered structure
Rejected because it does not address broad package ownership or cross-domain coupling.
Move immediately to microservices
Rejected because independent deployment, network boundaries, distributed transactions, and additional operations are not currently justified.
Create one Go module per business module
Rejected for the initial migration. One Go module keeps coordinated changes and local development simple. Package and CI rules provide the required boundaries.