Anti-patterns
Use this page as a review checklist. Each row names the smell, why it breaks replaceability, and the fix.
Cross-module
| Anti-pattern | Why it is wrong | Fix |
|---|---|---|
| Use Case imports peer Domain / Application | Couples compile graph to peer internals | Local ACL port + adapter, or Domain Event |
Use Case injects peer *ModuleInterface | Same coupling; fails HTTP-replace test | Consumer-owned {Need}PortInterface |
| UI imports peer ORM models to decide policy | Business rules leak into delivery | Call own Use Case; peer data via ACL |
| Shared folder holds business enums “for reuse” | Shared becomes a distributed Domain | ACL / Events; Shared is technical only |
| Thin event with ID only; consumer re-fetches publisher Domain | Re-introduces sync coupling | Rich happy-path payload (events) |
| Listener calls publisher Use Case / repository | Listener becomes an RPC client | Map from event payload → inbound Use Case |
God *ModuleInterface dumping the whole module | Unstable public API; hard to HTTP-replace | Thin capability façades (contracts) |
No eventId / idempotency on consumers | Duplicate side effects under at-least-once delivery | Event delivery |
| One SQL transaction writing two modules’ tables | Hidden distributed transaction | Transactions |
| ORM relations across module schemas for reports | Read-side coupling | Reads & reporting |
| Orchestrator imports three Domains | Collapses boundaries | Orchestration — ACL/Events only |
Layers and ports
| Anti-pattern | Why it is wrong | Fix |
|---|---|---|
| Controller / admin action calls repository directly | Skips orchestration; logic spreads | Controller → first-level Use Case |
| Use Case uses ORM / query builder | Application learns persistence shape | Domain repository port |
| Entity extends ORM model | Domain inseparable from driver | Plain Entity + Infrastructure record |
| Repository returns ORM models | Domain/Application polluted | Map to Entity / Domain DTO |
| Domain Event uses framework traits / facades | Domain tied to host | Pure event + Shared dispatcher port |
| Business rules inside composition root | Wiring layer grows a second Application | Move to Use Case / Domain service |
Use Cases and DTOs
| Anti-pattern | Why it is wrong | Fix |
|---|---|---|
Literal Feature/ directory | Placeholder mistaken for real path | Real capability name (Order/, …) |
UI calls nested helper under UseCases/…/Processing | Breaks entry-point boundary | Call first-level *UseCase only |
| Peer module calls this module’s Use Case | Breaks replaceability | ACL or Events |
| Application DTO used as Domain-wide shared type across modules | Cross-module DTO coupling | Local DTOs; map at ACL boundary |
| Many public methods on a Use Case | Unclear entry; hard to test | Single __invoke on first-level Use Cases |
Persistence and Shared
| Anti-pattern | Why it is wrong | Fix |
|---|---|---|
| Cross-module foreign keys for business joins via ORM relations | Hidden coupling through the database | ACL reads / Events; ownership per module |
| Promote to Shared on first use “for the future” | Premature kernel | Keep in module until second consumer appears |
| SQL / vendor SDK inside Domain | Domain not portable | Port + Infrastructure adapter |
Design test (final gate)
Before merge, answer again:
If the peer module is deleted and replaced by HTTP, does Application/Domain still compile?
If not, you still have an anti-pattern above.
Return to the decision tree or walk the Ordering ↔ Warehouse cookbook.