@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,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: domain-driven-design
|
|
3
|
+
version: "1.0"
|
|
4
|
+
license: MIT
|
|
5
|
+
tags: [architecture, ddd, modeling]
|
|
6
|
+
description: >
|
|
7
|
+
Design and review software using patterns from Eric Evans' "Domain-Driven
|
|
8
|
+
Design." Use for DDD tactical patterns (Entities, Value Objects, Aggregates,
|
|
9
|
+
Repositories, Factories, Domain Services), strategic patterns (Bounded Context,
|
|
10
|
+
Context Map, Anticorruption Layer, Shared Kernel, Open Host Service), supple
|
|
11
|
+
design (Intention-Revealing Interfaces, Side-Effect-Free Functions, Closure of
|
|
12
|
+
Operations), distillation (Core Domain, Segregated Core), large-scale structure
|
|
13
|
+
(Responsibility Layers, Knowledge Level), and Ubiquitous Language. Trigger on
|
|
14
|
+
"DDD", "domain-driven design", "bounded context", "aggregate root", "value
|
|
15
|
+
object", "entity", "repository pattern", "domain service", "anticorruption
|
|
16
|
+
layer", "context map", "ubiquitous language", "core domain", "specification
|
|
17
|
+
pattern", "supple design", "layered architecture", or "strategic design."
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Domain-Driven Design Skill
|
|
21
|
+
|
|
22
|
+
You are an expert software architect grounded in Eric Evans' *Domain-Driven Design*. You help developers in three modes: **Code Generation**, **Code Review**, and **Domain Migration Planning**.
|
|
23
|
+
|
|
24
|
+
- Generate/build/create/model/design → **Code Generation**
|
|
25
|
+
- Review/check/improve/audit/critique/refactor → **Code Review**
|
|
26
|
+
- Migrate to DDD/enrich domain/refactor toward DDD → **Migration Planning**
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Mode 1: Code Generation
|
|
31
|
+
|
|
32
|
+
### Pattern Selection
|
|
33
|
+
|
|
34
|
+
| Problem | Pattern |
|
|
35
|
+
|---------|---------|
|
|
36
|
+
| Application structure | Layered Architecture: UI → Application → Domain → Infrastructure |
|
|
37
|
+
| Object with identity and lifecycle | Entity (identity-based equality) |
|
|
38
|
+
| Descriptive concept without identity | Value Object (immutable, attribute-based equality, side-effect-free) |
|
|
39
|
+
| Enforce invariants across related objects | Aggregate (root entity, single boundary, transactional consistency) |
|
|
40
|
+
| Complex object creation | Factory (enforces all invariants atomically) |
|
|
41
|
+
| Collection-like persistence access | Repository (only for Aggregate roots) |
|
|
42
|
+
| Operation belonging to no single object | Domain Service (stateless, Ubiquitous Language name) |
|
|
43
|
+
| Composable business rules | Specification (isSatisfiedBy, and/or/not combinators) |
|
|
44
|
+
| Integration with external system | Anticorruption Layer (Façade + Adapter + Translator) |
|
|
45
|
+
| Shared model between teams | Shared Kernel (explicit joint ownership) |
|
|
46
|
+
| API for many consumers | Open Host Service + Published Language |
|
|
47
|
+
| Core competitive advantage | Core Domain distillation, Segregated Core |
|
|
48
|
+
|
|
49
|
+
### Code Generation Principles
|
|
50
|
+
|
|
51
|
+
- **Ubiquitous Language** — Class/method/variable names reflect domain terms. No "Manager", "Helper", "Processor" in the domain layer.
|
|
52
|
+
- **Layered Architecture** — Domain layer has zero dependencies on infrastructure. Infrastructure implements domain interfaces.
|
|
53
|
+
- **Entities** — Identity-based equality. Focused on lifecycle behavior, not data bags.
|
|
54
|
+
- **Value Objects** — Immutable. Attribute-based equality. Rich behavior (operations return new instances). Prefer over Entities when identity doesn't matter. Use for all domain concepts expressed as primitives: Money, OrderId, Email, Address, Dimensions.
|
|
55
|
+
- **Aggregates** — Single root entity. All external access through root. Enforce invariants at boundaries. Keep small; reference other Aggregates by ID only.
|
|
56
|
+
- **Repositories** — Collection-like interface. Domain layer defines the interface; infrastructure implements it. Only one Repository per Aggregate root.
|
|
57
|
+
- **Factories** — Encapsulate complex creation. Use private constructors + static factory methods to enforce invariants at creation time.
|
|
58
|
+
- **Domain Services** — Stateless. Named in Ubiquitous Language. Only for operations that genuinely don't belong on any Entity or Value Object.
|
|
59
|
+
- **Specification** — Business rules that combine, reuse, or query use `isSatisfiedBy()` with boolean combinators.
|
|
60
|
+
- **Anticorruption Layer** — Translate external models to your domain model. Domain interfaces know nothing about the external system.
|
|
61
|
+
|
|
62
|
+
### Code Generation Output
|
|
63
|
+
|
|
64
|
+
Produce: (1) Ubiquitous Language glossary, (2) Aggregate design with invariants, (3) Value Objects, (4) Domain Services, (5) Repository interfaces, (6) Factory methods, (7) Application Services for use-case orchestration.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Mode 2: Code Review
|
|
69
|
+
|
|
70
|
+
### Review Checklist
|
|
71
|
+
|
|
72
|
+
1. **Ubiquitous Language** — Do names reflect domain concepts? No technical jargon in the domain layer?
|
|
73
|
+
2. **Layered Architecture** — Does the domain layer import infrastructure/persistence/HTTP? Dependencies inverted?
|
|
74
|
+
3. **Entities vs Value Objects** — Are identity-less concepts modeled as immutable Value Objects? Is Primitive Obsession present (strings/ints used for domain concepts like orderId, Money, Email)?
|
|
75
|
+
4. **Aggregates** — Does the root enforce all invariants? Are external callers bypassing the root via setters? Are Aggregates small? Cross-Aggregate references by ID only?
|
|
76
|
+
5. **Repositories** — Only for Aggregate roots? Collection-like interface? Domain layer free of persistence details?
|
|
77
|
+
6. **Factories** — Is complex creation encapsulated with invariant enforcement? Private constructors + factory methods?
|
|
78
|
+
7. **Domain Services** — Truly stateless? Named in domain language? Not overused (anemic model symptom)?
|
|
79
|
+
8. **Supple Design** — Intention-Revealing Interfaces? Side-Effect-Free Functions? Conceptual Contours aligned?
|
|
80
|
+
9. **Strategic Design** — Bounded Contexts identified? Integration patterns applied?
|
|
81
|
+
10. **Distillation** — Core Domain getting the most design attention?
|
|
82
|
+
|
|
83
|
+
### Anti-Patterns to Flag
|
|
84
|
+
|
|
85
|
+
- **Anemic Domain Model / Transaction Script masquerading as DDD** — Entities with only getters/setters; all logic in service classes. Domain objects should have behavior, not just data. When service methods procedurally manipulate passive data objects, explicitly name this the *Transaction Script* anti-pattern.
|
|
86
|
+
- **God Aggregate / God Entity** — An Entity or Aggregate that has grown too large, accumulating too many fields and responsibilities. Count the fields: an Entity with 15+ fields is almost always a sign of missing Value Objects (Address, Dimensions, Money). Keep Aggregates small; reference by ID.
|
|
87
|
+
- **Repository for non-roots** — Only Aggregate roots get Repositories.
|
|
88
|
+
- **Leaking infrastructure into domain** — ORM annotations, HTTP/database imports in domain objects.
|
|
89
|
+
- **Missing Ubiquitous Language** — Technical names ("DataProcessor", "ItemManager") instead of domain terms.
|
|
90
|
+
- **Primitive Obsession** — Raw strings/ints for domain concepts instead of Value Objects (OrderId, Money, Email, Address).
|
|
91
|
+
- **Broken Aggregate invariants** — External code modifying Aggregate internals via setters, bypassing the root.
|
|
92
|
+
- **No Bounded Context boundaries** — One model serving all purposes; conflicting meanings for same terms.
|
|
93
|
+
- **Conformist when ACL is needed** — Blindly adopting another system's model instead of translating via ACL.
|
|
94
|
+
- **Smart UI / Fat Controller** — Domain logic in UI or application layer.
|
|
95
|
+
- **Missing Specifications** — Complex boolean rules hardcoded inline rather than composable Specification objects.
|
|
96
|
+
|
|
97
|
+
### Review Output Format
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
## Summary
|
|
101
|
+
Domain model assessment: patterns applied, overall DDD alignment.
|
|
102
|
+
|
|
103
|
+
## Strengths
|
|
104
|
+
DDD patterns correctly applied (be specific and genuine).
|
|
105
|
+
|
|
106
|
+
## Issues Found
|
|
107
|
+
- **What**: the problem
|
|
108
|
+
- **Why it matters**: modeling/maintainability/correctness risk
|
|
109
|
+
- **Pattern to apply**: which DDD pattern fixes this
|
|
110
|
+
- **Suggested fix**: concrete code change
|
|
111
|
+
|
|
112
|
+
## Recommendations
|
|
113
|
+
Priority-ordered improvements (critical first).
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Important for well-designed code:** When code correctly applies DDD patterns, say so explicitly in Strengths. Do NOT manufacture issues to seem thorough. Optional enhancements (domain events, additional Value Objects) must be clearly framed as enhancements, not defects.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Mode 3: Domain Migration Planning
|
|
121
|
+
|
|
122
|
+
Produce a phased migration plan when users want to incrementally move toward DDD.
|
|
123
|
+
|
|
124
|
+
**Phase 1 — Ubiquitous Language (zero-risk):** Rename classes/methods to domain terms. Build a glossary. Definition of done: domain expert can read names without a translator.
|
|
125
|
+
|
|
126
|
+
**Phase 2 — Value Objects (low-risk):** Extract Primitive Obsession into immutable Value Objects with validation in constructors (OrderId, Money, Email). Replace one class at a time.
|
|
127
|
+
|
|
128
|
+
**Phase 3 — Aggregate Boundaries (medium-risk):** Identify clusters that change together. Designate roots. Remove external setters; enforce invariants via domain methods. Cross-Aggregate references by ID only.
|
|
129
|
+
|
|
130
|
+
**Phase 4 — Repositories & Services (medium-risk):** Add Repository interfaces (domain layer) per Aggregate root. Move persistence to infrastructure. Extract stateless Domain Services for cross-entity operations.
|
|
131
|
+
|
|
132
|
+
**Phase 5 — Strategic Design (high-risk, optional):** Map Bounded Contexts. Build Anticorruption Layers for external integrations. Apply Strangler Fig pattern for monolith migration.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## General Guidelines
|
|
137
|
+
|
|
138
|
+
- Be practical, not dogmatic. DDD is most valuable for complex domains — not every CRUD app needs full DDD.
|
|
139
|
+
- **Ubiquitous Language is foundational.** No pattern compensates for misaligned naming.
|
|
140
|
+
- **Bounded Contexts before tactical patterns.** Strategic boundaries matter more than Entity vs Value Object classification.
|
|
141
|
+
- **Keep Aggregates small.** The most common DDD mistake is Aggregates that are too large.
|
|
142
|
+
- For deeper reference, see `references/patterns-catalog.md` (generation) and `references/review-checklist.md` (review).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"evals": [
|
|
3
|
+
{
|
|
4
|
+
"id": "eval-01-anemic-domain-model",
|
|
5
|
+
"prompt": "Review this code for a loan application system:\n\n```java\n// Domain object — just data\npublic class LoanApplication {\n private String applicantId;\n private BigDecimal requestedAmount;\n private int termMonths;\n private String status; // PENDING, APPROVED, REJECTED, DISBURSED\n private BigDecimal approvedAmount;\n private String rejectionReason;\n private LocalDate applicationDate;\n private String reviewerId;\n \n // getters and setters for all fields...\n}\n\n// All logic lives here\npublic class LoanApplicationService {\n private LoanApplicationRepository repository;\n private CreditScoreService creditScoreService;\n private RiskEngine riskEngine;\n \n public void approveLoan(String applicationId, String reviewerId, BigDecimal approvedAmount) {\n LoanApplication application = repository.findById(applicationId);\n if (!application.getStatus().equals(\"PENDING\")) {\n throw new IllegalStateException(\"Can only approve PENDING applications\");\n }\n if (approvedAmount.compareTo(application.getRequestedAmount()) > 0) {\n throw new IllegalArgumentException(\"Approved amount cannot exceed requested amount\");\n }\n application.setStatus(\"APPROVED\");\n application.setApprovedAmount(approvedAmount);\n application.setReviewerId(reviewerId);\n repository.save(application);\n }\n \n public void rejectLoan(String applicationId, String reviewerId, String reason) {\n LoanApplication application = repository.findById(applicationId);\n if (!application.getStatus().equals(\"PENDING\")) {\n throw new IllegalStateException(\"Can only reject PENDING applications\");\n }\n application.setStatus(\"REJECTED\");\n application.setRejectionReason(reason);\n application.setReviewerId(reviewerId);\n repository.save(application);\n }\n \n public boolean isEligibleForFastTrack(String applicationId) {\n LoanApplication application = repository.findById(applicationId);\n int creditScore = creditScoreService.getScore(application.getApplicantId());\n return creditScore > 750 && application.getRequestedAmount().compareTo(new BigDecimal(\"50000\")) <= 0;\n }\n}\n```",
|
|
6
|
+
"expectations": [
|
|
7
|
+
"Identifies this as an Anemic Domain Model — LoanApplication is a data bag with no behavior, all logic pushed into the service layer",
|
|
8
|
+
"Flags that approve() and reject() are domain operations that belong on the LoanApplication aggregate, not in the service",
|
|
9
|
+
"Notes that the invariant 'can only approve PENDING applications' is a business rule that LoanApplication should enforce internally",
|
|
10
|
+
"Flags Primitive Obsession: status as String instead of a LoanStatus enum or Value Object; applicantId and reviewerId as raw Strings instead of typed Value Objects (ApplicantId, ReviewerId)",
|
|
11
|
+
"Notes that requestedAmount and approvedAmount should be a Money Value Object (amount + currency), not a bare BigDecimal",
|
|
12
|
+
"Points out that the service directly sets fields via setters (setStatus, setApprovedAmount) — bypasses any invariant enforcement and breaks encapsulation",
|
|
13
|
+
"Recommends moving approve(ReviewerId reviewerId, Money approvedAmount) and reject(ReviewerId reviewerId, String reason) methods onto LoanApplication itself",
|
|
14
|
+
"Suggests that the service layer should be thin: load aggregate → call domain method → save",
|
|
15
|
+
"References the anti-pattern: 'Transaction Script masquerading as DDD — procedural service methods that manipulate passive data objects'"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "eval-02-missing-value-object",
|
|
20
|
+
"prompt": "Review this code for a shipping system:\n\n```java\npublic class Shipment {\n private String id;\n private String recipientName;\n private String streetAddress;\n private String city;\n private String stateCode;\n private String postalCode;\n private String countryCode;\n private String senderName;\n private String senderStreet;\n private String senderCity;\n private String senderStateCode;\n private String senderPostalCode;\n private String senderCountryCode;\n private double weightKg;\n private double lengthCm;\n private double widthCm;\n private double heightCm;\n private String trackingNumber;\n private String status;\n \n public double calculateVolumetricWeight() {\n return (lengthCm * widthCm * heightCm) / 5000.0;\n }\n \n public double getBillableWeight() {\n double volumetric = calculateVolumetricWeight();\n return Math.max(weightKg, volumetric);\n }\n \n public boolean isInternational() {\n return !senderCountryCode.equals(countryCode);\n }\n \n public String formatRecipientAddress() {\n return recipientName + \"\\n\" + streetAddress + \"\\n\" +\n city + \", \" + stateCode + \" \" + postalCode + \"\\n\" + countryCode;\n }\n \n public String formatSenderAddress() {\n return senderName + \"\\n\" + senderStreet + \"\\n\" +\n senderCity + \", \" + senderStateCode + \" \" + senderPostalCode + \"\\n\" + senderCountryCode;\n }\n}\n```",
|
|
21
|
+
"expectations": [
|
|
22
|
+
"Identifies the missing Value Objects: Address (street, city, state, postal code, country) is a natural concept that is duplicated for sender and recipient",
|
|
23
|
+
"Identifies a second Value Object: Dimensions (length, width, height) with volumetric weight behavior",
|
|
24
|
+
"Identifies a third Value Object candidate: Weight (value + unit — kg is baked into the field name but not the type)",
|
|
25
|
+
"Notes that the Shipment Entity is bloated with 19 fields — a God Entity collecting too many responsibilities",
|
|
26
|
+
"Points out that formatRecipientAddress() and formatSenderAddress() are the same logic duplicated with different field names — a symptom of not having an Address Value Object",
|
|
27
|
+
"Notes that calculateVolumetricWeight() belongs on a Dimensions Value Object, not on Shipment",
|
|
28
|
+
"Flags Primitive Obsession: all fields are raw strings/doubles instead of typed domain concepts",
|
|
29
|
+
"Recommends concrete refactoring: extract Address, Dimensions, Weight Value Objects; Shipment holds Address recipient, Address sender, Dimensions, Weight",
|
|
30
|
+
"Notes Value Objects should be immutable with attribute-based equality"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "eval-03-well-designed-aggregate",
|
|
35
|
+
"prompt": "Review this domain model for a bank account system:\n\n```java\npublic class AccountId {\n private final String value;\n \n public AccountId(String value) {\n if (value == null || value.isBlank()) throw new IllegalArgumentException(\"AccountId cannot be blank\");\n this.value = value;\n }\n \n public String getValue() { return value; }\n \n @Override public boolean equals(Object o) {\n if (this == o) return true;\n if (!(o instanceof AccountId)) return false;\n return value.equals(((AccountId) o).value);\n }\n @Override public int hashCode() { return value.hashCode(); }\n}\n\npublic class Money {\n private final BigDecimal amount;\n private final Currency currency;\n \n public Money(BigDecimal amount, Currency currency) {\n if (amount == null || currency == null) throw new IllegalArgumentException();\n this.amount = amount;\n this.currency = currency;\n }\n \n public Money add(Money other) {\n if (!this.currency.equals(other.currency)) throw new IllegalArgumentException(\"Currency mismatch\");\n return new Money(this.amount.add(other.amount), this.currency);\n }\n \n public Money subtract(Money other) {\n if (!this.currency.equals(other.currency)) throw new IllegalArgumentException(\"Currency mismatch\");\n return new Money(this.amount.subtract(other.amount), this.currency);\n }\n \n public boolean isNegative() { return amount.compareTo(BigDecimal.ZERO) < 0; }\n \n // equals, hashCode based on amount and currency\n}\n\npublic class BankAccount {\n private final AccountId id;\n private Money balance;\n private final List<Transaction> ledger;\n private AccountStatus status;\n \n private BankAccount(AccountId id, Money initialBalance) {\n this.id = id;\n this.balance = initialBalance;\n this.ledger = new ArrayList<>();\n this.status = AccountStatus.ACTIVE;\n }\n \n public static BankAccount open(AccountId id, Money initialDeposit) {\n if (initialDeposit.isNegative()) throw new IllegalArgumentException(\"Initial deposit must be positive\");\n return new BankAccount(id, initialDeposit);\n }\n \n public void deposit(Money amount) {\n if (status != AccountStatus.ACTIVE) throw new IllegalStateException(\"Cannot deposit to a \" + status + \" account\");\n if (amount.isNegative()) throw new IllegalArgumentException(\"Deposit amount must be positive\");\n this.balance = this.balance.add(amount);\n ledger.add(Transaction.credit(amount));\n }\n \n public void withdraw(Money amount) {\n if (status != AccountStatus.ACTIVE) throw new IllegalStateException(\"Cannot withdraw from a \" + status + \" account\");\n if (amount.isNegative()) throw new IllegalArgumentException(\"Withdrawal amount must be positive\");\n Money newBalance = this.balance.subtract(amount);\n if (newBalance.isNegative()) throw new DomainException(\"Insufficient funds\");\n this.balance = newBalance;\n ledger.add(Transaction.debit(amount));\n }\n \n public void close() {\n if (!balance.equals(Money.zero(balance.getCurrency()))) {\n throw new DomainException(\"Cannot close account with non-zero balance\");\n }\n this.status = AccountStatus.CLOSED;\n }\n \n public Money getBalance() { return balance; }\n public List<Transaction> getLedger() { return Collections.unmodifiableList(ledger); }\n public AccountId getId() { return id; }\n}\n```",
|
|
36
|
+
"expectations": [
|
|
37
|
+
"Recognizes this as a well-designed Aggregate and says so explicitly",
|
|
38
|
+
"Praises AccountId and Money as properly modeled Value Objects — immutable, attribute-based equality, validated in constructor",
|
|
39
|
+
"Praises the factory method BankAccount.open() for enforcing invariants at creation time",
|
|
40
|
+
"Praises that all business rules (no overdraft, no deposit to closed account, cannot close with balance) are enforced inside the aggregate rather than in a service layer",
|
|
41
|
+
"Praises that getLedger() returns an unmodifiable list — protecting the internal collection from external mutation",
|
|
42
|
+
"Praises the closed constructor — external code cannot create a BankAccount without going through the factory method",
|
|
43
|
+
"Does NOT manufacture fake issues just to have something to say",
|
|
44
|
+
"May offer optional suggestions (domain events for deposit/withdrawal, separate TransactionId Value Object) but clearly frames them as enhancements, not defects"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# After
|
|
2
|
+
|
|
3
|
+
`Order` becomes a rich aggregate root that owns its invariants and exposes domain-language behavior; `OrderService` becomes a thin application-layer coordinator.
|
|
4
|
+
|
|
5
|
+
```java
|
|
6
|
+
// Rich aggregate root — enforces all invariants internally
|
|
7
|
+
public class Order {
|
|
8
|
+
private final OrderId id;
|
|
9
|
+
private final List<OrderLine> lines = new ArrayList<>();
|
|
10
|
+
private OrderStatus status;
|
|
11
|
+
private Money total;
|
|
12
|
+
|
|
13
|
+
private Order(OrderId id, CustomerId customerId) {
|
|
14
|
+
this.id = id;
|
|
15
|
+
this.status = OrderStatus.PENDING;
|
|
16
|
+
this.total = Money.ZERO;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public static Order place(OrderId id, CustomerId customerId) {
|
|
20
|
+
return new Order(id, customerId);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public void addItem(Product product, int quantity) {
|
|
24
|
+
Validate.isTrue(quantity > 0, "Quantity must be positive");
|
|
25
|
+
Validate.isTrue(status == OrderStatus.PENDING, "Cannot modify a non-pending order");
|
|
26
|
+
lines.add(OrderLine.of(product.getId(), product.getPrice(), quantity));
|
|
27
|
+
recalculateTotal();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public void confirm() {
|
|
31
|
+
if (lines.isEmpty()) {
|
|
32
|
+
throw new DomainException("Cannot confirm an order with no items");
|
|
33
|
+
}
|
|
34
|
+
this.status = OrderStatus.CONFIRMED;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public void cancel() {
|
|
38
|
+
if (!status.isCancellable()) {
|
|
39
|
+
throw new DomainException("Order in status " + status + " cannot be cancelled");
|
|
40
|
+
}
|
|
41
|
+
this.status = OrderStatus.CANCELLED;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private void recalculateTotal() {
|
|
45
|
+
this.total = lines.stream()
|
|
46
|
+
.map(OrderLine::lineTotal)
|
|
47
|
+
.reduce(Money.ZERO, Money::add);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Getters return immutable views only
|
|
51
|
+
public List<OrderLine> lines() { return Collections.unmodifiableList(lines); }
|
|
52
|
+
public Money total() { return total; }
|
|
53
|
+
public OrderStatus status() { return status; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Application service is thin — orchestrates, does not contain business rules
|
|
57
|
+
public class PlaceOrderUseCase {
|
|
58
|
+
private final OrderRepository orderRepository;
|
|
59
|
+
private final ProductRepository productRepository;
|
|
60
|
+
|
|
61
|
+
public OrderId execute(PlaceOrderCommand cmd) {
|
|
62
|
+
Order order = Order.place(OrderId.generate(), cmd.customerId());
|
|
63
|
+
for (var item : cmd.requestedItems()) {
|
|
64
|
+
Product product = productRepository.findById(item.productId())
|
|
65
|
+
.orElseThrow(() -> new ProductNotFoundException(item.productId()));
|
|
66
|
+
order.addItem(product, item.quantity());
|
|
67
|
+
}
|
|
68
|
+
order.confirm();
|
|
69
|
+
orderRepository.save(order);
|
|
70
|
+
return order.getId();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Key improvements:
|
|
76
|
+
- Business rules (`confirm`, `cancel`, `addItem`) live inside `Order` — the anemic model anti-pattern is eliminated
|
|
77
|
+
- `Order.status.isCancellable()` encapsulates the cancellation-eligibility rule in an enum method rather than in a service
|
|
78
|
+
- External code cannot corrupt the aggregate: `lines` is returned as an unmodifiable view; `total` is always recalculated internally
|
|
79
|
+
- `OrderService` is renamed to `PlaceOrderUseCase` per the Ubiquitous Language (application command, not a generic "service")
|
|
80
|
+
- `Money` is a Value Object, preventing `double` arithmetic errors on financial amounts (Primitive Obsession eliminated)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Before
|
|
2
|
+
|
|
3
|
+
An anemic domain model where the `Order` class is a passive data container with no behavior, and all business logic is scattered across `OrderService`.
|
|
4
|
+
|
|
5
|
+
```java
|
|
6
|
+
// Anemic entity — just a data bag
|
|
7
|
+
public class Order {
|
|
8
|
+
public Long id;
|
|
9
|
+
public List<OrderLine> lines;
|
|
10
|
+
public String status;
|
|
11
|
+
public BigDecimal totalAmount;
|
|
12
|
+
public String customerId;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// All logic lives in the service
|
|
16
|
+
public class OrderService {
|
|
17
|
+
|
|
18
|
+
public void addItem(Order order, Product product, int quantity) {
|
|
19
|
+
OrderLine line = new OrderLine();
|
|
20
|
+
line.productId = product.id;
|
|
21
|
+
line.quantity = quantity;
|
|
22
|
+
line.unitPrice = product.price;
|
|
23
|
+
order.lines.add(line);
|
|
24
|
+
order.totalAmount = order.lines.stream()
|
|
25
|
+
.map(l -> l.unitPrice.multiply(BigDecimal.valueOf(l.quantity)))
|
|
26
|
+
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public void cancel(Order order) {
|
|
30
|
+
if (!order.status.equals("PENDING") && !order.status.equals("CONFIRMED")) {
|
|
31
|
+
throw new IllegalStateException("Cannot cancel");
|
|
32
|
+
}
|
|
33
|
+
order.status = "CANCELLED";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public void confirm(Order order) {
|
|
37
|
+
if (order.lines == null || order.lines.isEmpty()) {
|
|
38
|
+
throw new IllegalStateException("Cannot confirm empty order");
|
|
39
|
+
}
|
|
40
|
+
order.status = "CONFIRMED";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|