2026-04-27-ddd-naming-governance-design.md 15 KB

DDD Naming and Architecture Governance Design

Status: Third governance loop implemented for AchievementReportContent; exam-sprint-domain no longer depends on Jackson / JsonNode. Remaining content debt is modeling OUTLOOK and retiring transitional unmodeled content.

Goal

Establish a long-term governance model for DDD-aligned naming, module dependencies, and architectural boundaries in dcjxb.microservice, starting with the existing exam-sprint ability and then turning the resulting rules into a repeatable template for future abilities.

Current Context

The repository is a Java 17, Spring Boot 3.3.5, Maven multi-module project with this ability layout:

ability-center-runtime
ability-center-kernel
abilities/exam-sprint/contracts
abilities/exam-sprint/application
abilities/exam-sprint/domain
abilities/exam-sprint/infrastructure

The project already has a useful DDD/Clean Architecture foundation:

  • code is grouped by the exam-sprint business ability;
  • contracts, application, domain, and infrastructure are separate Maven modules;
  • ExamSprintReportRepository is defined in domain and implemented in infrastructure;
  • ExamSprintReport contains lifecycle behavior instead of being a pure data object;
  • HTTP adapters live under adapter.http rather than being mixed into domain code.

The main governance items are:

  • historical exam-sprint-domain -> exam-sprint-contracts coupling has been cleared and should remain guarded by architecture tests;
  • OUTLOOK report content remains transitional through unmodeled boundary-prepared content;
  • contracts payload classes contain many business concepts and value-object candidates;
  • ExamSprintReport mixes report, generation task, generated file, and status-record semantics;
  • Pipeline, Worker, Dispatcher, and Scheduler naming emphasizes execution mechanics over use cases;
  • Storage, Renderer, and PdfGenerator currently sit in domain even though they mostly describe technical delivery mechanisms.

Governance Principles

1. Domain owns the ubiquitous language

Domain classes should answer: “what is this in business terms?” Examples:

ExamSprintReportGeneration
ReportGenerationStatus
GeneratedReportFile
ReadinessAssessment
SyllabusMastery
FrequencyPlan
ScoreImprovementCase

Domain classes should not be named after transport or implementation concepts such as Payload, JsonNode, Response, storageObjectKey, pdfBytes, or htmlContent.

2. Contracts describe external protocol only

The contracts module owns external request/response shapes and API-facing enums. Contracts may contain validation annotations and JSON-friendly structure, but domain code must not depend on contract DTOs.

3. Application names use cases and ports

The application module owns use-case orchestration and outbound port definitions. Its primary names should be UseCase, Command, Query, Result, ApplicationService, and business-named ports.

4. Infrastructure names implementation technology

The infrastructure module may use technical names because its job is to adapt concrete technology. Prefixes such as AzureBlob, OpenHtmlToPdf, Classpath, and InMemory are appropriate there.

5. Governance is incremental

Existing debt should be baselined and reduced in small vertical slices. New code must follow the rules immediately. Historical exceptions need owners, expiry expectations, and clear exit criteria.

Target Module Dependencies

Long-term target:

runtime -> contracts
runtime -> application
runtime -> infrastructure

infrastructure -> application
infrastructure -> domain

application -> domain

contracts -> kernel / validation api
domain -> kernel, only for stable domain-agnostic primitives

Forbidden long-term dependencies:

domain -> contracts
domain -> application
domain -> infrastructure
domain -> runtime
domain -> jackson
domain -> spring
domain -> jakarta.validation

application -> infrastructure implementation classes
contracts -> domain/application/infrastructure/runtime
infrastructure -> runtime/adapter.http/controller

Naming Rules

Domain

Recommended names:

ExamSprintReportGeneration
ReportGenerationStatus
ReportType
GeneratedReportFile
ReportRetentionPolicy
ReportFailureReason
OutlookReportContent
AchievementReportContent
ReadinessAssessment
SyllabusMastery
FrequencyPlan
ScoreImprovementCase
ExamSprintReportRepository

Allowed suffixes:

Concept Preferred naming
Repository Repository
Domain service DomainService, only when behavior does not belong naturally on an entity/value object
Domain event Event
Business rule Policy or Specification
Identifier Id
Lifecycle state Status
Business category Type

Avoid in domain:

DTO
Request
Response
Payload
Controller
Manager
Processor
Handler
Pipeline
Worker
Dispatcher
Scheduler
JsonNode
Storage
Renderer
PdfGenerator

