Skip to main content

ADR-007: Use Modular Monolith

Status

Accepted

Date

2026-07-22

Context

Chilarai is being built as a multi-tenant school management platform.

The platform includes multiple business capabilities:

  • Identity and Membership
  • School Administration
  • Academic Management
  • Admissions
  • Student Management
  • Staff Management
  • Attendance
  • Fees
  • Assessments

These domains are related and frequently interact with each other.

The engineering team is currently small, and the platform is in the early product development stage.

A backend architecture decision is required before implementation begins.


Decision Drivers

The selected backend architecture should:

  • Support rapid product development
  • Keep operational complexity low
  • Support clear domain boundaries
  • Allow future scalability
  • Avoid premature distributed systems complexity
  • Support transactional consistency
  • Be easy to test, debug, and deploy
  • Work well with NestJS
  • Allow future extraction of services if needed

Alternatives Considered

Option 1: Microservices

Microservices would split the backend into independently deployed services.

Possible services:

  • Identity Service
  • School Service
  • Student Service
  • Attendance Service
  • Fee Service
  • Assessment Service

Advantages

  • Independent deployment
  • Independent scaling
  • Strong service ownership
  • Technology flexibility per service

Disadvantages

  • Higher operational complexity
  • Distributed transactions
  • More deployment pipelines
  • More monitoring and observability requirements
  • More infrastructure requirements
  • Harder local development
  • More complex debugging
  • More coordination across service boundaries
  • Premature for the current team size and product stage

Assessment

Microservices may become useful in the future if specific domains need independent scaling, ownership, or deployment.

At the current stage, microservices introduce more complexity than value.


Option 2: Traditional Monolith

A traditional monolith would place all backend logic in a single application without strict internal modular boundaries.

Advantages

  • Simple deployment
  • Simple development
  • Easy transactions
  • Easy debugging

Disadvantages

  • Risk of poor separation of concerns
  • Domain logic can become tightly coupled
  • Harder to maintain as the system grows
  • Can become difficult to split later

Assessment

A traditional monolith is simple initially but can become difficult to maintain if module boundaries are not enforced.


Option 3: Modular Monolith

A modular monolith keeps the backend as a single deployable application while organizing the codebase into clear domain modules.

Possible modules:

  • Identity
  • School
  • Academic
  • Student
  • Admission
  • Staff
  • Attendance
  • Fees
  • Assessment

Each module owns its domain logic and exposes well-defined application services.

Advantages

  • Single deployment unit
  • Lower operational complexity
  • Clear domain boundaries
  • Easier local development
  • Easier debugging
  • Easier database transactions
  • Good fit for a small team
  • Can evolve into microservices later if needed

Disadvantages

  • Requires discipline to maintain module boundaries
  • Poor module design can still lead to tight coupling
  • Scaling is initially at the application level
  • Independent deployment per domain is not available initially

Assessment

A modular monolith provides the best balance between simplicity and maintainability for Chilarai.

It supports rapid development while preserving internal structure.


Decision

Use a Modular Monolith for the Chilarai backend.

The backend will be implemented as a single NestJS application with clear domain-oriented modules.


Rationale

Microservices are not appropriate for the current stage of Chilarai.

The platform is early, the team is small, and rapid iteration is more important than independent service deployment.

A modular monolith provides clear boundaries without introducing distributed systems complexity.

This approach allows the team to:

  • Build quickly
  • Keep deployment simple
  • Maintain transactional consistency
  • Share common infrastructure
  • Organize code by domain
  • Evolve toward services later if needed

The modular monolith should be designed with disciplined module boundaries so that future extraction of services remains possible.


Backend Module Direction

The initial backend modules should align with the domain model.

Recommended modules:

  • Identity Module
  • School Module
  • Academic Module
  • Student Module
  • Admission Module
  • Staff Module
  • Attendance Module
  • Fees Module
  • Assessment Module

Each module should own its domain logic.

Cross-module access should happen through explicit services or application interfaces.


Architectural Rules

Module Ownership

Each module owns its domain behavior.

Examples:

  • Student Module owns Student and Student Enrollment behavior.
  • Fees Module owns Fee Structure, Student Fee, Payments, Charges, and Adjustments.
  • Attendance Module owns Attendance Events and Attendance records.

No Direct Cross-Module Data Mutation

One module should not directly modify another module's internal data without going through the owning module's service.


Shared Code

Shared code should be limited to reusable infrastructure and contracts.

Examples:

  • Shared Types
  • Shared Validation
  • Common Guards
  • Common Exceptions
  • Common Utilities

Shared code must not become a dumping ground for domain logic.


Events

Events may be introduced for asynchronous workflows.

Examples:

  • Admission Converted
  • Student Enrollment Created
  • Payment Recorded
  • Attendance Event Received

Events should be used when the workflow does not require immediate synchronous completion.


Database

The modular monolith will use a shared PostgreSQL database.

Domain boundaries should still be respected in application code.


Consequences

Positive Consequences

  • Simpler deployment
  • Faster development
  • Easier local setup
  • Easier debugging
  • Easier transaction handling
  • Lower infrastructure overhead
  • Clear domain-oriented structure
  • Future service extraction remains possible

Negative Consequences

  • Requires discipline to maintain module boundaries
  • Scaling is initially application-wide
  • Independent deployment by domain is not available initially
  • Poor implementation can degrade into a traditional monolith

Future Evolution

If a domain later requires independent scaling or ownership, it may be extracted into a separate service.

Possible future extraction candidates:

  • Attendance Event Processing
  • Notifications
  • Payments
  • Reporting
  • Integrations

Such extraction should be driven by concrete operational need, not by upfront architectural preference.


Follow-up Actions

  • Define Backend Architecture document.
  • Define module boundaries.
  • Define NestJS folder structure.
  • Define service interaction rules.
  • Define event usage guidelines.
  • Define shared module guidelines.
  • Define database access pattern.

  • ADR-001: Use Nx Monorepo
  • ADR-003: Use NestJS
  • ADR-004: Use PostgreSQL
  • ADR-006: Use Keycloak

Summary

Core decisions:

  • Chilarai backend will use a modular monolith.
  • Microservices are deferred.
  • Backend will be a single NestJS deployable application.
  • Domains will be implemented as explicit modules.
  • Module boundaries must be respected.
  • Shared PostgreSQL database will be used.
  • Events may be used for asynchronous workflows.
  • Services may be extracted later if real scaling or ownership needs emerge.