@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,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"evals": [
|
|
3
|
+
{
|
|
4
|
+
"id": "eval-01-distributed-transaction-no-saga",
|
|
5
|
+
"prompt": "Review this microservices code for an e-commerce checkout flow:\n\n```java\n// OrderService — orchestrates the entire checkout in one HTTP transaction\n@RestController\npublic class CheckoutController {\n private final OrderRepository orderRepository;\n private final InventoryServiceClient inventoryClient;\n private final PaymentServiceClient paymentClient;\n private final ShippingServiceClient shippingClient;\n private final NotificationServiceClient notificationClient;\n \n @PostMapping(\"/checkout\")\n @Transactional // <-- spans the entire method\n public OrderConfirmation checkout(@RequestBody CheckoutRequest request) {\n // Step 1: Reserve inventory\n inventoryClient.reserveItems(request.getItems());\n \n // Step 2: Create order in our DB\n Order order = orderRepository.save(new Order(request));\n \n // Step 3: Charge payment\n PaymentResult payment = paymentClient.charge(request.getPaymentInfo(), order.getTotal());\n \n // Step 4: Create shipping label\n ShippingLabel label = shippingClient.createLabel(order.getId(), request.getAddress());\n \n // Step 5: Send confirmation email\n notificationClient.sendConfirmation(request.getEmail(), order.getId());\n \n order.setShippingLabel(label.getTrackingNumber());\n order.setStatus(\"CONFIRMED\");\n return new OrderConfirmation(order.getId(), label.getTrackingNumber());\n }\n}\n```",
|
|
6
|
+
"expectations": [
|
|
7
|
+
"Identifies the core anti-pattern: a distributed transaction where @Transactional spans HTTP calls to 3 external services — this does not work as intended",
|
|
8
|
+
"Explains why @Transactional cannot span HTTP calls: the ACID transaction boundary is the local database only; remote service calls are not rolled back if a later step fails",
|
|
9
|
+
"Identifies the failure scenario: if shippingClient.createLabel() throws after paymentClient.charge() succeeds, the customer is charged but never gets a shipping label — and inventory is reserved but no rollback occurs",
|
|
10
|
+
"Names the pattern that is needed: the Saga pattern (orchestration-based) to handle this cross-service workflow",
|
|
11
|
+
"Explains compensating transactions: each step needs a corresponding undo — if shipping fails, a ReversePaymentSaga step should issue a refund and a ReleaseInventory step should free reserved stock",
|
|
12
|
+
"Notes the synchronous chain anti-pattern: OrderService → Inventory → Payment → Shipping → Notification is a fragile chain — if Notification is slow or down, the entire checkout hangs",
|
|
13
|
+
"Recommends making notifications asynchronous via a message queue (fire-and-forget)",
|
|
14
|
+
"Suggests using Saga orchestration with explicit saga states (PENDING_INVENTORY, PENDING_PAYMENT, PENDING_SHIPPING, CONFIRMED, FAILED) stored durably"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"id": "eval-02-shared-database",
|
|
19
|
+
"prompt": "Review this microservices architecture:\n\n```java\n// ProductService — manages product catalog\n@Service\npublic class ProductService {\n @Autowired\n private JdbcTemplate jdbc; // Connects to: jdbc:postgresql://shared-db:5432/platform\n \n public Product getProduct(Long id) {\n return jdbc.queryForObject(\n \"SELECT * FROM products WHERE id = ?\",\n new ProductRowMapper(), id);\n }\n \n public void updatePrice(Long productId, BigDecimal newPrice) {\n jdbc.update(\"UPDATE products SET price = ?, updated_at = NOW() WHERE id = ?\",\n newPrice, productId);\n }\n}\n\n// OrderService — in a SEPARATE deployable service/process\n@Service\npublic class OrderService {\n @Autowired\n private JdbcTemplate jdbc; // ALSO connects to: jdbc:postgresql://shared-db:5432/platform\n \n public Order createOrder(CreateOrderRequest req) {\n // Directly reads from products table owned by ProductService\n BigDecimal price = jdbc.queryForObject(\n \"SELECT price FROM products WHERE id = ?\",\n BigDecimal.class, req.getProductId());\n \n // Directly joins across service boundaries\n List<OrderLine> lines = jdbc.query(\n \"SELECT o.*, p.name, p.sku FROM orders o JOIN products p ON o.product_id = p.id WHERE o.customer_id = ?\",\n new OrderLineRowMapper(), req.getCustomerId());\n \n jdbc.update(\"INSERT INTO orders (product_id, customer_id, price) VALUES (?, ?, ?)\",\n req.getProductId(), req.getCustomerId(), price);\n return buildOrder(lines);\n }\n}\n```",
|
|
20
|
+
"expectations": [
|
|
21
|
+
"Identifies the Shared Database anti-pattern: both ProductService and OrderService connect to the same database and directly access each other's tables",
|
|
22
|
+
"Explains why this is problematic: ProductService cannot change its products table schema without coordinating with OrderService — the services are coupled at the data layer despite being separate deployables",
|
|
23
|
+
"Flags that OrderService reads from products — meaning ProductService's internal data model is now OrderService's public API; any rename or restructure breaks OrderService",
|
|
24
|
+
"Flags the cross-service JOIN in SQL: joins across service boundaries are a strong indicator of incorrect service decomposition or a shared database violation",
|
|
25
|
+
"Explains the correct pattern: each service owns its own database/schema; OrderService should get product information by calling ProductService's API (synchronous) or by maintaining a local read model via events (CQRS)",
|
|
26
|
+
"Recommends that OrderService should store the price at order-creation time (denormalized) rather than joining live to the products table — price at time of purchase is correct business behavior",
|
|
27
|
+
"Notes that this architecture makes independent deployment impossible: upgrading ProductService's database schema requires a coordinated deployment with OrderService"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "eval-03-event-driven-saga",
|
|
32
|
+
"prompt": "Review this saga implementation for a ride-sharing trip booking:\n\n```java\n// TripSaga — orchestrator (Spring Saga / Axon-style pseudocode)\n@Saga\npublic class BookTripSaga {\n private String tripId;\n private String driverId;\n private String paymentAuthId;\n \n @StartSaga\n @SagaEventHandler(associationProperty = \"tripId\")\n public void on(TripRequestedEvent event) {\n this.tripId = event.getTripId();\n commandGateway.send(new FindAvailableDriverCommand(event.getTripId(), event.getLocation()));\n }\n \n @SagaEventHandler(associationProperty = \"tripId\")\n public void on(DriverAssignedEvent event) {\n this.driverId = event.getDriverId();\n commandGateway.send(new AuthorizePaymentCommand(event.getTripId(), event.getEstimatedFare()));\n }\n \n @SagaEventHandler(associationProperty = \"tripId\")\n public void on(PaymentAuthorizedEvent event) {\n this.paymentAuthId = event.getAuthorizationId();\n commandGateway.send(new ConfirmTripCommand(tripId, driverId));\n }\n \n @SagaEventHandler(associationProperty = \"tripId\")\n public void on(NoDriverAvailableEvent event) {\n commandGateway.send(new CancelTripCommand(tripId, \"No drivers available\"));\n SagaLifecycle.end();\n }\n \n @SagaEventHandler(associationProperty = \"tripId\")\n public void on(PaymentDeclinedEvent event) {\n // Compensate: release the reserved driver\n commandGateway.send(new ReleaseDriverCommand(driverId, tripId));\n commandGateway.send(new CancelTripCommand(tripId, \"Payment declined\"));\n SagaLifecycle.end();\n }\n \n @EndSaga\n @SagaEventHandler(associationProperty = \"tripId\")\n public void on(TripConfirmedEvent event) {\n // Saga complete — trip is live\n }\n}\n```",
|
|
33
|
+
"expectations": [
|
|
34
|
+
"Recognizes this as a well-designed orchestration-based saga and says so explicitly",
|
|
35
|
+
"Praises the compensating transactions: PaymentDeclinedEvent triggers both ReleaseDriverCommand and CancelTripCommand — each forward step has a corresponding undo",
|
|
36
|
+
"Praises that the saga stores intermediate state (driverId, paymentAuthId) to enable compensation — the saga has the information it needs to undo each step",
|
|
37
|
+
"Praises that the saga is event-driven: each step reacts to an event rather than making synchronous calls — this decouples the booking flow from service availability",
|
|
38
|
+
"Praises explicit saga lifecycle management: SagaLifecycle.end() on failure paths and @EndSaga on success ensure the saga doesn't leak memory",
|
|
39
|
+
"Praises modeling failure paths as first-class events (NoDriverAvailableEvent, PaymentDeclinedEvent) — not exceptions, but domain events the saga handles explicitly",
|
|
40
|
+
"Does NOT manufacture fake issues just to have something to say",
|
|
41
|
+
"May offer optional suggestions (timeout handling if DriverAssignedEvent never arrives, idempotent command handlers) but frames them as additional robustness, not defects in the current design"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# After
|
|
2
|
+
|
|
3
|
+
`InventoryService` owns its own read model, populated by consuming `OrderLineItemAdded` domain events published by the Order Service — no shared database access.
|
|
4
|
+
|
|
5
|
+
```java
|
|
6
|
+
// --- Order Service publishes domain events (its own codebase) ---
|
|
7
|
+
|
|
8
|
+
@DomainEvent
|
|
9
|
+
public record OrderLineItemAdded(
|
|
10
|
+
String orderId,
|
|
11
|
+
String productId,
|
|
12
|
+
int quantity,
|
|
13
|
+
Instant occurredAt
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
// Order Service publishes this event after saving the order aggregate
|
|
17
|
+
orderEventPublisher.publish(new OrderLineItemAdded(
|
|
18
|
+
order.getId(), line.getProductId(), line.getQuantity(), Instant.now()
|
|
19
|
+
));
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
// --- Inventory Service: owns its own read model, no shared DB ---
|
|
23
|
+
|
|
24
|
+
// Private denormalized table — owned exclusively by Inventory Service
|
|
25
|
+
@Entity @Table(name = "product_weekly_sales")
|
|
26
|
+
public class ProductWeeklySales {
|
|
27
|
+
@Id private String productId;
|
|
28
|
+
private int unitsSoldLast7Days;
|
|
29
|
+
private Instant lastUpdated;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Component
|
|
33
|
+
public class OrderLineItemAddedConsumer {
|
|
34
|
+
|
|
35
|
+
@KafkaListener(topics = "order.events", groupId = "inventory-service")
|
|
36
|
+
public void handle(OrderLineItemAdded event) {
|
|
37
|
+
// Idempotent: uses event's occurredAt to filter stale events
|
|
38
|
+
if (event.occurredAt().isBefore(Instant.now().minus(7, DAYS))) return;
|
|
39
|
+
|
|
40
|
+
weeklySalesRepository.incrementUnitsSold(event.productId(), event.quantity());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@RestController @RequestMapping("/inventory")
|
|
45
|
+
public class InventoryController {
|
|
46
|
+
|
|
47
|
+
@GetMapping("/reorder-candidates")
|
|
48
|
+
public List<ReorderItem> getReorderCandidates() {
|
|
49
|
+
// Queries Inventory Service's OWN database — no cross-service DB access
|
|
50
|
+
return weeklySalesRepository.findAll().stream()
|
|
51
|
+
.filter(sales -> {
|
|
52
|
+
int stockLevel = stockRepository.getLevel(sales.getProductId());
|
|
53
|
+
return stockLevel < sales.getUnitsSoldLast7Days() * 2;
|
|
54
|
+
})
|
|
55
|
+
.map(sales -> new ReorderItem(
|
|
56
|
+
sales.getProductId(),
|
|
57
|
+
sales.getUnitsSoldLast7Days() * 3 - stockRepository.getLevel(sales.getProductId())
|
|
58
|
+
))
|
|
59
|
+
.toList();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Key improvements:
|
|
65
|
+
- Each service owns its database — `InventoryService` never touches the `orders` schema (Database per Service pattern)
|
|
66
|
+
- `OrderLineItemAdded` domain event decouples the services; Order Service does not know Inventory Service exists
|
|
67
|
+
- `ProductWeeklySales` is a denormalized read model maintained by consuming events — a lightweight CQRS view
|
|
68
|
+
- The Kafka consumer is idempotent: events outside the 7-day window are skipped, making re-delivery safe
|
|
69
|
+
- Deleting the coupling to `sharedDataSource` eliminates the risk that a schema change in Order Service breaks Inventory Service at runtime
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Before
|
|
2
|
+
|
|
3
|
+
An `InventoryService` that directly queries the `orders` database table owned by another service, creating tight coupling and a shared-database anti-pattern.
|
|
4
|
+
|
|
5
|
+
```java
|
|
6
|
+
@RestController
|
|
7
|
+
@RequestMapping("/inventory")
|
|
8
|
+
public class InventoryController {
|
|
9
|
+
|
|
10
|
+
@Autowired
|
|
11
|
+
private DataSource sharedDataSource; // connected to the orders DB
|
|
12
|
+
|
|
13
|
+
@GetMapping("/reorder-candidates")
|
|
14
|
+
public List<ReorderItem> getReorderCandidates() {
|
|
15
|
+
List<ReorderItem> candidates = new ArrayList<>();
|
|
16
|
+
|
|
17
|
+
// Directly querying the Order Service's database table
|
|
18
|
+
try (Connection conn = sharedDataSource.getConnection();
|
|
19
|
+
PreparedStatement ps = conn.prepareStatement(
|
|
20
|
+
"SELECT product_id, SUM(quantity) as sold_qty " +
|
|
21
|
+
"FROM orders.order_lines " +
|
|
22
|
+
"WHERE created_at > NOW() - INTERVAL 7 DAY " +
|
|
23
|
+
"GROUP BY product_id")) {
|
|
24
|
+
|
|
25
|
+
ResultSet rs = ps.executeQuery();
|
|
26
|
+
while (rs.next()) {
|
|
27
|
+
String productId = rs.getString("product_id");
|
|
28
|
+
int soldQty = rs.getInt("sold_qty");
|
|
29
|
+
int stockLevel = getStockLevel(productId);
|
|
30
|
+
if (stockLevel < soldQty * 2) {
|
|
31
|
+
candidates.add(new ReorderItem(productId, soldQty * 3 - stockLevel));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (SQLException e) {
|
|
35
|
+
throw new RuntimeException(e);
|
|
36
|
+
}
|
|
37
|
+
return candidates;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# Microservices Patterns Catalog
|
|
2
|
+
|
|
3
|
+
Comprehensive reference of patterns from Chris Richardson's *Microservices Patterns*.
|
|
4
|
+
Organized by problem category. Read the section relevant to the code you're generating.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
1. [Decomposition Patterns](#decomposition-patterns)
|
|
11
|
+
2. [Communication Patterns](#communication-patterns)
|
|
12
|
+
3. [API Gateway Patterns](#api-gateway-patterns)
|
|
13
|
+
4. [Transaction Management — Sagas](#transaction-management--sagas)
|
|
14
|
+
5. [Business Logic Patterns](#business-logic-patterns)
|
|
15
|
+
6. [Event Sourcing](#event-sourcing)
|
|
16
|
+
7. [Query Patterns](#query-patterns)
|
|
17
|
+
8. [Testing Patterns](#testing-patterns)
|
|
18
|
+
9. [Deployment Patterns](#deployment-patterns)
|
|
19
|
+
10. [Observability Patterns](#observability-patterns)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Decomposition Patterns
|
|
24
|
+
|
|
25
|
+
### Decompose by Business Capability
|
|
26
|
+
|
|
27
|
+
Map services to what the organization *does*. Business capabilities are stable
|
|
28
|
+
over time even as org structure changes.
|
|
29
|
+
|
|
30
|
+
- Identify top-level capabilities (Order Management, Delivery, Billing, etc.)
|
|
31
|
+
- Each capability becomes a candidate service
|
|
32
|
+
- Services own the data for their capability
|
|
33
|
+
|
|
34
|
+
**When to use**: Starting a new microservices project or breaking up a monolith.
|
|
35
|
+
|
|
36
|
+
### Decompose by Subdomain (DDD)
|
|
37
|
+
|
|
38
|
+
Use Domain-Driven Design to identify bounded contexts. Each bounded context
|
|
39
|
+
becomes a service.
|
|
40
|
+
|
|
41
|
+
- Core subdomains: the competitive advantage (invest the most here)
|
|
42
|
+
- Supporting subdomains: necessary but not differentiating
|
|
43
|
+
- Generic subdomains: solved problems (use off-the-shelf solutions)
|
|
44
|
+
|
|
45
|
+
**When to use**: Complex domain where business capability mapping isn't granular enough.
|
|
46
|
+
|
|
47
|
+
### Strangler Fig Pattern
|
|
48
|
+
|
|
49
|
+
Incrementally migrate from monolith to microservices by building new functionality
|
|
50
|
+
as services and gradually routing traffic away from the monolith.
|
|
51
|
+
|
|
52
|
+
- Stand up new service alongside monolith
|
|
53
|
+
- Route specific requests to new service
|
|
54
|
+
- Gradually migrate functionality
|
|
55
|
+
- Eventually decommission monolith component
|
|
56
|
+
|
|
57
|
+
**When to use**: Migrating an existing monolith without a big-bang rewrite.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Communication Patterns
|
|
62
|
+
|
|
63
|
+
### Synchronous — REST
|
|
64
|
+
|
|
65
|
+
- Use for simple request/response interactions
|
|
66
|
+
- Design APIs using the Richardson Maturity Model (ideally Level 2+)
|
|
67
|
+
- Define IDL: OpenAPI/Swagger for REST
|
|
68
|
+
- Handle partial failure: timeouts, retries, circuit breakers
|
|
69
|
+
|
|
70
|
+
### Synchronous — gRPC
|
|
71
|
+
|
|
72
|
+
- Binary protocol, strongly typed via Protocol Buffers
|
|
73
|
+
- More efficient than REST for inter-service calls
|
|
74
|
+
- Supports streaming (server, client, bidirectional)
|
|
75
|
+
- Good for polyglot environments (code generation for many languages)
|
|
76
|
+
|
|
77
|
+
### Asynchronous — Messaging
|
|
78
|
+
|
|
79
|
+
- Services communicate via message broker (Kafka, RabbitMQ, etc.)
|
|
80
|
+
- Message types: **Document** (carries data), **Command** (request action), **Event** (notification of change)
|
|
81
|
+
- Channels: **Point-to-point** (one consumer) or **Publish-subscribe** (many consumers)
|
|
82
|
+
|
|
83
|
+
Key implementation concerns:
|
|
84
|
+
|
|
85
|
+
- **Message ordering**: Use partitioned channels (e.g., Kafka partitions keyed by aggregate ID)
|
|
86
|
+
- **Duplicate handling**: Make consumers idempotent (track processed message IDs, or make operations naturally idempotent)
|
|
87
|
+
- **Transactional outbox**: Write events to an OUTBOX table in the same transaction as business data, then relay to broker — ensures atomicity without distributed transactions
|
|
88
|
+
- **Polling publisher or Transaction log tailing**: Two strategies for relaying outbox messages to the broker
|
|
89
|
+
|
|
90
|
+
### Circuit Breaker
|
|
91
|
+
|
|
92
|
+
Wrap remote calls in a circuit breaker to handle downstream failures gracefully:
|
|
93
|
+
|
|
94
|
+
- **Closed**: Requests pass through normally
|
|
95
|
+
- **Open**: Requests fail immediately (after failure threshold exceeded)
|
|
96
|
+
- **Half-open**: Periodically try a request; if it succeeds, close the circuit
|
|
97
|
+
|
|
98
|
+
Use libraries like Resilience4j (Java) or Polly (.NET).
|
|
99
|
+
|
|
100
|
+
### Service Discovery
|
|
101
|
+
|
|
102
|
+
Services need to locate each other. Two approaches:
|
|
103
|
+
|
|
104
|
+
- **Client-side discovery**: Client queries a registry (e.g., Eureka) and load-balances
|
|
105
|
+
- **Server-side discovery**: Client calls a router/load balancer that queries the registry (e.g., Kubernetes services, AWS ELB)
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## API Gateway Patterns
|
|
110
|
+
|
|
111
|
+
### API Gateway
|
|
112
|
+
|
|
113
|
+
Single entry point for external clients. Responsibilities:
|
|
114
|
+
|
|
115
|
+
- Request routing to appropriate microservice
|
|
116
|
+
- API composition (aggregate responses from multiple services)
|
|
117
|
+
- Protocol translation (external REST to internal gRPC/messaging)
|
|
118
|
+
- Authentication and rate limiting
|
|
119
|
+
- Edge functions (caching, monitoring, etc.)
|
|
120
|
+
|
|
121
|
+
### Backend for Frontend (BFF)
|
|
122
|
+
|
|
123
|
+
Separate API gateway per client type (web, mobile, third-party). Each BFF:
|
|
124
|
+
|
|
125
|
+
- Tailors the API to its client's needs
|
|
126
|
+
- Handles client-specific data aggregation
|
|
127
|
+
- Is owned by the client team
|
|
128
|
+
- Reduces coupling between client and backend service evolution
|
|
129
|
+
|
|
130
|
+
**When to use BFF over single gateway**: When different clients have significantly different API needs.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Transaction Management — Sagas
|
|
135
|
+
|
|
136
|
+
### The Problem
|
|
137
|
+
|
|
138
|
+
Microservices use Database per Service, so you cannot use ACID transactions
|
|
139
|
+
across services. Sagas maintain data consistency using a sequence of local
|
|
140
|
+
transactions with compensating transactions for rollback.
|
|
141
|
+
|
|
142
|
+
### Choreography-based Saga
|
|
143
|
+
|
|
144
|
+
Services publish events and subscribe to each other's events:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
OrderService -> OrderCreated event
|
|
148
|
+
-> PaymentService listens, processes payment, publishes PaymentAuthorized
|
|
149
|
+
-> KitchenService listens, creates ticket, publishes TicketCreated
|
|
150
|
+
-> OrderService listens, approves order
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- Pros: Simple, no central coordinator, loose coupling
|
|
154
|
+
- Cons: Hard to understand the flow, cyclic dependencies possible
|
|
155
|
+
|
|
156
|
+
### Orchestration-based Saga
|
|
157
|
+
|
|
158
|
+
A central saga orchestrator tells participants what to do:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
CreateOrderSaga:
|
|
162
|
+
1. OrderService.createOrder(PENDING)
|
|
163
|
+
2. PaymentService.authorize() -> fail? -> OrderService.rejectOrder()
|
|
164
|
+
3. KitchenService.createTicket() -> fail? -> PaymentService.reverseAuth(), OrderService.rejectOrder()
|
|
165
|
+
4. OrderService.approveOrder()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
- Pros: Clear flow, easy to understand, avoids cyclic dependencies
|
|
169
|
+
- Cons: Risk of centralizing too much logic in orchestrator
|
|
170
|
+
|
|
171
|
+
### Saga Design Rules
|
|
172
|
+
|
|
173
|
+
- Each saga step modifies one aggregate in one service
|
|
174
|
+
- Every forward step needs a compensating transaction (undo/rollback action)
|
|
175
|
+
- Compensating transactions must be idempotent (safe to retry)
|
|
176
|
+
- Use semantic locks — mark records as "pending" during saga execution
|
|
177
|
+
- Countermeasures for lack of isolation: semantic locks, commutative updates, pessimistic/optimistic views, re-reading values
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Business Logic Patterns
|
|
182
|
+
|
|
183
|
+
### Aggregate Pattern (DDD)
|
|
184
|
+
|
|
185
|
+
An aggregate is a cluster of domain objects treated as a unit for data changes.
|
|
186
|
+
|
|
187
|
+
- **Aggregate root**: Top-level entity through which all external access occurs
|
|
188
|
+
- **Invariants**: Business rules enforced within the aggregate
|
|
189
|
+
- **Transaction boundary**: One transaction = one aggregate update
|
|
190
|
+
- **References between aggregates**: Use IDs, not object references
|
|
191
|
+
|
|
192
|
+
Design rules:
|
|
193
|
+
- Keep aggregates small — reference other aggregates by identity
|
|
194
|
+
- Business logic lives in the aggregate, not in service classes
|
|
195
|
+
- Aggregates publish domain events when their state changes
|
|
196
|
+
|
|
197
|
+
### Domain Events
|
|
198
|
+
|
|
199
|
+
Events represent something meaningful that happened in the domain:
|
|
200
|
+
|
|
201
|
+
- Named in past tense: OrderCreated, PaymentAuthorized, TicketAccepted
|
|
202
|
+
- Contain relevant data (IDs, state at time of event)
|
|
203
|
+
- Published by aggregates after state changes
|
|
204
|
+
- Consumed by other services for integration
|
|
205
|
+
|
|
206
|
+
### Domain Event Publishing
|
|
207
|
+
|
|
208
|
+
Two approaches to reliably publish events:
|
|
209
|
+
|
|
210
|
+
1. **Transactional outbox**: Store events in an outbox table in the same DB transaction as business data, then asynchronously publish to message broker
|
|
211
|
+
2. **Event sourcing**: Events are the primary store (see below)
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Event Sourcing
|
|
216
|
+
|
|
217
|
+
Instead of storing current state, store a sequence of state-changing events.
|
|
218
|
+
Reconstruct current state by replaying events.
|
|
219
|
+
|
|
220
|
+
### How It Works
|
|
221
|
+
|
|
222
|
+
- Each aggregate stored as a sequence of events in an event store
|
|
223
|
+
- To load: fetch all events, replay to reconstruct state
|
|
224
|
+
- To save: append new events to the store
|
|
225
|
+
- Event store is append-only (never update, never delete)
|
|
226
|
+
|
|
227
|
+
### Benefits
|
|
228
|
+
|
|
229
|
+
- Reliable event publishing (events ARE the data — no outbox needed)
|
|
230
|
+
- Complete audit trail
|
|
231
|
+
- Temporal queries (reconstruct state at any point in time)
|
|
232
|
+
- Enables CQRS naturally
|
|
233
|
+
|
|
234
|
+
### Challenges
|
|
235
|
+
|
|
236
|
+
- Learning curve; different way of thinking
|
|
237
|
+
- Querying the event store is hard (need CQRS for queries)
|
|
238
|
+
- Event schema evolution — events are immutable, so versioning matters
|
|
239
|
+
- Deleting data (e.g., GDPR) requires special handling like encryption with deletable keys
|
|
240
|
+
|
|
241
|
+
### Snapshots
|
|
242
|
+
|
|
243
|
+
For aggregates with many events, periodically save a snapshot of current state
|
|
244
|
+
to avoid replaying the entire history. Load: last snapshot + events after snapshot.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Query Patterns
|
|
249
|
+
|
|
250
|
+
### API Composition
|
|
251
|
+
|
|
252
|
+
For queries spanning multiple services, an API composer calls each service
|
|
253
|
+
and combines results in memory.
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
API Composer (or API Gateway):
|
|
257
|
+
1. Call OrderService.getOrders(customerId)
|
|
258
|
+
2. Call DeliveryService.getDeliveries(orderIds)
|
|
259
|
+
3. Call RestaurantService.getRestaurants(restaurantIds)
|
|
260
|
+
4. Join results in memory, return to client
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
- Pros: Simple to implement
|
|
264
|
+
- Cons: Increased latency, no efficient join/filter across services, reduced availability
|
|
265
|
+
|
|
266
|
+
**When to use**: Simple queries where data from 2-3 services needs combining.
|
|
267
|
+
|
|
268
|
+
### CQRS (Command Query Responsibility Segregation)
|
|
269
|
+
|
|
270
|
+
Separate the write model (commands) from the read model (queries):
|
|
271
|
+
|
|
272
|
+
- **Command side**: Services handle commands, publish domain events
|
|
273
|
+
- **Query side**: A separate view service subscribes to events and maintains denormalized read-optimized database
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
OrderService publishes OrderCreated ->
|
|
277
|
+
DeliveryService publishes DeliveryStatusUpdated ->
|
|
278
|
+
OrderHistoryViewService subscribes to both,
|
|
279
|
+
maintains denormalized OrderHistoryView table,
|
|
280
|
+
serves GET /order-history queries
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
- Pros: Efficient queries, scales reads independently, supports complex cross-service queries
|
|
284
|
+
- Cons: Extra complexity, eventual consistency, additional infrastructure
|
|
285
|
+
|
|
286
|
+
**When to use**: Complex queries spanning many services, or when read performance is critical.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Testing Patterns
|
|
291
|
+
|
|
292
|
+
### Consumer-Driven Contract Testing
|
|
293
|
+
|
|
294
|
+
Each consumer defines a contract: "I expect your API to behave like X."
|
|
295
|
+
Provider runs these contracts as tests to ensure compatibility.
|
|
296
|
+
|
|
297
|
+
- Tools: Spring Cloud Contract, Pact
|
|
298
|
+
- Catches breaking changes before deployment
|
|
299
|
+
|
|
300
|
+
### Service Component Testing
|
|
301
|
+
|
|
302
|
+
Test a service in isolation by stubbing its dependencies:
|
|
303
|
+
|
|
304
|
+
- In-memory stubs for downstream services
|
|
305
|
+
- In-memory or containerized database
|
|
306
|
+
- Verify behavior end-to-end through the service's API
|
|
307
|
+
|
|
308
|
+
### Integration Testing
|
|
309
|
+
|
|
310
|
+
Test interaction between a service and its infrastructure:
|
|
311
|
+
|
|
312
|
+
- Database integration tests (verify SQL, schema migrations)
|
|
313
|
+
- Messaging integration tests (verify event publishing/consuming)
|
|
314
|
+
- REST client integration tests (verify HTTP calls)
|
|
315
|
+
- Use Docker (Testcontainers) for realistic infrastructure
|
|
316
|
+
|
|
317
|
+
### Testing Pyramid for Microservices
|
|
318
|
+
|
|
319
|
+
From bottom (fast, many) to top (slow, few):
|
|
320
|
+
|
|
321
|
+
1. Unit tests — domain logic, aggregates, sagas
|
|
322
|
+
2. Integration tests — persistence, messaging, REST clients
|
|
323
|
+
3. Component tests — single service end-to-end with stubs
|
|
324
|
+
4. Contract tests — API compatibility between consumer and provider
|
|
325
|
+
5. End-to-end tests — full system (keep these minimal)
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Deployment Patterns
|
|
330
|
+
|
|
331
|
+
### Service per Container
|
|
332
|
+
|
|
333
|
+
Package each service as a Docker container image:
|
|
334
|
+
|
|
335
|
+
- Encapsulates service and dependencies
|
|
336
|
+
- Consistent across environments
|
|
337
|
+
- Use multi-stage builds to keep images small
|
|
338
|
+
|
|
339
|
+
### Kubernetes Orchestration
|
|
340
|
+
|
|
341
|
+
- Deployment: Manages replicas and rolling updates
|
|
342
|
+
- Service: Stable network endpoint for service discovery
|
|
343
|
+
- ConfigMap/Secret: Externalized configuration
|
|
344
|
+
- Probes: Liveness and readiness health checks
|
|
345
|
+
|
|
346
|
+
### Service Mesh
|
|
347
|
+
|
|
348
|
+
Infrastructure layer handling service-to-service communication:
|
|
349
|
+
|
|
350
|
+
- Automatic mTLS between services
|
|
351
|
+
- Traffic management (retries, timeouts, circuit breaking)
|
|
352
|
+
- Observability (distributed tracing, metrics)
|
|
353
|
+
- Tools: Istio, Linkerd
|
|
354
|
+
|
|
355
|
+
### Externalized Configuration
|
|
356
|
+
|
|
357
|
+
- Environment variables, config servers (Spring Cloud Config), Kubernetes ConfigMaps
|
|
358
|
+
- Secrets managed separately (Vault, Kubernetes Secrets)
|
|
359
|
+
- Same artifact runs in different environments
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Observability Patterns
|
|
364
|
+
|
|
365
|
+
### Health Check API
|
|
366
|
+
|
|
367
|
+
Every service exposes a health endpoint (GET /health) reporting:
|
|
368
|
+
- Service status (UP/DOWN)
|
|
369
|
+
- Dependency status (database, message broker)
|
|
370
|
+
- Used by orchestrators and load balancers
|
|
371
|
+
|
|
372
|
+
### Distributed Tracing
|
|
373
|
+
|
|
374
|
+
Assign unique trace ID to each external request, propagate through all service calls.
|
|
375
|
+
- Tools: Zipkin, Jaeger, AWS X-Ray
|
|
376
|
+
- Instrument with OpenTelemetry
|
|
377
|
+
|
|
378
|
+
### Log Aggregation
|
|
379
|
+
|
|
380
|
+
Centralize logs from all services:
|
|
381
|
+
- Include trace IDs for correlation
|
|
382
|
+
- Structured logging (JSON format)
|
|
383
|
+
- Tools: ELK Stack, Fluentd, Datadog
|
|
384
|
+
|
|
385
|
+
### Application Metrics
|
|
386
|
+
|
|
387
|
+
Collect and expose metrics from each service:
|
|
388
|
+
- Request rate, latency, error rate (RED method)
|
|
389
|
+
- Resource utilization
|
|
390
|
+
- Business metrics
|
|
391
|
+
- Tools: Prometheus + Grafana, Datadog, CloudWatch
|