residential-amenities-platform

Module Boundaries

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.

Dependency rule

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.

1. Identity & Access

Responsibility

Own authentication and authorization primitives.

Owns

Does not own

Exposes

Examples of contracts:

Dependency notes

Identity must not depend on Angular/Ionic or any frontend-specific concept. Authentication and authorization are server-side capabilities.


2. Buildings & Memberships

Responsibility

Own the residential ownership/tenant boundary and who is authorized to act within it.

Owns

Does not own

Exposes

Important invariant

A display label such as 3A is not a global identifier. Membership is explicit and scoped to a building.


3. Amenities & Availability

Responsibility

Own reservable resources and the structural availability constraints that are independent of a specific reservation transaction.

Owns

Does not own

Exposes

Boundary rule

The module describes when/resources can be used. The Reservations module decides whether a concrete booking can be accepted after considering existing reservations.


4. Reservations

Responsibility

Own the booking lifecycle and enforce reservation invariants.

Owns

Depends on

Does not own

Exposes

Critical invariant

Two incompatible bookings must never both become valid for the same resource/time window.


5. Pricing

Responsibility

Own price rules and authoritative price calculation.

Owns

Produces

A quote/price breakdown that Reservations snapshots into historical reservation price lines.

Does not own

Important invariant

Changing today’s price rules must not rewrite the historical amount of an existing reservation.


6. Payments

Responsibility

Own payment attempts, payment-provider integration and payment state.

Owns

Depends on

Does not own

Boundary rule

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.


7. Administration

Responsibility

Provide privileged application use cases by orchestrating existing modules.

Owns

Primarily application workflows, not duplicate domain entities.

Examples:

Boundary rule

Administration does not bypass module invariants or write tables directly. Admin powers use the same domain rules with elevated authorization where explicitly allowed.


8. Audit

Responsibility

Record important security, financial and administrative actions.

Owns

Consumes

Events/actions emitted by modules such as:

Boundary rule

Audit recording should not become the business authority for another module.

Administration (issue #26)

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.

Implementation (issue #27)

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.


9. Notifications

Responsibility

Translate business events into user-facing notification intents and deliveries.

Examples

Boundary rule

A notification failure must not roll back a successfully completed core reservation/payment transaction unless a future business rule explicitly requires it.


10. Messaging — post-MVP

Responsibility

Provide communication scoped to a valid reservation/shared-use context.

Owns

Depends on

Reservations to validate the conversation context and participants.

Explicit non-goal

This module is not a general-purpose social network or building-wide WhatsApp replacement.


11. Reporting / Analytics — post-MVP

Responsibility

Provide read-oriented metrics and reports without becoming the source of truth for operational workflows.

Examples

Boundary rule

Reporting may consume/query operational data through defined read models, but business commands must continue to go through their owning modules.


Cross-module communication

Use three mechanisms deliberately.

1. Direct application contract

For synchronous information required to complete a command.

Example:

Reservations
  -> Buildings & Memberships: validate active membership
  -> Amenities & Availability: load resource constraints
  -> Pricing: calculate quote

2. Domain/application event

For side effects that do not own the originating transaction.

Example:

ReservationConfirmed
  -> Audit
  -> Notifications

3. Read model/query

For dashboards/reporting where joining operational data is useful but must not create command coupling.

Forbidden coupling

The following patterns are not allowed:

Shared technical kernel

A very small shared technical layer is acceptable for non-business primitives such as:

Business concepts such as Reservation, Payment, Building or PriceRule do not belong in a generic shared kernel.

Proposed backend solution direction

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.

Evolution rule

A module becomes a separate deployable service only when there is concrete evidence such as:

“Microservices look more professional” is not sufficient justification.