This document refines the modular-monolith boundaries for the first backend implementation.
The goal is not to imitate microservices inside one process. The goal is to make ownership of business rules explicit so the codebase can grow without turning every feature into a cross-module dependency.
Modules may depend on another module only through an explicit application contract or stable shared abstraction. A module must not reach directly into another module’s persistence implementation or internal domain objects.
flowchart LR
IAM[Identity & Access]
BM[Buildings & Memberships]
AA[Amenities & Availability]
R[Reservations]
P[Pricing]
PAY[Payments]
ADM[Administration]
AUD[Audit]
N[Notifications]
MSG[Messaging]
REP[Reporting / Analytics]
IAM --> BM
R --> BM
R --> AA
R --> P
PAY --> R
ADM --> BM
ADM --> AA
ADM --> R
ADM --> P
ADM --> PAY
BM -.domain events.-> AUD
R -.domain events.-> AUD
PAY -.domain events.-> AUD
R -.events.-> N
PAY -.events.-> N
MSG --> R
REP --> BM
REP --> R
REP --> PAY
Dashed arrows represent event-driven side effects/read concerns rather than synchronous business authority.
Own authentication and authorization primitives.
Examples of contracts:
Identity must not depend on Angular/Ionic or any frontend-specific concept. Authentication and authorization are server-side capabilities.
Own the residential ownership/tenant boundary and who is authorized to act within it.
A display label such as 3A is not a global identifier. Membership is explicit and scoped to a building.
Own reservable resources and the structural availability constraints that are independent of a specific reservation transaction.
The module describes when/resources can be used. The Reservations module decides whether a concrete booking can be accepted after considering existing reservations.
Own the booking lifecycle and enforce reservation invariants.
Two incompatible bookings must never both become valid for the same resource/time window.
Own price rules and authoritative price calculation.
A quote/price breakdown that Reservations snapshots into historical reservation price lines.
Changing today’s price rules must not rewrite the historical amount of an existing reservation.
Own payment attempts, payment-provider integration and payment state.
Payments can report a trusted payment outcome. Reservations owns the booking transition that follows that outcome.
This intentionally prevents Mercado Pago-specific statuses from becoming reservation-domain statuses.
Provide privileged application use cases by orchestrating existing modules.
Primarily application workflows, not duplicate domain entities.
Examples:
Administration does not bypass module invariants or write tables directly. Admin powers use the same domain rules with elevated authorization where explicitly allowed.
Record important security, financial and administrative actions.
Events/actions emitted by modules such as:
Audit recording should not become the business authority for another module.
Administration is an orchestrator: it exposes /api/admin/... and composes read models but owns no data and never touches another module’s tables. Commands go through explicit contracts implemented by the owning module (IReservationAdminContract, IEventSlotAdminContract, IPricingAdminContract, IAmenityAdminContract; read side IReservationAdminQuery, IPaymentAdminQuery), so each module keeps its own invariants. See docs/04-data/domain-model.md#administrative-operations-issue-26.
Modules call Audit; Audit calls no module. The public surface is
IAuditRecorder.Record(AuditRecord), which only adds the entry to the current
AppDbContext so the owning use case controls SaveChanges and the
transaction (the transition and its audit entry commit together). Actors are
User, System and ExternalProvider; actions are a closed catalog
(AuditAction) and metadata is built only by typed, allowlisted
AuditMetadata factories. AuditLog has no foreign keys to other modules’
tables and is append-only. Reservations audits its own transitions as the
system (it never knows the payment provider or method); Payments audits the
real actor. See docs/04-data/domain-model.md#audit-trail-issue-27.
Translate business events into user-facing notification intents and deliveries.
A notification failure must not roll back a successfully completed core reservation/payment transaction unless a future business rule explicitly requires it.
Provide communication scoped to a valid reservation/shared-use context.
Reservations to validate the conversation context and participants.
This module is not a general-purpose social network or building-wide WhatsApp replacement.
Provide read-oriented metrics and reports without becoming the source of truth for operational workflows.
Reporting may consume/query operational data through defined read models, but business commands must continue to go through their owning modules.
Use three mechanisms deliberately.
For synchronous information required to complete a command.
Example:
Reservations
-> Buildings & Memberships: validate active membership
-> Amenities & Availability: load resource constraints
-> Pricing: calculate quote
For side effects that do not own the originating transaction.
Example:
ReservationConfirmed
-> Audit
-> Notifications
For dashboards/reporting where joining operational data is useful but must not create command coupling.
The following patterns are not allowed:
A very small shared technical layer is acceptable for non-business primitives such as:
Result/Error abstractions;Business concepts such as Reservation, Payment, Building or PriceRule do not belong in a generic shared kernel.
The exact .NET project layout will be validated in the technical spike, but the logical target is:
apps/api/
src/
Api/
Modules/
Identity/
Buildings/
Amenities/
Reservations/
Pricing/
Payments/
Administration/
Audit/
Notifications/
tests/
Unit/
Integration/
Whether every logical module becomes a separate .NET project immediately is intentionally left open for the toolchain/scaffold spike. Boundary quality matters more than generating many projects.
A module becomes a separate deployable service only when there is concrete evidence such as:
“Microservices look more professional” is not sufficient justification.