ADR-0009: Anti-Corruption Layer per module for bounded context isolation

Status

Accepted

Tags

architecture, ddd, bounded-context, acl, microservice, modules

Decision

Each module defines its own local domain model for any external entity it needs (e.g., leave/domain/user.go — not imported from the user module). An Anti-Corruption Layer adapter in the outbound persistence layer converts the external/shared model into the module’s local representation. The module’s port interface is the only contract; the implementation can be swapped between PostgreSQL and HTTP without touching business logic.

Why

Direct imports of another module’s domain package create tight coupling: schema changes in the user module break the leave module, circular imports become possible, and independent deployment is blocked. Giving each module its own bounded view of external entities means:
  • The leave module defines what “User” means for leave purposes (ID, Name, ManagerID, DepartmentID) — independent of the user module’s full model
  • The ACL adapter handles translation; the module never sees the external schema
  • Swapping from a shared PostgreSQL query to a remote HTTP call requires only a new adapter implementation — zero business logic changes

Structure

Rules for agents

  • Each module must have its own domain representation of any external entity it needs — do not import from another module’s domain/ package
  • The ACL conversion function (convertToLeaveUser, etc.) lives in the outbound adapter, not in the domain layer
  • Module wiring (NewModule) selects the adapter based on config (MicroserviceMode); business logic does not branch on this
  • The port interface must be defined in the module’s own application/port/ — not in shared packages
  • Adding a new module that needs user data: create {module}/domain/user.go + {module}/application/port/ interface + PostgreSQL adapter; do not reuse leave’s domain model

Bad pattern (do not generate)

Good pattern