Storage, Renderer, and PdfGenerator may only remain in domain if the team explicitly decides they are domain capabilities rather than technical delivery mechanisms.

Application

Recommended names:

CreateExamSprintReportUseCase
GenerateExamSprintReportUseCase
GetExamSprintReportUseCase
DownloadExamSprintReportUseCase
ExpireExamSprintReportsUseCase
CreateExamSprintReportCommand
ExamSprintReportDetailResult
ReportFileStorage
ReportDocumentRenderer
PdfDocumentGenerator
ReportGenerationRequestPublisher

Preferred suffixes:

UseCase
ApplicationService
Command
Query
Result
Port
Executor
Publisher
Mapper
Job
ScheduledJob

Avoid using Pipeline, Worker, Dispatcher, and Scheduler for new business-flow classes. If execution mechanics are the actual concept, place the class in a technical package and document the reason in review.

Contracts

Recommended names:

CreateExamSprintReportRequest
CreateExamSprintReportResponse
CreateExamSprintReportWithUrlResponse
ExamSprintReportDetailResponse
OutlookExamSprintReportPayload
AchievementExamSprintReportPayload
ExamSprintReportType
ExamSprintReportGenerationStatus

Allowed suffixes:

Request
Response
Payload
Type
Status

Rules:

  • contracts DTOs must not be used as domain models;
  • contracts enums that duplicate domain concepts must be mapped explicitly;
  • nested payload records that contain business behavior must be registered as domain value-object candidates.

Infrastructure

Recommended names:

AzureBlobReportFileStorage
InMemoryExamSprintReportRepository
ClasspathOutlookReportDocumentRenderer
ClasspathAchievementReportDocumentRenderer
OpenHtmlToPdfDocumentGenerator
ExamSprintReportInfrastructureConfiguration

Allowed technology prefixes:

AzureBlob
OpenHtmlToPdf
Classpath
InMemory
Jdbc
Jpa
Redis
Kafka
Rabbit

Infrastructure names should make the chosen technology visible.

Phased Roadmap

Short term: 1-2 weeks

Goal: establish rules and stop new violations.

Actions:

  1. Add this governance document to the repository.
  2. Add a first architecture test baseline with allowlisted existing debt.
  3. Start PR checklist enforcement for naming and dependency direction.
  4. Move one lifecycle concept from contracts ownership into domain ownership; recommended first candidate: ReportGenerationStatus.
  5. Keep external API responses unchanged by mapping domain status back to contract status in application code.

Acceptance criteria:

  • new domain code cannot introduce new dependencies on contracts or JsonNode without a reviewed exception;
  • at least one existing domain-to-contract dependency is removed;
  • architecture tests run in CI or in the normal Maven test command;
  • external HTTP contract remains compatible.

Mid term: 1-2 months

Goal: make domain independent from contracts and reduce JSON leakage.

Actions:

  1. Move or duplicate domain-owned enums into domain and map at boundaries.
  2. Replace domain JsonNode payload usage with strongly named content concepts for at least one report type.
  3. Move business invariants from payload records into domain value objects.
  4. Reclassify ExamSprintReportStorage, ExamSprintReportRenderer, and ExamSprintReportPdfGenerator as application ports unless the team explicitly documents domain ownership.
  5. Start thinning DefaultExamSprintReportApplicationService by extracting use cases behind the existing facade.

Acceptance criteria:

  • exam-sprint-domain no longer depends on exam-sprint-contracts;
  • new domain code does not use Jackson types;
  • at least one report content payload has a domain model;
  • application ports own rendering, PDF, and file-storage abstractions if they are technical delivery mechanisms.

Long term: 1-2 quarters

Goal: establish a stable DDD/Clean Architecture template for future abilities.

Actions:

  1. Decide whether the current ExamSprintReport should become ExamSprintReportGeneration.
  2. Extract GeneratedReportFile, ReportGenerationFailure, and report-content value objects when their behavior becomes stable.
  3. Split application orchestration into use cases: create, generate, get, download, and expire.
  4. Convert architecture rules from allowlist-based soft governance to hard CI checks.
  5. Publish the exam-sprint structure as the standard template for new abilities/{ability-name} modules.

Acceptance criteria:

  • domain contains business language and business rules only;
  • application contains use cases and ports;
  • contracts contains external API protocol only;
  • infrastructure contains concrete adapters only;
  • architectural allowlist trends toward zero;
  • new ability modules follow the same pattern from creation.

Technical Debt Register

