@booklib/core 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/booklib-standards.mdc +40 -0
- package/.gemini/context.md +372 -0
- package/AGENTS.md +166 -0
- package/CHANGELOG.md +226 -0
- package/CLAUDE.md +81 -0
- package/CODE_OF_CONDUCT.md +31 -0
- package/CONTRIBUTING.md +304 -0
- package/LICENSE +21 -0
- package/PLAN.md +28 -0
- package/README.ja.md +198 -0
- package/README.ko.md +198 -0
- package/README.md +503 -0
- package/README.pt-BR.md +198 -0
- package/README.uk.md +241 -0
- package/README.zh-CN.md +198 -0
- package/SECURITY.md +9 -0
- package/agents/architecture-reviewer.md +136 -0
- package/agents/booklib-reviewer.md +90 -0
- package/agents/data-reviewer.md +107 -0
- package/agents/jvm-reviewer.md +146 -0
- package/agents/python-reviewer.md +128 -0
- package/agents/rust-reviewer.md +115 -0
- package/agents/ts-reviewer.md +110 -0
- package/agents/ui-reviewer.md +117 -0
- package/assets/logo.svg +36 -0
- package/bin/booklib-mcp.js +304 -0
- package/bin/booklib.js +1705 -0
- package/bin/skills.cjs +1292 -0
- package/booklib-router.mdc +36 -0
- package/booklib.config.json +19 -0
- package/commands/animation-at-work.md +10 -0
- package/commands/clean-code-reviewer.md +10 -0
- package/commands/data-intensive-patterns.md +10 -0
- package/commands/data-pipelines.md +10 -0
- package/commands/design-patterns.md +10 -0
- package/commands/domain-driven-design.md +10 -0
- package/commands/effective-java.md +10 -0
- package/commands/effective-kotlin.md +10 -0
- package/commands/effective-python.md +10 -0
- package/commands/effective-typescript.md +10 -0
- package/commands/kotlin-in-action.md +10 -0
- package/commands/lean-startup.md +10 -0
- package/commands/microservices-patterns.md +10 -0
- package/commands/programming-with-rust.md +10 -0
- package/commands/refactoring-ui.md +10 -0
- package/commands/rust-in-action.md +10 -0
- package/commands/skill-router.md +10 -0
- package/commands/spring-boot-in-action.md +10 -0
- package/commands/storytelling-with-data.md +10 -0
- package/commands/system-design-interview.md +10 -0
- package/commands/using-asyncio-python.md +10 -0
- package/commands/web-scraping-python.md +10 -0
- package/community/registry.json +1616 -0
- package/hooks/hooks.json +23 -0
- package/hooks/posttooluse-capture.mjs +67 -0
- package/hooks/suggest.js +153 -0
- package/lib/agent-behaviors.js +40 -0
- package/lib/agent-detector.js +96 -0
- package/lib/config-loader.js +39 -0
- package/lib/conflict-resolver.js +148 -0
- package/lib/context-builder.js +574 -0
- package/lib/discovery-engine.js +298 -0
- package/lib/doctor/hook-installer.js +83 -0
- package/lib/doctor/usage-tracker.js +87 -0
- package/lib/engine/ai-features.js +253 -0
- package/lib/engine/auditor.js +103 -0
- package/lib/engine/bm25-index.js +178 -0
- package/lib/engine/capture.js +120 -0
- package/lib/engine/corrections.js +198 -0
- package/lib/engine/doctor.js +195 -0
- package/lib/engine/graph-injector.js +137 -0
- package/lib/engine/graph.js +161 -0
- package/lib/engine/handoff.js +405 -0
- package/lib/engine/indexer.js +242 -0
- package/lib/engine/parser.js +53 -0
- package/lib/engine/query-expander.js +42 -0
- package/lib/engine/reranker.js +40 -0
- package/lib/engine/rrf.js +59 -0
- package/lib/engine/scanner.js +151 -0
- package/lib/engine/searcher.js +139 -0
- package/lib/engine/session-coordinator.js +306 -0
- package/lib/engine/session-manager.js +429 -0
- package/lib/engine/synthesizer.js +70 -0
- package/lib/installer.js +70 -0
- package/lib/instinct-block.js +33 -0
- package/lib/mcp-config-writer.js +88 -0
- package/lib/paths.js +57 -0
- package/lib/profiles/design.md +19 -0
- package/lib/profiles/general.md +16 -0
- package/lib/profiles/research-analysis.md +22 -0
- package/lib/profiles/software-development.md +23 -0
- package/lib/profiles/writing-content.md +19 -0
- package/lib/project-initializer.js +916 -0
- package/lib/registry/skills.js +102 -0
- package/lib/registry-searcher.js +99 -0
- package/lib/rules/rules-manager.js +169 -0
- package/lib/skill-fetcher.js +333 -0
- package/lib/well-known-builder.js +70 -0
- package/lib/wizard/index.js +404 -0
- package/lib/wizard/integration-detector.js +41 -0
- package/lib/wizard/project-detector.js +100 -0
- package/lib/wizard/prompt.js +156 -0
- package/lib/wizard/registry-embeddings.js +107 -0
- package/lib/wizard/skill-recommender.js +69 -0
- package/llms-full.txt +254 -0
- package/llms.txt +70 -0
- package/package.json +45 -0
- package/research-reports/2026-04-01-current-architecture.md +160 -0
- package/research-reports/IDEAS.md +93 -0
- package/rules/common/clean-code.md +42 -0
- package/rules/java/effective-java.md +42 -0
- package/rules/kotlin/effective-kotlin.md +37 -0
- package/rules/python/effective-python.md +38 -0
- package/rules/rust/rust.md +37 -0
- package/rules/typescript/effective-typescript.md +42 -0
- package/scripts/gen-llms-full.mjs +36 -0
- package/scripts/gen-og.mjs +142 -0
- package/scripts/validate-frontmatter.js +25 -0
- package/skills/animation-at-work/SKILL.md +270 -0
- package/skills/animation-at-work/assets/example_asset.txt +1 -0
- package/skills/animation-at-work/evals/evals.json +44 -0
- package/skills/animation-at-work/evals/results.json +13 -0
- package/skills/animation-at-work/examples/after.md +64 -0
- package/skills/animation-at-work/examples/before.md +35 -0
- package/skills/animation-at-work/references/api_reference.md +369 -0
- package/skills/animation-at-work/references/review-checklist.md +79 -0
- package/skills/animation-at-work/scripts/audit_animations.py +295 -0
- package/skills/animation-at-work/scripts/example.py +1 -0
- package/skills/clean-code-reviewer/SKILL.md +444 -0
- package/skills/clean-code-reviewer/audit.json +35 -0
- package/skills/clean-code-reviewer/evals/evals.json +185 -0
- package/skills/clean-code-reviewer/evals/results.json +13 -0
- package/skills/clean-code-reviewer/examples/after.md +48 -0
- package/skills/clean-code-reviewer/examples/before.md +33 -0
- package/skills/clean-code-reviewer/references/api_reference.md +158 -0
- package/skills/clean-code-reviewer/references/practices-catalog.md +282 -0
- package/skills/clean-code-reviewer/references/review-checklist.md +254 -0
- package/skills/clean-code-reviewer/scripts/pre-review.py +206 -0
- package/skills/data-intensive-patterns/SKILL.md +267 -0
- package/skills/data-intensive-patterns/assets/example_asset.txt +1 -0
- package/skills/data-intensive-patterns/evals/evals.json +54 -0
- package/skills/data-intensive-patterns/evals/results.json +13 -0
- package/skills/data-intensive-patterns/examples/after.md +61 -0
- package/skills/data-intensive-patterns/examples/before.md +38 -0
- package/skills/data-intensive-patterns/references/api_reference.md +34 -0
- package/skills/data-intensive-patterns/references/patterns-catalog.md +551 -0
- package/skills/data-intensive-patterns/references/review-checklist.md +193 -0
- package/skills/data-intensive-patterns/scripts/adr.py +213 -0
- package/skills/data-intensive-patterns/scripts/example.py +1 -0
- package/skills/data-pipelines/SKILL.md +259 -0
- package/skills/data-pipelines/assets/example_asset.txt +1 -0
- package/skills/data-pipelines/evals/evals.json +45 -0
- package/skills/data-pipelines/evals/results.json +13 -0
- package/skills/data-pipelines/examples/after.md +97 -0
- package/skills/data-pipelines/examples/before.md +37 -0
- package/skills/data-pipelines/references/api_reference.md +301 -0
- package/skills/data-pipelines/references/review-checklist.md +181 -0
- package/skills/data-pipelines/scripts/example.py +1 -0
- package/skills/data-pipelines/scripts/new_pipeline.py +444 -0
- package/skills/design-patterns/SKILL.md +271 -0
- package/skills/design-patterns/assets/example_asset.txt +1 -0
- package/skills/design-patterns/evals/evals.json +46 -0
- package/skills/design-patterns/evals/results.json +13 -0
- package/skills/design-patterns/examples/after.md +52 -0
- package/skills/design-patterns/examples/before.md +29 -0
- package/skills/design-patterns/references/api_reference.md +1 -0
- package/skills/design-patterns/references/patterns-catalog.md +726 -0
- package/skills/design-patterns/references/review-checklist.md +173 -0
- package/skills/design-patterns/scripts/example.py +1 -0
- package/skills/design-patterns/scripts/scaffold.py +807 -0
- package/skills/domain-driven-design/SKILL.md +142 -0
- package/skills/domain-driven-design/assets/example_asset.txt +1 -0
- package/skills/domain-driven-design/evals/evals.json +48 -0
- package/skills/domain-driven-design/evals/results.json +13 -0
- package/skills/domain-driven-design/examples/after.md +80 -0
- package/skills/domain-driven-design/examples/before.md +43 -0
- package/skills/domain-driven-design/references/api_reference.md +1 -0
- package/skills/domain-driven-design/references/patterns-catalog.md +545 -0
- package/skills/domain-driven-design/references/review-checklist.md +158 -0
- package/skills/domain-driven-design/scripts/example.py +1 -0
- package/skills/domain-driven-design/scripts/scaffold.py +421 -0
- package/skills/effective-java/SKILL.md +227 -0
- package/skills/effective-java/assets/example_asset.txt +1 -0
- package/skills/effective-java/evals/evals.json +46 -0
- package/skills/effective-java/evals/results.json +13 -0
- package/skills/effective-java/examples/after.md +83 -0
- package/skills/effective-java/examples/before.md +37 -0
- package/skills/effective-java/references/api_reference.md +1 -0
- package/skills/effective-java/references/items-catalog.md +955 -0
- package/skills/effective-java/references/review-checklist.md +216 -0
- package/skills/effective-java/scripts/checkstyle_setup.py +211 -0
- package/skills/effective-java/scripts/example.py +1 -0
- package/skills/effective-kotlin/SKILL.md +271 -0
- package/skills/effective-kotlin/assets/example_asset.txt +1 -0
- package/skills/effective-kotlin/audit.json +29 -0
- package/skills/effective-kotlin/evals/evals.json +45 -0
- package/skills/effective-kotlin/evals/results.json +13 -0
- package/skills/effective-kotlin/examples/after.md +36 -0
- package/skills/effective-kotlin/examples/before.md +38 -0
- package/skills/effective-kotlin/references/api_reference.md +1 -0
- package/skills/effective-kotlin/references/practices-catalog.md +1228 -0
- package/skills/effective-kotlin/references/review-checklist.md +126 -0
- package/skills/effective-kotlin/scripts/example.py +1 -0
- package/skills/effective-python/SKILL.md +441 -0
- package/skills/effective-python/evals/evals.json +44 -0
- package/skills/effective-python/evals/results.json +13 -0
- package/skills/effective-python/examples/after.md +56 -0
- package/skills/effective-python/examples/before.md +40 -0
- package/skills/effective-python/ref-01-pythonic-thinking.md +202 -0
- package/skills/effective-python/ref-02-lists-and-dicts.md +146 -0
- package/skills/effective-python/ref-03-functions.md +186 -0
- package/skills/effective-python/ref-04-comprehensions-generators.md +211 -0
- package/skills/effective-python/ref-05-classes-interfaces.md +188 -0
- package/skills/effective-python/ref-06-metaclasses-attributes.md +209 -0
- package/skills/effective-python/ref-07-concurrency.md +213 -0
- package/skills/effective-python/ref-08-robustness-performance.md +248 -0
- package/skills/effective-python/ref-09-testing-debugging.md +253 -0
- package/skills/effective-python/ref-10-collaboration.md +175 -0
- package/skills/effective-python/references/api_reference.md +218 -0
- package/skills/effective-python/references/practices-catalog.md +483 -0
- package/skills/effective-python/references/review-checklist.md +190 -0
- package/skills/effective-python/scripts/lint.py +173 -0
- package/skills/effective-typescript/SKILL.md +262 -0
- package/skills/effective-typescript/audit.json +29 -0
- package/skills/effective-typescript/evals/evals.json +37 -0
- package/skills/effective-typescript/evals/results.json +13 -0
- package/skills/effective-typescript/examples/after.md +70 -0
- package/skills/effective-typescript/examples/before.md +47 -0
- package/skills/effective-typescript/references/api_reference.md +118 -0
- package/skills/effective-typescript/references/practices-catalog.md +371 -0
- package/skills/effective-typescript/scripts/review.py +169 -0
- package/skills/kotlin-in-action/SKILL.md +261 -0
- package/skills/kotlin-in-action/assets/example_asset.txt +1 -0
- package/skills/kotlin-in-action/evals/evals.json +43 -0
- package/skills/kotlin-in-action/evals/results.json +13 -0
- package/skills/kotlin-in-action/examples/after.md +53 -0
- package/skills/kotlin-in-action/examples/before.md +39 -0
- package/skills/kotlin-in-action/references/api_reference.md +1 -0
- package/skills/kotlin-in-action/references/practices-catalog.md +436 -0
- package/skills/kotlin-in-action/references/review-checklist.md +204 -0
- package/skills/kotlin-in-action/scripts/example.py +1 -0
- package/skills/kotlin-in-action/scripts/setup_detekt.py +224 -0
- package/skills/lean-startup/SKILL.md +160 -0
- package/skills/lean-startup/assets/example_asset.txt +1 -0
- package/skills/lean-startup/evals/evals.json +43 -0
- package/skills/lean-startup/evals/results.json +13 -0
- package/skills/lean-startup/examples/after.md +80 -0
- package/skills/lean-startup/examples/before.md +34 -0
- package/skills/lean-startup/references/api_reference.md +319 -0
- package/skills/lean-startup/references/review-checklist.md +137 -0
- package/skills/lean-startup/scripts/example.py +1 -0
- package/skills/lean-startup/scripts/new_experiment.py +286 -0
- package/skills/microservices-patterns/SKILL.md +384 -0
- package/skills/microservices-patterns/evals/evals.json +45 -0
- package/skills/microservices-patterns/evals/results.json +13 -0
- package/skills/microservices-patterns/examples/after.md +69 -0
- package/skills/microservices-patterns/examples/before.md +40 -0
- package/skills/microservices-patterns/references/patterns-catalog.md +391 -0
- package/skills/microservices-patterns/references/review-checklist.md +169 -0
- package/skills/microservices-patterns/scripts/new_service.py +583 -0
- package/skills/programming-with-rust/SKILL.md +209 -0
- package/skills/programming-with-rust/evals/evals.json +37 -0
- package/skills/programming-with-rust/evals/results.json +13 -0
- package/skills/programming-with-rust/examples/after.md +107 -0
- package/skills/programming-with-rust/examples/before.md +59 -0
- package/skills/programming-with-rust/references/api_reference.md +152 -0
- package/skills/programming-with-rust/references/practices-catalog.md +335 -0
- package/skills/programming-with-rust/scripts/review.py +142 -0
- package/skills/refactoring-ui/SKILL.md +362 -0
- package/skills/refactoring-ui/assets/example_asset.txt +1 -0
- package/skills/refactoring-ui/evals/evals.json +45 -0
- package/skills/refactoring-ui/evals/results.json +13 -0
- package/skills/refactoring-ui/examples/after.md +85 -0
- package/skills/refactoring-ui/examples/before.md +58 -0
- package/skills/refactoring-ui/references/api_reference.md +355 -0
- package/skills/refactoring-ui/references/review-checklist.md +114 -0
- package/skills/refactoring-ui/scripts/audit_css.py +250 -0
- package/skills/refactoring-ui/scripts/example.py +1 -0
- package/skills/rust-in-action/SKILL.md +350 -0
- package/skills/rust-in-action/evals/evals.json +38 -0
- package/skills/rust-in-action/evals/results.json +13 -0
- package/skills/rust-in-action/examples/after.md +156 -0
- package/skills/rust-in-action/examples/before.md +56 -0
- package/skills/rust-in-action/references/practices-catalog.md +346 -0
- package/skills/rust-in-action/scripts/review.py +147 -0
- package/skills/skill-router/SKILL.md +186 -0
- package/skills/skill-router/evals/evals.json +38 -0
- package/skills/skill-router/evals/results.json +13 -0
- package/skills/skill-router/examples/after.md +63 -0
- package/skills/skill-router/examples/before.md +39 -0
- package/skills/skill-router/references/api_reference.md +24 -0
- package/skills/skill-router/references/routing-heuristics.md +89 -0
- package/skills/skill-router/references/skill-catalog.md +174 -0
- package/skills/skill-router/scripts/route.py +266 -0
- package/skills/spring-boot-in-action/SKILL.md +340 -0
- package/skills/spring-boot-in-action/evals/evals.json +39 -0
- package/skills/spring-boot-in-action/evals/results.json +13 -0
- package/skills/spring-boot-in-action/examples/after.md +185 -0
- package/skills/spring-boot-in-action/examples/before.md +84 -0
- package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
- package/skills/spring-boot-in-action/scripts/review.py +184 -0
- package/skills/storytelling-with-data/SKILL.md +241 -0
- package/skills/storytelling-with-data/assets/example_asset.txt +1 -0
- package/skills/storytelling-with-data/evals/evals.json +47 -0
- package/skills/storytelling-with-data/evals/results.json +13 -0
- package/skills/storytelling-with-data/examples/after.md +50 -0
- package/skills/storytelling-with-data/examples/before.md +33 -0
- package/skills/storytelling-with-data/references/api_reference.md +379 -0
- package/skills/storytelling-with-data/references/review-checklist.md +111 -0
- package/skills/storytelling-with-data/scripts/chart_review.py +301 -0
- package/skills/storytelling-with-data/scripts/example.py +1 -0
- package/skills/system-design-interview/SKILL.md +233 -0
- package/skills/system-design-interview/assets/example_asset.txt +1 -0
- package/skills/system-design-interview/evals/evals.json +46 -0
- package/skills/system-design-interview/evals/results.json +13 -0
- package/skills/system-design-interview/examples/after.md +94 -0
- package/skills/system-design-interview/examples/before.md +27 -0
- package/skills/system-design-interview/references/api_reference.md +582 -0
- package/skills/system-design-interview/references/review-checklist.md +201 -0
- package/skills/system-design-interview/scripts/example.py +1 -0
- package/skills/system-design-interview/scripts/new_design.py +421 -0
- package/skills/using-asyncio-python/SKILL.md +290 -0
- package/skills/using-asyncio-python/assets/example_asset.txt +1 -0
- package/skills/using-asyncio-python/evals/evals.json +43 -0
- package/skills/using-asyncio-python/evals/results.json +13 -0
- package/skills/using-asyncio-python/examples/after.md +68 -0
- package/skills/using-asyncio-python/examples/before.md +39 -0
- package/skills/using-asyncio-python/references/api_reference.md +267 -0
- package/skills/using-asyncio-python/references/review-checklist.md +149 -0
- package/skills/using-asyncio-python/scripts/check_blocking.py +270 -0
- package/skills/using-asyncio-python/scripts/example.py +1 -0
- package/skills/web-scraping-python/SKILL.md +280 -0
- package/skills/web-scraping-python/assets/example_asset.txt +1 -0
- package/skills/web-scraping-python/evals/evals.json +46 -0
- package/skills/web-scraping-python/evals/results.json +13 -0
- package/skills/web-scraping-python/examples/after.md +109 -0
- package/skills/web-scraping-python/examples/before.md +40 -0
- package/skills/web-scraping-python/references/api_reference.md +393 -0
- package/skills/web-scraping-python/references/review-checklist.md +163 -0
- package/skills/web-scraping-python/scripts/example.py +1 -0
- package/skills/web-scraping-python/scripts/new_scraper.py +231 -0
- package/skills/writing-plans/audit.json +34 -0
- package/tests/agent-detector.test.js +83 -0
- package/tests/corrections.test.js +245 -0
- package/tests/doctor/hook-installer.test.js +72 -0
- package/tests/doctor/usage-tracker.test.js +140 -0
- package/tests/engine/benchmark-eval.test.js +31 -0
- package/tests/engine/bm25-index.test.js +85 -0
- package/tests/engine/capture-command.test.js +35 -0
- package/tests/engine/capture.test.js +17 -0
- package/tests/engine/graph-augmented-search.test.js +107 -0
- package/tests/engine/graph-injector.test.js +44 -0
- package/tests/engine/graph.test.js +216 -0
- package/tests/engine/hybrid-searcher.test.js +74 -0
- package/tests/engine/indexer-bm25.test.js +37 -0
- package/tests/engine/mcp-tools.test.js +73 -0
- package/tests/engine/project-initializer-mcp.test.js +99 -0
- package/tests/engine/query-expander.test.js +36 -0
- package/tests/engine/reranker.test.js +51 -0
- package/tests/engine/rrf.test.js +49 -0
- package/tests/engine/srag-prefix.test.js +47 -0
- package/tests/instinct-block.test.js +23 -0
- package/tests/mcp-config-writer.test.js +60 -0
- package/tests/project-initializer-new-agents.test.js +48 -0
- package/tests/rules/rules-manager.test.js +230 -0
- package/tests/well-known-builder.test.js +40 -0
- package/tests/wizard/integration-detector.test.js +31 -0
- package/tests/wizard/project-detector.test.js +51 -0
- package/tests/wizard/prompt-session.test.js +61 -0
- package/tests/wizard/prompt.test.js +16 -0
- package/tests/wizard/registry-embeddings.test.js +35 -0
- package/tests/wizard/skill-recommender.test.js +34 -0
- package/tests/wizard/slot-count.test.js +25 -0
- package/vercel.json +21 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Domain-Driven Design — Code Review Checklist
|
|
2
|
+
|
|
3
|
+
Systematic checklist for reviewing code against DDD principles. Organized by
|
|
4
|
+
category with specific items to inspect.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Ubiquitous Language
|
|
9
|
+
|
|
10
|
+
- [ ] **Class names match domain terms** — Classes are named after domain concepts the business recognizes, not technical abstractions (e.g., `Policy` not `RuleEngine`, `Shipment` not `DataTransferObject`)
|
|
11
|
+
- [ ] **Method names describe domain operations** — Methods read like domain actions (e.g., `order.cancel()` not `order.setStatus(CANCELLED)`, `account.debit(amount)` not `account.updateBalance(-amount)`)
|
|
12
|
+
- [ ] **No technical jargon in domain layer** — Avoid names like Manager, Handler, Processor, Helper, Util, Data, Info in the domain layer
|
|
13
|
+
- [ ] **Consistent vocabulary** — Same concept uses the same name everywhere. No synonyms (e.g., don't mix "client" and "customer" for the same concept)
|
|
14
|
+
- [ ] **Language reflects current understanding** — Terms have been refined as the model evolved, not stuck with initial naive names
|
|
15
|
+
- [ ] **Module/package names tell a domain story** — Packages are organized by domain concept, not by pattern type (not `entities/`, `services/`, `repositories/`)
|
|
16
|
+
|
|
17
|
+
## 2. Layered Architecture
|
|
18
|
+
|
|
19
|
+
- [ ] **Four layers identifiable** — UI/Presentation, Application, Domain, Infrastructure are clearly separated
|
|
20
|
+
- [ ] **Domain layer has zero outward dependencies** — Domain classes don't import UI, Application, or Infrastructure classes
|
|
21
|
+
- [ ] **Infrastructure implements domain interfaces** — Dependency Inversion: domain defines interfaces (e.g., `OrderRepository`), infrastructure implements them (e.g., `JpaOrderRepository`)
|
|
22
|
+
- [ ] **Application layer is thin** — Application services coordinate but contain NO business logic. They manage transactions, security, and use-case flow
|
|
23
|
+
- [ ] **No domain logic in controllers** — UI/API controllers delegate to application services, never contain business rules
|
|
24
|
+
- [ ] **No domain logic in infrastructure** — Persistence logic doesn't enforce business rules; it only stores and retrieves
|
|
25
|
+
- [ ] **No circular dependencies** — Lower layers never depend on higher layers
|
|
26
|
+
|
|
27
|
+
## 3. Entities
|
|
28
|
+
|
|
29
|
+
- [ ] **Identity is explicit** — Entity has a clear, typed identifier (preferably a Value Object like `OrderId`, not a raw `String` or `long`)
|
|
30
|
+
- [ ] **Equality based on identity** — `equals()` and `hashCode()` use the identifier, not attributes
|
|
31
|
+
- [ ] **Encapsulates behavior** — Entity contains domain logic, not just getters and setters
|
|
32
|
+
- [ ] **Lifecycle management** — Entity handles its own state transitions and lifecycle events
|
|
33
|
+
- [ ] **Not overloaded** — Entity is focused on identity and core behavior; ancillary logic is in Value Objects or Services
|
|
34
|
+
- [ ] **Attributes that change are still same entity** — Design acknowledges that attributes can change while identity persists
|
|
35
|
+
|
|
36
|
+
## 4. Value Objects
|
|
37
|
+
|
|
38
|
+
- [ ] **Immutable** — No setters, no mutable state. All fields are final/readonly
|
|
39
|
+
- [ ] **Equality by attributes** — `equals()` compares all attributes, not reference identity
|
|
40
|
+
- [ ] **Rich behavior** — Value Objects contain domain logic (calculations, validations, transformations), not just data
|
|
41
|
+
- [ ] **Side-effect-free methods** — Operations return new Value Objects rather than modifying state
|
|
42
|
+
- [ ] **Used instead of primitives** — Domain concepts like money, dates, addresses, quantities are Value Objects, not raw types (no `double price`, `String email`, `int quantity`)
|
|
43
|
+
- [ ] **Freely shareable** — No aliasing bugs because they're immutable
|
|
44
|
+
- [ ] **Self-validating** — Value Object constructor validates invariants (e.g., `Email` rejects invalid format)
|
|
45
|
+
|
|
46
|
+
## 5. Aggregates
|
|
47
|
+
|
|
48
|
+
- [ ] **Clear root entity** — One Entity is the root with global identity; all access goes through it
|
|
49
|
+
- [ ] **Boundary is defined** — It's clear which Entities and Value Objects are inside the Aggregate
|
|
50
|
+
- [ ] **Invariants enforced by root** — The root ensures all business rules within the boundary are consistent
|
|
51
|
+
- [ ] **No external references to internals** — Outside code cannot hold direct references to internal entities. References are through the root only
|
|
52
|
+
- [ ] **Kept small** — Aggregate contains only what's needed for invariant enforcement. Other Aggregates are referenced by ID
|
|
53
|
+
- [ ] **One Aggregate per transaction** — Changes to one Aggregate are committed in one transaction. Cross-Aggregate changes are eventually consistent
|
|
54
|
+
- [ ] **Delete cascades from root** — Removing the root removes everything inside the boundary
|
|
55
|
+
- [ ] **Internal entities have local identity** — IDs of internal entities are meaningful only within the Aggregate
|
|
56
|
+
|
|
57
|
+
## 6. Repositories
|
|
58
|
+
|
|
59
|
+
- [ ] **Only for Aggregate roots** — No Repository exists for internal Aggregate entities or Value Objects
|
|
60
|
+
- [ ] **Collection-like interface** — API resembles an in-memory collection: add, remove, find, query
|
|
61
|
+
- [ ] **Interface in domain layer** — The Repository interface is defined in the domain layer
|
|
62
|
+
- [ ] **Implementation in infrastructure** — The concrete class (JPA, JDBC, etc.) is in the infrastructure layer
|
|
63
|
+
- [ ] **Returns whole Aggregates** — Queries return fully reconstituted Aggregates, not partial objects or DTOs
|
|
64
|
+
- [ ] **No persistence leakage** — Domain code has no awareness of SQL, ORM annotations, or storage details
|
|
65
|
+
- [ ] **Encapsulates query strategy** — Complex queries are behind well-named methods, not scattered SQL
|
|
66
|
+
- [ ] **Delegates to Factory for reconstitution** — Complex object rebuilding is handled by a Factory, not in the Repository itself
|
|
67
|
+
|
|
68
|
+
## 7. Factories
|
|
69
|
+
|
|
70
|
+
- [ ] **Used for complex creation** — If creation requires more than a simple constructor, a Factory exists
|
|
71
|
+
- [ ] **Atomic creation** — Factory produces a valid, consistent object or fails entirely (no partially created objects)
|
|
72
|
+
- [ ] **Invariants enforced at creation** — All Aggregate invariants are validated during Factory creation
|
|
73
|
+
- [ ] **Abstracts concrete types** — Client code depends on abstractions, not on the specific class the Factory creates
|
|
74
|
+
- [ ] **Reconstitution vs. creation distinguished** — Factories for loading from persistence don't re-validate business rules that only apply at creation time, and don't generate new IDs
|
|
75
|
+
|
|
76
|
+
## 8. Domain Services
|
|
77
|
+
|
|
78
|
+
- [ ] **Stateless** — Domain Services hold no mutable state between calls
|
|
79
|
+
- [ ] **Named in Ubiquitous Language** — Service name describes a domain operation (e.g., `TransferService`, `PricingService`)
|
|
80
|
+
- [ ] **Not overused** — Domain Services are the exception, not the rule. Most behavior belongs on Entities and Value Objects
|
|
81
|
+
- [ ] **Parameters and return types are domain objects** — Service interfaces use domain types, not primitives or infrastructure types
|
|
82
|
+
- [ ] **No anemic domain model** — If all logic is in Services and Entities are just data bags, the model is anemic
|
|
83
|
+
- [ ] **Distinguished from Application Services** — Domain Services contain business logic; Application Services coordinate use cases
|
|
84
|
+
|
|
85
|
+
## 9. Supple Design
|
|
86
|
+
|
|
87
|
+
- [ ] **Intention-Revealing Interfaces** — Can a developer understand what a class/method does without reading the implementation?
|
|
88
|
+
- [ ] **Side-Effect-Free Functions** — Complex logic lives in pure functions (especially on Value Objects). Commands are simple state changes
|
|
89
|
+
- [ ] **Assertions / Post-conditions** — Critical invariants are documented or enforced. Tests verify post-conditions
|
|
90
|
+
- [ ] **Conceptual Contours** — Object boundaries align with natural domain concepts. Things that change together are together
|
|
91
|
+
- [ ] **Standalone Classes** — Dependencies are minimized. Each class can be understood with minimal context
|
|
92
|
+
- [ ] **Closure of Operations** — Operations on a type return the same type where natural (e.g., `Money.add(Money): Money`)
|
|
93
|
+
- [ ] **Declarative style** — Where possible, the code describes WHAT should happen, not HOW (e.g., specifications, rules)
|
|
94
|
+
|
|
95
|
+
## 10. Specification Pattern
|
|
96
|
+
|
|
97
|
+
- [ ] **Business rules as objects** — Complex boolean conditions are modeled as Specification objects, not inline conditionals
|
|
98
|
+
- [ ] **isSatisfiedBy method** — Each Specification has a clear test method
|
|
99
|
+
- [ ] **Composable** — Specifications can be combined with AND, OR, NOT
|
|
100
|
+
- [ ] **Three use cases considered** — Validation (testing), Selection (querying), Building-to-order (generation)
|
|
101
|
+
- [ ] **Named in domain language** — `OverdueInvoiceSpecification`, not `InvoiceFilter42`
|
|
102
|
+
- [ ] **Implemented as Value Objects** — Specifications are immutable and side-effect-free
|
|
103
|
+
|
|
104
|
+
## 11. Strategic Design — Bounded Contexts
|
|
105
|
+
|
|
106
|
+
- [ ] **Contexts are explicitly defined** — Each Bounded Context has a clear name and boundary
|
|
107
|
+
- [ ] **One model per context** — No concept is defined differently within the same context
|
|
108
|
+
- [ ] **Context Map exists** — Relationships between contexts are documented
|
|
109
|
+
- [ ] **Integration patterns identified** — Each context-to-context relationship uses a named pattern (Shared Kernel, ACL, Conformist, etc.)
|
|
110
|
+
- [ ] **No model bleeding** — Types from one context don't appear in another context's domain layer
|
|
111
|
+
- [ ] **Team boundaries align** — Ideally, one team per Bounded Context
|
|
112
|
+
|
|
113
|
+
## 12. Integration Patterns
|
|
114
|
+
|
|
115
|
+
- [ ] **Anticorruption Layer present where needed** — External systems and legacy integrations are wrapped in ACL
|
|
116
|
+
- [ ] **ACL components identifiable** — Façade, Adapter, and Translator are present as needed
|
|
117
|
+
- [ ] **Domain interfaces define integration** — The domain layer defines what it needs; infrastructure implements it
|
|
118
|
+
- [ ] **Shared Kernel is minimal** — If two contexts share code, the shared portion is as small as possible with joint CI
|
|
119
|
+
- [ ] **Open Host Service is well-documented** — Public APIs have clear protocols and versioning
|
|
120
|
+
- [ ] **Published Language is formal** — Shared data formats are documented and standardized
|
|
121
|
+
- [ ] **Conformist choice is conscious** — If a team conforms to an upstream model, it's a deliberate decision with documented trade-offs
|
|
122
|
+
|
|
123
|
+
## 13. Distillation
|
|
124
|
+
|
|
125
|
+
- [ ] **Core Domain identified** — The team knows which part of the system is the competitive differentiator
|
|
126
|
+
- [ ] **Best talent on Core** — The most skilled developers are working on the Core Domain
|
|
127
|
+
- [ ] **Generic Subdomains simplified** — Non-core parts use off-the-shelf solutions, simpler models, or outsourced implementations
|
|
128
|
+
- [ ] **Domain Vision Statement exists** — A short document describes the Core Domain's value proposition
|
|
129
|
+
- [ ] **Core is highlighted** — Developers can quickly identify which code is Core Domain (via packages, annotations, or documentation)
|
|
130
|
+
- [ ] **Segregated Core** — Core Domain code is in its own module with minimal dependencies on supporting code
|
|
131
|
+
|
|
132
|
+
## 14. Large-Scale Structure
|
|
133
|
+
|
|
134
|
+
- [ ] **Structure serves the team** — If a large-scale structure exists, it helps developers navigate and make decisions
|
|
135
|
+
- [ ] **Not over-engineered** — Structure is as simple as possible. No structure is better than bad structure
|
|
136
|
+
- [ ] **Evolving** — Structure is allowed to change as understanding deepens
|
|
137
|
+
- [ ] **Responsibility Layers (if used)** — Layers have clear domain-level responsibilities and dependencies flow downward
|
|
138
|
+
- [ ] **Knowledge Level (if used)** — Configuration rules are separated from operational data in a meta-model
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Quick Review Workflow
|
|
143
|
+
|
|
144
|
+
1. **Start with Ubiquitous Language** — Read the code. Does it speak the domain? Can a domain expert recognize the concepts?
|
|
145
|
+
2. **Check architecture** — Are layers separated? Does the domain depend on nothing?
|
|
146
|
+
3. **Inspect building blocks** — Are Entities, Value Objects, Aggregates, Repositories, and Services used correctly?
|
|
147
|
+
4. **Evaluate design quality** — Is the design supple? Intention-revealing? Side-effect-free where possible?
|
|
148
|
+
5. **Assess strategic alignment** — Are Bounded Contexts defined? Is the Core Domain getting the most attention?
|
|
149
|
+
6. **Flag anti-patterns** — Look for anemic domain model, God Aggregates, primitive obsession, leaking infrastructure, missing ACL
|
|
150
|
+
|
|
151
|
+
## Severity Levels
|
|
152
|
+
|
|
153
|
+
| Severity | Description | Example |
|
|
154
|
+
|----------|------------|---------|
|
|
155
|
+
| **Critical** | Broken invariants, data corruption risk, no domain model | Anemic domain model, no Aggregate boundaries, infrastructure in domain |
|
|
156
|
+
| **High** | Incorrect pattern application, model integrity issues | Repository for non-root, mutable Value Objects, leaked internals |
|
|
157
|
+
| **Medium** | Design improvement opportunities | Missing Specifications, primitive types for concepts, overly large Aggregates |
|
|
158
|
+
| **Low** | Polish and naming refinements | Inconsistent naming, missing documentation, suboptimal module organization |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
DDD Scaffold — generates aggregate building blocks for a bounded context.
|
|
4
|
+
Usage: python scaffold.py <AggregateName> [--lang python|kotlin|java] [--output-dir ./]
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from string import Template
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Templates
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
PYTHON_AGGREGATE = Template('''\
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import List
|
|
20
|
+
from .${name}Id import ${name}Id
|
|
21
|
+
from .${name}Events import ${name}Created, ${name}Event
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ${name}:
|
|
26
|
+
"""Aggregate root for ${name}."""
|
|
27
|
+
|
|
28
|
+
id: ${name}Id
|
|
29
|
+
_events: List[${name}Event] = field(default_factory=list, init=False, repr=False)
|
|
30
|
+
|
|
31
|
+
# ------------------------------------------------------------------
|
|
32
|
+
# Factory method — the only sanctioned way to create a ${name}.
|
|
33
|
+
# ------------------------------------------------------------------
|
|
34
|
+
@classmethod
|
|
35
|
+
def create(cls, id: ${name}Id, **kwargs) -> "${name}":
|
|
36
|
+
instance = cls(id=id)
|
|
37
|
+
instance._check_invariants()
|
|
38
|
+
instance._record(${name}Created(aggregate_id=id))
|
|
39
|
+
return instance
|
|
40
|
+
|
|
41
|
+
# ------------------------------------------------------------------
|
|
42
|
+
# Commands — each mutates state and records an event.
|
|
43
|
+
# ------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
# def rename(self, new_name: str) -> None:
|
|
46
|
+
# if not new_name.strip():
|
|
47
|
+
# raise ValueError("Name must not be blank.")
|
|
48
|
+
# self.name = new_name
|
|
49
|
+
# self._record(${name}Renamed(aggregate_id=self.id, name=new_name))
|
|
50
|
+
|
|
51
|
+
# ------------------------------------------------------------------
|
|
52
|
+
# Domain events
|
|
53
|
+
# ------------------------------------------------------------------
|
|
54
|
+
def pull_events(self) -> List[${name}Event]:
|
|
55
|
+
"""Return and clear pending domain events."""
|
|
56
|
+
events, self._events = self._events, []
|
|
57
|
+
return events
|
|
58
|
+
|
|
59
|
+
def _record(self, event: ${name}Event) -> None:
|
|
60
|
+
self._events.append(event)
|
|
61
|
+
|
|
62
|
+
# ------------------------------------------------------------------
|
|
63
|
+
# Invariants
|
|
64
|
+
# ------------------------------------------------------------------
|
|
65
|
+
def _check_invariants(self) -> None:
|
|
66
|
+
"""Raise if the aggregate is in an invalid state."""
|
|
67
|
+
if self.id is None:
|
|
68
|
+
raise ValueError("${name} must have an ID.")
|
|
69
|
+
''')
|
|
70
|
+
|
|
71
|
+
PYTHON_ID = Template('''\
|
|
72
|
+
from __future__ import annotations
|
|
73
|
+
from dataclasses import dataclass
|
|
74
|
+
import uuid
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True)
|
|
78
|
+
class ${name}Id:
|
|
79
|
+
"""Value Object — identity of a ${name} aggregate."""
|
|
80
|
+
|
|
81
|
+
value: str
|
|
82
|
+
|
|
83
|
+
def __post_init__(self) -> None:
|
|
84
|
+
if not self.value or not self.value.strip():
|
|
85
|
+
raise ValueError("${name}Id must not be blank.")
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def generate(cls) -> "${name}Id":
|
|
89
|
+
return cls(value=str(uuid.uuid4()))
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_string(cls, raw: str) -> "${name}Id":
|
|
93
|
+
return cls(value=raw)
|
|
94
|
+
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
return self.value
|
|
97
|
+
''')
|
|
98
|
+
|
|
99
|
+
PYTHON_REPOSITORY = Template('''\
|
|
100
|
+
from __future__ import annotations
|
|
101
|
+
from abc import ABC, abstractmethod
|
|
102
|
+
from typing import Optional
|
|
103
|
+
from .${name}Id import ${name}Id
|
|
104
|
+
from .${name} import ${name}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ${name}Repository(ABC):
|
|
108
|
+
"""Port (interface) for ${name} persistence.
|
|
109
|
+
|
|
110
|
+
Concrete adapters live in the infrastructure layer.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
@abstractmethod
|
|
114
|
+
def find_by_id(self, id: ${name}Id) -> Optional[${name}]:
|
|
115
|
+
"""Return the aggregate or None if not found."""
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def save(self, aggregate: ${name}) -> None:
|
|
119
|
+
"""Persist all state changes."""
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def delete(self, id: ${name}Id) -> None:
|
|
123
|
+
"""Remove the aggregate."""
|
|
124
|
+
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def exists(self, id: ${name}Id) -> bool:
|
|
127
|
+
"""Check existence without loading the aggregate."""
|
|
128
|
+
''')
|
|
129
|
+
|
|
130
|
+
PYTHON_EVENTS = Template('''\
|
|
131
|
+
from __future__ import annotations
|
|
132
|
+
from dataclasses import dataclass, field
|
|
133
|
+
from datetime import datetime, timezone
|
|
134
|
+
from typing import ClassVar
|
|
135
|
+
from .${name}Id import ${name}Id
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@dataclass(frozen=True)
|
|
139
|
+
class ${name}Event:
|
|
140
|
+
"""Base class for all ${name} domain events."""
|
|
141
|
+
|
|
142
|
+
event_type: ClassVar[str]
|
|
143
|
+
aggregate_id: ${name}Id
|
|
144
|
+
occurred_at: datetime = field(
|
|
145
|
+
default_factory=lambda: datetime.now(tz=timezone.utc)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass(frozen=True)
|
|
150
|
+
class ${name}Created(${name}Event):
|
|
151
|
+
"""Raised when a new ${name} is created."""
|
|
152
|
+
|
|
153
|
+
event_type: ClassVar[str] = "${name}Created"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# Add more events below as your domain grows, for example:
|
|
157
|
+
# @dataclass(frozen=True)
|
|
158
|
+
# class ${name}Renamed(${name}Event):
|
|
159
|
+
# event_type: ClassVar[str] = "${name}Renamed"
|
|
160
|
+
# name: str = ""
|
|
161
|
+
''')
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# Kotlin templates
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
KOTLIN_AGGREGATE = Template('''\
|
|
168
|
+
package com.example.${lname}
|
|
169
|
+
|
|
170
|
+
import java.time.Instant
|
|
171
|
+
|
|
172
|
+
class ${name}(val id: ${name}Id) {
|
|
173
|
+
|
|
174
|
+
private val _events: MutableList<${name}Event> = mutableListOf()
|
|
175
|
+
val events: List<${name}Event> get() = _events.toList()
|
|
176
|
+
|
|
177
|
+
companion object {
|
|
178
|
+
/** Factory — the only way to create a valid ${name}. */
|
|
179
|
+
fun create(id: ${name}Id): ${name} {
|
|
180
|
+
val agg = ${name}(id)
|
|
181
|
+
agg.checkInvariants()
|
|
182
|
+
agg.record(${name}Created(aggregateId = id))
|
|
183
|
+
return agg
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Pull and clear pending domain events. */
|
|
188
|
+
fun pullEvents(): List<${name}Event> {
|
|
189
|
+
val copy = _events.toList()
|
|
190
|
+
_events.clear()
|
|
191
|
+
return copy
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private fun record(event: ${name}Event) { _events.add(event) }
|
|
195
|
+
|
|
196
|
+
private fun checkInvariants() {
|
|
197
|
+
// Add invariant assertions here.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
''')
|
|
201
|
+
|
|
202
|
+
KOTLIN_ID = Template('''\
|
|
203
|
+
package com.example.${lname}
|
|
204
|
+
|
|
205
|
+
import java.util.UUID
|
|
206
|
+
|
|
207
|
+
@JvmInline
|
|
208
|
+
value class ${name}Id(val value: String) {
|
|
209
|
+
init {
|
|
210
|
+
require(value.isNotBlank()) { "${name}Id must not be blank." }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
companion object {
|
|
214
|
+
fun generate(): ${name}Id = ${name}Id(UUID.randomUUID().toString())
|
|
215
|
+
fun of(raw: String): ${name}Id = ${name}Id(raw)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
override fun toString(): String = value
|
|
219
|
+
}
|
|
220
|
+
''')
|
|
221
|
+
|
|
222
|
+
KOTLIN_REPOSITORY = Template('''\
|
|
223
|
+
package com.example.${lname}
|
|
224
|
+
|
|
225
|
+
interface ${name}Repository {
|
|
226
|
+
fun findById(id: ${name}Id): ${name}?
|
|
227
|
+
fun save(aggregate: ${name})
|
|
228
|
+
fun delete(id: ${name}Id)
|
|
229
|
+
fun exists(id: ${name}Id): Boolean
|
|
230
|
+
}
|
|
231
|
+
''')
|
|
232
|
+
|
|
233
|
+
KOTLIN_EVENTS = Template('''\
|
|
234
|
+
package com.example.${lname}
|
|
235
|
+
|
|
236
|
+
import java.time.Instant
|
|
237
|
+
|
|
238
|
+
sealed class ${name}Event {
|
|
239
|
+
abstract val aggregateId: ${name}Id
|
|
240
|
+
abstract val occurredAt: Instant
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
data class ${name}Created(
|
|
244
|
+
override val aggregateId: ${name}Id,
|
|
245
|
+
override val occurredAt: Instant = Instant.now()
|
|
246
|
+
) : ${name}Event()
|
|
247
|
+
|
|
248
|
+
// Add more events as the domain grows:
|
|
249
|
+
// data class ${name}Renamed(
|
|
250
|
+
// override val aggregateId: ${name}Id,
|
|
251
|
+
// val name: String,
|
|
252
|
+
// override val occurredAt: Instant = Instant.now()
|
|
253
|
+
// ) : ${name}Event()
|
|
254
|
+
''')
|
|
255
|
+
|
|
256
|
+
# ---------------------------------------------------------------------------
|
|
257
|
+
# Java templates
|
|
258
|
+
# ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
JAVA_AGGREGATE = Template('''\
|
|
261
|
+
package com.example.${lname};
|
|
262
|
+
|
|
263
|
+
import java.util.ArrayList;
|
|
264
|
+
import java.util.Collections;
|
|
265
|
+
import java.util.List;
|
|
266
|
+
import java.util.Objects;
|
|
267
|
+
|
|
268
|
+
public final class ${name} {
|
|
269
|
+
|
|
270
|
+
private final ${name}Id id;
|
|
271
|
+
private final List<${name}Event> events = new ArrayList<>();
|
|
272
|
+
|
|
273
|
+
private ${name}(${name}Id id) {
|
|
274
|
+
this.id = Objects.requireNonNull(id, "id must not be null");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Factory — the only way to create a valid ${name}. */
|
|
278
|
+
public static ${name} create(${name}Id id) {
|
|
279
|
+
var agg = new ${name}(id);
|
|
280
|
+
agg.checkInvariants();
|
|
281
|
+
agg.record(new ${name}Created(id));
|
|
282
|
+
return agg;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public ${name}Id getId() { return id; }
|
|
286
|
+
|
|
287
|
+
/** Pull and clear pending domain events. */
|
|
288
|
+
public List<${name}Event> pullEvents() {
|
|
289
|
+
var copy = List.copyOf(events);
|
|
290
|
+
events.clear();
|
|
291
|
+
return copy;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private void record(${name}Event event) { events.add(event); }
|
|
295
|
+
|
|
296
|
+
private void checkInvariants() {
|
|
297
|
+
// Add invariant assertions here.
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
''')
|
|
301
|
+
|
|
302
|
+
JAVA_ID = Template('''\
|
|
303
|
+
package com.example.${lname};
|
|
304
|
+
|
|
305
|
+
import java.util.Objects;
|
|
306
|
+
import java.util.UUID;
|
|
307
|
+
|
|
308
|
+
public record ${name}Id(String value) {
|
|
309
|
+
|
|
310
|
+
public ${name}Id {
|
|
311
|
+
Objects.requireNonNull(value, "value must not be null");
|
|
312
|
+
if (value.isBlank()) throw new IllegalArgumentException("${name}Id must not be blank.");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
public static ${name}Id generate() { return new ${name}Id(UUID.randomUUID().toString()); }
|
|
316
|
+
public static ${name}Id of(String raw) { return new ${name}Id(raw); }
|
|
317
|
+
|
|
318
|
+
@Override public String toString() { return value; }
|
|
319
|
+
}
|
|
320
|
+
''')
|
|
321
|
+
|
|
322
|
+
JAVA_REPOSITORY = Template('''\
|
|
323
|
+
package com.example.${lname};
|
|
324
|
+
|
|
325
|
+
import java.util.Optional;
|
|
326
|
+
|
|
327
|
+
public interface ${name}Repository {
|
|
328
|
+
Optional<${name}> findById(${name}Id id);
|
|
329
|
+
void save(${name} aggregate);
|
|
330
|
+
void delete(${name}Id id);
|
|
331
|
+
boolean exists(${name}Id id);
|
|
332
|
+
}
|
|
333
|
+
''')
|
|
334
|
+
|
|
335
|
+
JAVA_EVENTS = Template('''\
|
|
336
|
+
package com.example.${lname};
|
|
337
|
+
|
|
338
|
+
import java.time.Instant;
|
|
339
|
+
|
|
340
|
+
public sealed interface ${name}Event permits ${name}Created {
|
|
341
|
+
${name}Id aggregateId();
|
|
342
|
+
Instant occurredAt();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
record ${name}Created(${name}Id aggregateId, Instant occurredAt) implements ${name}Event {
|
|
346
|
+
${name}Created(${name}Id aggregateId) { this(aggregateId, Instant.now()); }
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Add more events as the domain grows:
|
|
350
|
+
// record ${name}Renamed(${name}Id aggregateId, String name, Instant occurredAt)
|
|
351
|
+
// implements ${name}Event { ... }
|
|
352
|
+
''')
|
|
353
|
+
|
|
354
|
+
TEMPLATES = {
|
|
355
|
+
"python": {
|
|
356
|
+
"ext": "py",
|
|
357
|
+
"aggregate": PYTHON_AGGREGATE,
|
|
358
|
+
"id": PYTHON_ID,
|
|
359
|
+
"repository": PYTHON_REPOSITORY,
|
|
360
|
+
"events": PYTHON_EVENTS,
|
|
361
|
+
},
|
|
362
|
+
"kotlin": {
|
|
363
|
+
"ext": "kt",
|
|
364
|
+
"aggregate": KOTLIN_AGGREGATE,
|
|
365
|
+
"id": KOTLIN_ID,
|
|
366
|
+
"repository": KOTLIN_REPOSITORY,
|
|
367
|
+
"events": KOTLIN_EVENTS,
|
|
368
|
+
},
|
|
369
|
+
"java": {
|
|
370
|
+
"ext": "java",
|
|
371
|
+
"aggregate": JAVA_AGGREGATE,
|
|
372
|
+
"id": JAVA_ID,
|
|
373
|
+
"repository": JAVA_REPOSITORY,
|
|
374
|
+
"events": JAVA_EVENTS,
|
|
375
|
+
},
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def write(path: Path, content: str) -> None:
|
|
380
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
381
|
+
path.write_text(content)
|
|
382
|
+
print(f" Created: {path}")
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def scaffold(name: str, lang: str, output_dir: Path) -> None:
|
|
386
|
+
t = TEMPLATES[lang]
|
|
387
|
+
ext = t["ext"]
|
|
388
|
+
ctx = {"name": name, "lname": name.lower()}
|
|
389
|
+
files = {
|
|
390
|
+
f"{name}.{ext}": t["aggregate"],
|
|
391
|
+
f"{name}Id.{ext}": t["id"],
|
|
392
|
+
f"{name}Repository.{ext}": t["repository"],
|
|
393
|
+
f"{name}Events.{ext}": t["events"],
|
|
394
|
+
}
|
|
395
|
+
print(f"\nScaffolding DDD aggregate '{name}' ({lang}) in {output_dir}/\n")
|
|
396
|
+
for filename, tmpl in files.items():
|
|
397
|
+
write(output_dir / filename, tmpl.substitute(ctx))
|
|
398
|
+
|
|
399
|
+
print("\nNext steps:")
|
|
400
|
+
print(f" 1. Implement your domain commands inside {name}.{ext}")
|
|
401
|
+
print(f" 2. Add concrete repository in infrastructure/ (implements {name}Repository)")
|
|
402
|
+
print(f" 3. Publish events from {name}Events.{ext} via a message broker")
|
|
403
|
+
print(f" 4. Keep the aggregate free of infrastructure concerns\n")
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def main() -> None:
|
|
407
|
+
parser = argparse.ArgumentParser(description="Scaffold DDD aggregate building blocks.")
|
|
408
|
+
parser.add_argument("name", help="Aggregate name (PascalCase), e.g. Order")
|
|
409
|
+
parser.add_argument("--lang", choices=["python", "kotlin", "java"], default="python")
|
|
410
|
+
parser.add_argument("--output-dir", default=".", type=Path)
|
|
411
|
+
args = parser.parse_args()
|
|
412
|
+
|
|
413
|
+
if not args.name[0].isupper():
|
|
414
|
+
print(f"ERROR: AggregateName should be PascalCase (got '{args.name}').", file=sys.stderr)
|
|
415
|
+
sys.exit(1)
|
|
416
|
+
|
|
417
|
+
scaffold(args.name, args.lang, args.output_dir)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
if __name__ == "__main__":
|
|
421
|
+
main()
|