Debt Status / current reason Exit condition or guardrail
domain -> contracts Cleared in the ReportType loop after ReportGenerationStatus and ReportType became domain-owned keep ArchUnit hard rule; map future contract/domain duplicates in application
domain -> jackson Cleared at compile-time in the AchievementReportContent loop by replacing domain JsonNode with ReportContent; OUTLOOK still uses a transitional unmodeled content wrapper prepared at application boundary introduce OutlookReportContent, remove UnmodeledReportContent, and keep ArchUnit hard rule
Payload records contain business behavior AchievementReportContent now owns the selected achievement content shape; OutlookExamSprintReportPayload and richer invariants remain in contracts/infrastructure migrate OutlookReportContent and move stable invariants into domain value objects incrementally
ExamSprintReport has mixed semantics current model stores generation state and file references model is renamed or split around generation semantics
domain hosts Storage/Renderer/PdfGenerator ports were initially placed near report aggregate ports move to application or are explicitly documented as domain capabilities
application contains technical-flow names existing pipeline/worker/dispatcher naming new use-case names introduced and old names retired gradually

Review Checklist

Use this list for every PR that touches abilities/exam-sprint.

Dependencies

  • Did domain add a dependency on contracts?
  • Did domain add Jackson, Spring, validation, Azure, PDF, or persistence dependencies?
  • Did application depend on infrastructure implementation classes?
  • Did contracts depend on domain, application, infrastructure, or runtime?
  • Did infrastructure depend on runtime, controller, or HTTP adapter classes?

Naming

  • Does each new domain name describe a business concept?
  • Does each new application name describe a use case, command, result, or port?
  • Does each new contract name clearly describe request/response/payload protocol?
  • Does each new infrastructure name describe concrete technology or adapter behavior?
  • If a new class uses Manager, Processor, Handler, Pipeline, Worker, Dispatcher, or Scheduler, is the reason documented?

Model boundaries

  • Is an API DTO being used as a domain model?
  • Is JsonNode, Map<String,Object>, or raw JSON entering domain logic?
  • Is a new string field actually a value-object candidate?
  • Is a business invariant implemented outside domain without a reason?
  • Are lifecycle transitions controlled by domain methods?

Compatibility and tests

  • Does the public API remain compatible?
  • Are contracts mapped explicitly when domain concepts differ?
  • Are domain/application tests updated for success and failure paths?
  • Are architecture tests updated when dependency rules change?
  • Is any allowlist entry reduced, updated, or justified?

Metrics

Track these metrics once per iteration:

Metric Target
domain -> contracts references 0
domain -> jackson references 0
JsonNode usages in domain 0
new domain classes ending in DTO, Request, Response, or Payload 0
architecture allowlist entries decreasing
new architectural violations 0
application classes with broad orchestration responsibilities decreasing
payload nested records with business behavior decreasing or explicitly accepted

First Governance Loop

The recommended first loop is:

domain dependency baseline + ReportGenerationStatus migration

Why this loop first:

  • generation status is a lifecycle concept and belongs to domain language;
  • it reduces domain -> contracts coupling without changing the external API;
  • it created a repeatable mapper pattern later used for the ReportType migration and still applicable to payload migrations;
  • it gives the architecture tests a concrete baseline and a concrete debt reduction.

First loop result: ReportGenerationStatus is now domain-owned; the application boundary maps it back to public contracts status, and architecture tests established the initial dependency baseline.

Second loop result: ReportType is now domain-owned; the application boundary maps it to and from public ExamSprintReportType; exam-sprint-domain no longer depends on exam-sprint-contracts; and the domain-to-contract architecture rule is a hard guardrail.

Third loop result: AchievementReportContent is introduced as strongly named domain report content; the application boundary converts public achievement payload JSON/contracts DTOs into domain content; domain main source no longer imports Jackson / JsonNode; and the domain-to-Jackson architecture rule is tightened to a hard guardrail.

Remaining payload debt: OUTLOOK content remains unmodeled through a transitional UnmodeledReportContent wrapper so this loop does not migrate all payload records or rewrite the larger outlook renderer.

Next recommended loop: introduce OutlookReportContent and retire UnmodeledReportContent, then review whether rendering/file/PDF ports should move to application in a separate dedicated loop.

The detailed implementation plan is in:

docs/superpowers/plans/2026-04-27-ddd-naming-governance-first-loop.md
docs/superpowers/plans/2026-04-28-ddd-naming-governance-report-type-loop.md
docs/superpowers/plans/2026-04-28-ddd-naming-governance-jsonnode-payload-loop.md