@k2works/claude-code-booster 6.1.0 → 6.3.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.
Files changed (622) hide show
  1. package/lib/assets/.claude/skills/practicing-getting-start-tdd/SKILL.md +3 -7
  2. package/lib/assets/docs/article/index.md +16 -41
  3. package/lib/assets/docs/assets/js/extra.js +5 -1
  4. package/lib/assets/docs/assets/js/mathjax.js +27 -0
  5. package/lib/assets/docs/index.md +1 -1
  6. package/lib/assets/mkdocs.yml +6 -7
  7. package/lib/assets/ops/docker/mkdoc/Dockerfile +19 -19
  8. package/lib/assets/package.json +1 -1
  9. package/package.json +1 -1
  10. package/lib/assets/docs/article/functional-desgin-ppp/all/01-immutability-and-data-transformation.md +0 -484
  11. package/lib/assets/docs/article/functional-desgin-ppp/all/02-function-composition.md +0 -528
  12. package/lib/assets/docs/article/functional-desgin-ppp/all/03-polymorphism.md +0 -546
  13. package/lib/assets/docs/article/functional-desgin-ppp/all/04-data-validation.md +0 -309
  14. package/lib/assets/docs/article/functional-desgin-ppp/all/05-property-based-testing.md +0 -329
  15. package/lib/assets/docs/article/functional-desgin-ppp/all/06-tdd-and-functional.md +0 -507
  16. package/lib/assets/docs/article/functional-desgin-ppp/all/07-composite-pattern.md +0 -307
  17. package/lib/assets/docs/article/functional-desgin-ppp/all/08-decorator-pattern.md +0 -300
  18. package/lib/assets/docs/article/functional-desgin-ppp/all/09-adapter-pattern.md +0 -345
  19. package/lib/assets/docs/article/functional-desgin-ppp/all/10-strategy-pattern.md +0 -312
  20. package/lib/assets/docs/article/functional-desgin-ppp/all/11-command-pattern.md +0 -295
  21. package/lib/assets/docs/article/functional-desgin-ppp/all/12-visitor-pattern.md +0 -331
  22. package/lib/assets/docs/article/functional-desgin-ppp/all/13-abstract-factory-pattern.md +0 -328
  23. package/lib/assets/docs/article/functional-desgin-ppp/all/14-abstract-server-pattern.md +0 -374
  24. package/lib/assets/docs/article/functional-desgin-ppp/all/15-gossiping-bus-drivers.md +0 -165
  25. package/lib/assets/docs/article/functional-desgin-ppp/all/16-payroll-system.md +0 -187
  26. package/lib/assets/docs/article/functional-desgin-ppp/all/17-video-rental-system.md +0 -321
  27. package/lib/assets/docs/article/functional-desgin-ppp/all/18-concurrency-system.md +0 -296
  28. package/lib/assets/docs/article/functional-desgin-ppp/all/19-wa-tor-simulation.md +0 -295
  29. package/lib/assets/docs/article/functional-desgin-ppp/all/20-pattern-interactions.md +0 -283
  30. package/lib/assets/docs/article/functional-desgin-ppp/all/21-best-practices.md +0 -303
  31. package/lib/assets/docs/article/functional-desgin-ppp/all/22-oo-to-fp-migration.md +0 -346
  32. package/lib/assets/docs/article/functional-desgin-ppp/all/index.md +0 -388
  33. package/lib/assets/docs/article/functional-desgin-ppp/clojure/01-immutability-and-data-transformation.md +0 -282
  34. package/lib/assets/docs/article/functional-desgin-ppp/clojure/02-function-composition.md +0 -389
  35. package/lib/assets/docs/article/functional-desgin-ppp/clojure/03-polymorphism.md +0 -393
  36. package/lib/assets/docs/article/functional-desgin-ppp/clojure/04-clojure-spec.md +0 -359
  37. package/lib/assets/docs/article/functional-desgin-ppp/clojure/05-property-based-testing.md +0 -361
  38. package/lib/assets/docs/article/functional-desgin-ppp/clojure/06-tdd-in-functional.md +0 -392
  39. package/lib/assets/docs/article/functional-desgin-ppp/clojure/07-composite-pattern.md +0 -538
  40. package/lib/assets/docs/article/functional-desgin-ppp/clojure/08-decorator-pattern.md +0 -404
  41. package/lib/assets/docs/article/functional-desgin-ppp/clojure/09-adapter-pattern.md +0 -408
  42. package/lib/assets/docs/article/functional-desgin-ppp/clojure/10-strategy-pattern.md +0 -494
  43. package/lib/assets/docs/article/functional-desgin-ppp/clojure/11-command-pattern.md +0 -575
  44. package/lib/assets/docs/article/functional-desgin-ppp/clojure/12-visitor-pattern.md +0 -576
  45. package/lib/assets/docs/article/functional-desgin-ppp/clojure/13-abstract-factory-pattern.md +0 -484
  46. package/lib/assets/docs/article/functional-desgin-ppp/clojure/14-abstract-server-pattern.md +0 -471
  47. package/lib/assets/docs/article/functional-desgin-ppp/clojure/15-gossiping-bus-drivers.md +0 -334
  48. package/lib/assets/docs/article/functional-desgin-ppp/clojure/16-payroll-system.md +0 -410
  49. package/lib/assets/docs/article/functional-desgin-ppp/clojure/17-video-rental-system.md +0 -459
  50. package/lib/assets/docs/article/functional-desgin-ppp/clojure/18-concurrency-system.md +0 -484
  51. package/lib/assets/docs/article/functional-desgin-ppp/clojure/19-wator-simulation.md +0 -748
  52. package/lib/assets/docs/article/functional-desgin-ppp/clojure/20-pattern-interactions.md +0 -576
  53. package/lib/assets/docs/article/functional-desgin-ppp/clojure/21-best-practices.md +0 -527
  54. package/lib/assets/docs/article/functional-desgin-ppp/clojure/22-oo-to-fp-migration.md +0 -541
  55. package/lib/assets/docs/article/functional-desgin-ppp/clojure/index.md +0 -241
  56. package/lib/assets/docs/article/functional-desgin-ppp/elixir/01-immutability-and-data-transformation.md +0 -392
  57. package/lib/assets/docs/article/functional-desgin-ppp/elixir/02-function-composition.md +0 -383
  58. package/lib/assets/docs/article/functional-desgin-ppp/elixir/03-polymorphism.md +0 -384
  59. package/lib/assets/docs/article/functional-desgin-ppp/elixir/04-data-validation.md +0 -204
  60. package/lib/assets/docs/article/functional-desgin-ppp/elixir/05-property-based-testing.md +0 -277
  61. package/lib/assets/docs/article/functional-desgin-ppp/elixir/06-tdd-and-fp.md +0 -303
  62. package/lib/assets/docs/article/functional-desgin-ppp/elixir/07-effects-and-pure-functions.md +0 -173
  63. package/lib/assets/docs/article/functional-desgin-ppp/elixir/08-error-handling-strategies.md +0 -177
  64. package/lib/assets/docs/article/functional-desgin-ppp/elixir/09-io-and-external-systems.md +0 -263
  65. package/lib/assets/docs/article/functional-desgin-ppp/elixir/10-concurrency-patterns.md +0 -278
  66. package/lib/assets/docs/article/functional-desgin-ppp/elixir/11-command-pattern.md +0 -157
  67. package/lib/assets/docs/article/functional-desgin-ppp/elixir/12-visitor-pattern.md +0 -185
  68. package/lib/assets/docs/article/functional-desgin-ppp/elixir/13-abstract-factory-pattern.md +0 -613
  69. package/lib/assets/docs/article/functional-desgin-ppp/elixir/14-abstract-server-pattern.md +0 -738
  70. package/lib/assets/docs/article/functional-desgin-ppp/elixir/15-gossiping-bus-drivers.md +0 -300
  71. package/lib/assets/docs/article/functional-desgin-ppp/elixir/16-payroll-system.md +0 -429
  72. package/lib/assets/docs/article/functional-desgin-ppp/elixir/17-video-rental-system.md +0 -328
  73. package/lib/assets/docs/article/functional-desgin-ppp/elixir/18-concurrency-system.md +0 -475
  74. package/lib/assets/docs/article/functional-desgin-ppp/elixir/19-wator-simulation.md +0 -532
  75. package/lib/assets/docs/article/functional-desgin-ppp/elixir/20-pattern-interactions.md +0 -296
  76. package/lib/assets/docs/article/functional-desgin-ppp/elixir/21-best-practices.md +0 -349
  77. package/lib/assets/docs/article/functional-desgin-ppp/elixir/22-oo-to-fp-migration.md +0 -404
  78. package/lib/assets/docs/article/functional-desgin-ppp/elixir/index.md +0 -248
  79. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/01-immutability-and-data-transformation.md +0 -393
  80. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/02-function-composition.md +0 -461
  81. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/03-polymorphism.md +0 -504
  82. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/04-data-validation.md +0 -425
  83. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/05-property-based-testing.md +0 -391
  84. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/06-tdd-functional.md +0 -696
  85. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/07-composite-pattern.md +0 -451
  86. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/08-decorator-pattern.md +0 -488
  87. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/09-adapter-pattern.md +0 -488
  88. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/10-strategy-pattern.md +0 -436
  89. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/11-command-pattern.md +0 -437
  90. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/12-visitor-pattern.md +0 -348
  91. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/13-abstract-factory-pattern.md +0 -318
  92. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/14-abstract-server-pattern.md +0 -605
  93. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/15-gossiping-bus-drivers.md +0 -364
  94. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/16-payroll-system.md +0 -359
  95. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/17-video-rental-system.md +0 -423
  96. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/18-concurrency-system.md +0 -376
  97. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/19-wator-simulation.md +0 -412
  98. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/20-pattern-interactions.md +0 -300
  99. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/21-best-practices.md +0 -333
  100. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/22-oo-to-fp-migration.md +0 -341
  101. package/lib/assets/docs/article/functional-desgin-ppp/fsharp/index.md +0 -274
  102. package/lib/assets/docs/article/functional-desgin-ppp/haskell/01-immutability-and-data-transformation.md +0 -307
  103. package/lib/assets/docs/article/functional-desgin-ppp/haskell/02-function-composition.md +0 -313
  104. package/lib/assets/docs/article/functional-desgin-ppp/haskell/03-polymorphism.md +0 -371
  105. package/lib/assets/docs/article/functional-desgin-ppp/haskell/04-data-validation.md +0 -266
  106. package/lib/assets/docs/article/functional-desgin-ppp/haskell/05-property-based-testing.md +0 -263
  107. package/lib/assets/docs/article/functional-desgin-ppp/haskell/06-tdd-functional.md +0 -292
  108. package/lib/assets/docs/article/functional-desgin-ppp/haskell/07-composite-pattern.md +0 -404
  109. package/lib/assets/docs/article/functional-desgin-ppp/haskell/08-decorator-pattern.md +0 -328
  110. package/lib/assets/docs/article/functional-desgin-ppp/haskell/09-adapter-pattern.md +0 -391
  111. package/lib/assets/docs/article/functional-desgin-ppp/haskell/10-strategy-pattern.md +0 -296
  112. package/lib/assets/docs/article/functional-desgin-ppp/haskell/11-command-pattern.md +0 -312
  113. package/lib/assets/docs/article/functional-desgin-ppp/haskell/12-visitor-pattern.md +0 -335
  114. package/lib/assets/docs/article/functional-desgin-ppp/haskell/13-abstract-factory-pattern.md +0 -341
  115. package/lib/assets/docs/article/functional-desgin-ppp/haskell/14-abstract-server-pattern.md +0 -388
  116. package/lib/assets/docs/article/functional-desgin-ppp/haskell/15-gossiping-bus-drivers.md +0 -186
  117. package/lib/assets/docs/article/functional-desgin-ppp/haskell/16-payroll-system.md +0 -228
  118. package/lib/assets/docs/article/functional-desgin-ppp/haskell/17-video-rental-system.md +0 -253
  119. package/lib/assets/docs/article/functional-desgin-ppp/haskell/18-concurrency-system.md +0 -372
  120. package/lib/assets/docs/article/functional-desgin-ppp/haskell/19-wator-simulation.md +0 -447
  121. package/lib/assets/docs/article/functional-desgin-ppp/haskell/20-pattern-interactions.md +0 -334
  122. package/lib/assets/docs/article/functional-desgin-ppp/haskell/21-best-practices.md +0 -412
  123. package/lib/assets/docs/article/functional-desgin-ppp/haskell/22-oo-to-fp-migration.md +0 -478
  124. package/lib/assets/docs/article/functional-desgin-ppp/haskell/index.md +0 -174
  125. package/lib/assets/docs/article/functional-desgin-ppp/index.md +0 -90
  126. package/lib/assets/docs/article/functional-desgin-ppp/rust/01-immutability-and-data-transformation.md +0 -459
  127. package/lib/assets/docs/article/functional-desgin-ppp/rust/02-function-composition.md +0 -472
  128. package/lib/assets/docs/article/functional-desgin-ppp/rust/03-polymorphism.md +0 -434
  129. package/lib/assets/docs/article/functional-desgin-ppp/rust/04-data-validation.md +0 -282
  130. package/lib/assets/docs/article/functional-desgin-ppp/rust/05-property-based-testing.md +0 -256
  131. package/lib/assets/docs/article/functional-desgin-ppp/rust/06-tdd-and-functional.md +0 -850
  132. package/lib/assets/docs/article/functional-desgin-ppp/rust/07-composite-pattern.md +0 -393
  133. package/lib/assets/docs/article/functional-desgin-ppp/rust/08-decorator-pattern.md +0 -392
  134. package/lib/assets/docs/article/functional-desgin-ppp/rust/09-adapter-pattern.md +0 -348
  135. package/lib/assets/docs/article/functional-desgin-ppp/rust/10-strategy-pattern.md +0 -340
  136. package/lib/assets/docs/article/functional-desgin-ppp/rust/11-command-pattern.md +0 -365
  137. package/lib/assets/docs/article/functional-desgin-ppp/rust/12-visitor-pattern.md +0 -388
  138. package/lib/assets/docs/article/functional-desgin-ppp/rust/13-abstract-factory-pattern.md +0 -370
  139. package/lib/assets/docs/article/functional-desgin-ppp/rust/14-abstract-server-pattern.md +0 -401
  140. package/lib/assets/docs/article/functional-desgin-ppp/rust/15-gossiping-bus-drivers.md +0 -309
  141. package/lib/assets/docs/article/functional-desgin-ppp/rust/16-payroll-system.md +0 -306
  142. package/lib/assets/docs/article/functional-desgin-ppp/rust/17-video-rental-system.md +0 -313
  143. package/lib/assets/docs/article/functional-desgin-ppp/rust/18-concurrency-system.md +0 -324
  144. package/lib/assets/docs/article/functional-desgin-ppp/rust/19-wator-simulation.md +0 -320
  145. package/lib/assets/docs/article/functional-desgin-ppp/rust/20-pattern-interactions.md +0 -313
  146. package/lib/assets/docs/article/functional-desgin-ppp/rust/21-best-practices.md +0 -345
  147. package/lib/assets/docs/article/functional-desgin-ppp/rust/22-oo-to-fp-migration.md +0 -358
  148. package/lib/assets/docs/article/functional-desgin-ppp/rust/index.md +0 -243
  149. package/lib/assets/docs/article/functional-desgin-ppp/scala/01-immutability-and-data-transformation.md +0 -337
  150. package/lib/assets/docs/article/functional-desgin-ppp/scala/02-function-composition.md +0 -357
  151. package/lib/assets/docs/article/functional-desgin-ppp/scala/03-polymorphism.md +0 -366
  152. package/lib/assets/docs/article/functional-desgin-ppp/scala/04-data-validation.md +0 -373
  153. package/lib/assets/docs/article/functional-desgin-ppp/scala/05-property-based-testing.md +0 -524
  154. package/lib/assets/docs/article/functional-desgin-ppp/scala/06-tdd-functional.md +0 -566
  155. package/lib/assets/docs/article/functional-desgin-ppp/scala/07-composite-pattern.md +0 -372
  156. package/lib/assets/docs/article/functional-desgin-ppp/scala/08-decorator-pattern.md +0 -336
  157. package/lib/assets/docs/article/functional-desgin-ppp/scala/09-adapter-pattern.md +0 -526
  158. package/lib/assets/docs/article/functional-desgin-ppp/scala/10-strategy-pattern.md +0 -450
  159. package/lib/assets/docs/article/functional-desgin-ppp/scala/11-command-pattern.md +0 -416
  160. package/lib/assets/docs/article/functional-desgin-ppp/scala/12-visitor-pattern.md +0 -388
  161. package/lib/assets/docs/article/functional-desgin-ppp/scala/13-abstract-factory-pattern.md +0 -407
  162. package/lib/assets/docs/article/functional-desgin-ppp/scala/14-abstract-server-pattern.md +0 -485
  163. package/lib/assets/docs/article/functional-desgin-ppp/scala/15-gossiping-bus-drivers.md +0 -400
  164. package/lib/assets/docs/article/functional-desgin-ppp/scala/16-payroll-system.md +0 -351
  165. package/lib/assets/docs/article/functional-desgin-ppp/scala/17-video-rental-system.md +0 -333
  166. package/lib/assets/docs/article/functional-desgin-ppp/scala/18-concurrency-system.md +0 -739
  167. package/lib/assets/docs/article/functional-desgin-ppp/scala/19-wator-simulation.md +0 -633
  168. package/lib/assets/docs/article/functional-desgin-ppp/scala/20-pattern-interactions.md +0 -521
  169. package/lib/assets/docs/article/functional-desgin-ppp/scala/21-best-practices.md +0 -442
  170. package/lib/assets/docs/article/functional-desgin-ppp/scala/22-oo-to-fp-migration.md +0 -697
  171. package/lib/assets/docs/article/functional-desgin-ppp/scala/index.md +0 -243
  172. package/lib/assets/docs/article/getting-start-docker-kubernetes/01-container-and-docker-basics.md +0 -341
  173. package/lib/assets/docs/article/getting-start-docker-kubernetes/02-container-deployment.md +0 -785
  174. package/lib/assets/docs/article/getting-start-docker-kubernetes/03-practical-container-build-deploy.md +0 -466
  175. package/lib/assets/docs/article/getting-start-docker-kubernetes/04-multi-container-application.md +0 -878
  176. package/lib/assets/docs/article/getting-start-docker-kubernetes/05-kubernetes-introduction.md +0 -585
  177. package/lib/assets/docs/article/getting-start-docker-kubernetes/06-kubernetes-deploy-cluster.md +0 -620
  178. package/lib/assets/docs/article/getting-start-docker-kubernetes/07-kubernetes-advanced.md +0 -621
  179. package/lib/assets/docs/article/getting-start-docker-kubernetes/08-kubernetes-packaging.md +0 -518
  180. package/lib/assets/docs/article/getting-start-docker-kubernetes/09-container-operations.md +0 -546
  181. package/lib/assets/docs/article/getting-start-docker-kubernetes/10-optimal-container-image.md +0 -640
  182. package/lib/assets/docs/article/getting-start-docker-kubernetes/11-continuous-delivery.md +0 -607
  183. package/lib/assets/docs/article/getting-start-docker-kubernetes/12-container-use-cases.md +0 -463
  184. package/lib/assets/docs/article/getting-start-docker-kubernetes/13-case-monolith-compose-vs-kustomize.md +0 -346
  185. package/lib/assets/docs/article/getting-start-docker-kubernetes/14-case-event-driven-kustomize-vs-helm.md +0 -324
  186. package/lib/assets/docs/article/getting-start-docker-kubernetes/15-case-escqrs-axon-kustomize-vs-helm.md +0 -297
  187. package/lib/assets/docs/article/getting-start-docker-kubernetes/16-case-escqrs-kafka-kustomize-vs-helm.md +0 -256
  188. package/lib/assets/docs/article/getting-start-docker-kubernetes/17-case-comparison-summary.md +0 -141
  189. package/lib/assets/docs/article/getting-start-docker-kubernetes/18-case-load-testing.md +0 -303
  190. package/lib/assets/docs/article/getting-start-docker-kubernetes/appendix-a-dev-tools-setup.md +0 -274
  191. package/lib/assets/docs/article/getting-start-docker-kubernetes/appendix-b-orchestration-environments.md +0 -530
  192. package/lib/assets/docs/article/getting-start-docker-kubernetes/appendix-c-tips.md +0 -519
  193. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-01-comic.png +0 -0
  194. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-02-comic.png +0 -0
  195. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-03-comic.png +0 -0
  196. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-04-comic.png +0 -0
  197. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-05-comic.png +0 -0
  198. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-06-comic.png +0 -0
  199. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-07-comic.png +0 -0
  200. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-08-comic.png +0 -0
  201. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-09-comic.png +0 -0
  202. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-10-comic.png +0 -0
  203. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-11-comic.png +0 -0
  204. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/chapter-12-comic.png +0 -0
  205. package/lib/assets/docs/article/getting-start-docker-kubernetes/images/overview-comic.png +0 -0
  206. package/lib/assets/docs/article/getting-start-docker-kubernetes/index.md +0 -82
  207. package/lib/assets/docs/article/getting-start-docker-kubernetes/outline.md +0 -220
  208. package/lib/assets/docs/article/getting-start-tdd/clojure/01-todo-list-and-first-test.md +0 -175
  209. package/lib/assets/docs/article/getting-start-tdd/clojure/02-fake-it-and-triangulation.md +0 -171
  210. package/lib/assets/docs/article/getting-start-tdd/clojure/03-obvious-implementation-and-refactoring.md +0 -144
  211. package/lib/assets/docs/article/getting-start-tdd/clojure/04-version-control-and-conventional-commits.md +0 -97
  212. package/lib/assets/docs/article/getting-start-tdd/clojure/05-package-management-and-static-analysis.md +0 -308
  213. package/lib/assets/docs/article/getting-start-tdd/clojure/06-task-runner-and-ci-cd.md +0 -250
  214. package/lib/assets/docs/article/getting-start-tdd/clojure/07-protocols-and-records.md +0 -140
  215. package/lib/assets/docs/article/getting-start-tdd/clojure/08-multimethods-and-design-patterns.md +0 -139
  216. package/lib/assets/docs/article/getting-start-tdd/clojure/09-namespaces-and-module-design.md +0 -136
  217. package/lib/assets/docs/article/getting-start-tdd/clojure/10-higher-order-functions-and-composition.md +0 -123
  218. package/lib/assets/docs/article/getting-start-tdd/clojure/11-persistent-data-and-pipeline.md +0 -147
  219. package/lib/assets/docs/article/getting-start-tdd/clojure/12-error-handling-and-spec.md +0 -170
  220. package/lib/assets/docs/article/getting-start-tdd/clojure/index.md +0 -65
  221. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter01.md +0 -241
  222. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter02.md +0 -253
  223. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter03.md +0 -211
  224. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter04.md +0 -101
  225. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter05.md +0 -265
  226. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter06.md +0 -204
  227. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter07.md +0 -223
  228. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter08.md +0 -258
  229. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter09.md +0 -183
  230. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter10.md +0 -175
  231. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter11.md +0 -201
  232. package/lib/assets/docs/article/getting-start-tdd/csharp/chapter12.md +0 -220
  233. package/lib/assets/docs/article/getting-start-tdd/csharp/index.md +0 -83
  234. package/lib/assets/docs/article/getting-start-tdd/elixir/01-todo-list-and-first-test.md +0 -96
  235. package/lib/assets/docs/article/getting-start-tdd/elixir/02-fake-it-and-triangulation.md +0 -104
  236. package/lib/assets/docs/article/getting-start-tdd/elixir/03-obvious-implementation-and-refactoring.md +0 -118
  237. package/lib/assets/docs/article/getting-start-tdd/elixir/04-version-control-and-conventional-commits.md +0 -105
  238. package/lib/assets/docs/article/getting-start-tdd/elixir/05-package-management-and-static-analysis.md +0 -97
  239. package/lib/assets/docs/article/getting-start-tdd/elixir/06-task-runner-and-ci-cd.md +0 -80
  240. package/lib/assets/docs/article/getting-start-tdd/elixir/07-structs-and-protocols.md +0 -119
  241. package/lib/assets/docs/article/getting-start-tdd/elixir/08-pattern-matching-and-guards.md +0 -117
  242. package/lib/assets/docs/article/getting-start-tdd/elixir/09-module-design-and-behaviours.md +0 -113
  243. package/lib/assets/docs/article/getting-start-tdd/elixir/10-higher-order-functions-and-pipeline.md +0 -187
  244. package/lib/assets/docs/article/getting-start-tdd/elixir/11-stream-and-lazy-evaluation.md +0 -151
  245. package/lib/assets/docs/article/getting-start-tdd/elixir/12-error-handling-and-with.md +0 -154
  246. package/lib/assets/docs/article/getting-start-tdd/elixir/index.md +0 -35
  247. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter01.md +0 -211
  248. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter02.md +0 -255
  249. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter03.md +0 -227
  250. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter04.md +0 -188
  251. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter05.md +0 -276
  252. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter06.md +0 -199
  253. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter07.md +0 -170
  254. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter08.md +0 -184
  255. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter09.md +0 -231
  256. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter10.md +0 -198
  257. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter11.md +0 -221
  258. package/lib/assets/docs/article/getting-start-tdd/fsharp/chapter12.md +0 -224
  259. package/lib/assets/docs/article/getting-start-tdd/fsharp/index.md +0 -71
  260. package/lib/assets/docs/article/getting-start-tdd/go/01-todo-list-and-first-test.md +0 -222
  261. package/lib/assets/docs/article/getting-start-tdd/go/02-fake-it-and-triangulation.md +0 -311
  262. package/lib/assets/docs/article/getting-start-tdd/go/03-obvious-implementation-and-refactoring.md +0 -348
  263. package/lib/assets/docs/article/getting-start-tdd/go/04-version-control-and-conventional-commits.md +0 -121
  264. package/lib/assets/docs/article/getting-start-tdd/go/05-package-management-and-static-analysis.md +0 -281
  265. package/lib/assets/docs/article/getting-start-tdd/go/06-task-runner-and-ci-cd.md +0 -242
  266. package/lib/assets/docs/article/getting-start-tdd/go/07-encapsulation-and-polymorphism.md +0 -402
  267. package/lib/assets/docs/article/getting-start-tdd/go/08-design-patterns.md +0 -430
  268. package/lib/assets/docs/article/getting-start-tdd/go/09-solid-principles-and-module-design.md +0 -408
  269. package/lib/assets/docs/article/getting-start-tdd/go/10-higher-order-functions-and-composition.md +0 -234
  270. package/lib/assets/docs/article/getting-start-tdd/go/11-immutable-data-and-pipeline.md +0 -304
  271. package/lib/assets/docs/article/getting-start-tdd/go/12-error-handling-and-type-safety.md +0 -419
  272. package/lib/assets/docs/article/getting-start-tdd/go/index.md +0 -83
  273. package/lib/assets/docs/article/getting-start-tdd/haskell/01-todo-list-and-first-test.md +0 -288
  274. package/lib/assets/docs/article/getting-start-tdd/haskell/02-fake-it-and-triangulation.md +0 -346
  275. package/lib/assets/docs/article/getting-start-tdd/haskell/03-obvious-implementation-and-refactoring.md +0 -266
  276. package/lib/assets/docs/article/getting-start-tdd/haskell/04-version-control-and-conventional-commits.md +0 -191
  277. package/lib/assets/docs/article/getting-start-tdd/haskell/05-package-management-and-static-analysis.md +0 -322
  278. package/lib/assets/docs/article/getting-start-tdd/haskell/06-task-runner-and-ci-cd.md +0 -318
  279. package/lib/assets/docs/article/getting-start-tdd/haskell/07-algebraic-data-types-and-type-classes.md +0 -421
  280. package/lib/assets/docs/article/getting-start-tdd/haskell/08-pattern-matching-and-guards.md +0 -399
  281. package/lib/assets/docs/article/getting-start-tdd/haskell/09-module-design-and-smart-constructors.md +0 -470
  282. package/lib/assets/docs/article/getting-start-tdd/haskell/10-higher-order-functions-and-currying.md +0 -443
  283. package/lib/assets/docs/article/getting-start-tdd/haskell/11-function-composition-and-point-free.md +0 -401
  284. package/lib/assets/docs/article/getting-start-tdd/haskell/12-monad-and-error-handling.md +0 -640
  285. package/lib/assets/docs/article/getting-start-tdd/haskell/index.md +0 -49
  286. package/lib/assets/docs/article/getting-start-tdd/index.md +0 -93
  287. package/lib/assets/docs/article/getting-start-tdd/integration/01-language-overview.md +0 -384
  288. package/lib/assets/docs/article/getting-start-tdd/integration/02-test-framework-comparison.md +0 -358
  289. package/lib/assets/docs/article/getting-start-tdd/integration/03-tdd-pattern-comparison.md +0 -454
  290. package/lib/assets/docs/article/getting-start-tdd/integration/04-type-system-comparison.md +0 -418
  291. package/lib/assets/docs/article/getting-start-tdd/integration/05-dev-environment-comparison.md +0 -339
  292. package/lib/assets/docs/article/getting-start-tdd/integration/06-learning-roadmap.md +0 -299
  293. package/lib/assets/docs/article/getting-start-tdd/integration/index.md +0 -69
  294. package/lib/assets/docs/article/getting-start-tdd/java/01-todo-list-and-first-test.md +0 -243
  295. package/lib/assets/docs/article/getting-start-tdd/java/02-fake-it-and-triangulation.md +0 -270
  296. package/lib/assets/docs/article/getting-start-tdd/java/03-obvious-implementation-and-refactoring.md +0 -194
  297. package/lib/assets/docs/article/getting-start-tdd/java/04-version-control-and-conventional-commits.md +0 -124
  298. package/lib/assets/docs/article/getting-start-tdd/java/05-package-management-and-static-analysis.md +0 -391
  299. package/lib/assets/docs/article/getting-start-tdd/java/06-task-runner-and-ci-cd.md +0 -281
  300. package/lib/assets/docs/article/getting-start-tdd/java/07-encapsulation-and-polymorphism.md +0 -635
  301. package/lib/assets/docs/article/getting-start-tdd/java/08-design-patterns.md +0 -402
  302. package/lib/assets/docs/article/getting-start-tdd/java/09-solid-principles-and-module-design.md +0 -319
  303. package/lib/assets/docs/article/getting-start-tdd/java/10-higher-order-functions-and-composition.md +0 -197
  304. package/lib/assets/docs/article/getting-start-tdd/java/11-immutable-data-and-pipeline.md +0 -176
  305. package/lib/assets/docs/article/getting-start-tdd/java/12-error-handling-and-type-safety.md +0 -214
  306. package/lib/assets/docs/article/getting-start-tdd/java/index.md +0 -61
  307. package/lib/assets/docs/article/getting-start-tdd/node/01-todo-list-and-first-test.md +0 -253
  308. package/lib/assets/docs/article/getting-start-tdd/node/02-fake-it-and-triangulation.md +0 -271
  309. package/lib/assets/docs/article/getting-start-tdd/node/03-obvious-implementation-and-refactoring.md +0 -178
  310. package/lib/assets/docs/article/getting-start-tdd/node/04-version-control-and-conventional-commits.md +0 -121
  311. package/lib/assets/docs/article/getting-start-tdd/node/05-package-management-and-static-analysis.md +0 -323
  312. package/lib/assets/docs/article/getting-start-tdd/node/06-task-runner-and-ci-cd.md +0 -244
  313. package/lib/assets/docs/article/getting-start-tdd/node/07-encapsulation-and-polymorphism.md +0 -336
  314. package/lib/assets/docs/article/getting-start-tdd/node/08-design-patterns.md +0 -331
  315. package/lib/assets/docs/article/getting-start-tdd/node/09-solid-principles-and-module-design.md +0 -294
  316. package/lib/assets/docs/article/getting-start-tdd/node/10-higher-order-functions-and-composition.md +0 -208
  317. package/lib/assets/docs/article/getting-start-tdd/node/11-immutable-data-and-pipeline.md +0 -216
  318. package/lib/assets/docs/article/getting-start-tdd/node/12-error-handling-and-type-safety.md +0 -304
  319. package/lib/assets/docs/article/getting-start-tdd/node/index.md +0 -56
  320. package/lib/assets/docs/article/getting-start-tdd/php/01-todo-list-and-first-test.md +0 -268
  321. package/lib/assets/docs/article/getting-start-tdd/php/02-fake-it-and-triangulation.md +0 -209
  322. package/lib/assets/docs/article/getting-start-tdd/php/03-obvious-implementation-and-refactoring.md +0 -257
  323. package/lib/assets/docs/article/getting-start-tdd/php/04-version-control-and-conventional-commits.md +0 -150
  324. package/lib/assets/docs/article/getting-start-tdd/php/05-package-management-and-static-analysis.md +0 -419
  325. package/lib/assets/docs/article/getting-start-tdd/php/06-task-runner-and-ci-cd.md +0 -330
  326. package/lib/assets/docs/article/getting-start-tdd/php/07-encapsulation-and-polymorphism.md +0 -380
  327. package/lib/assets/docs/article/getting-start-tdd/php/08-design-patterns.md +0 -461
  328. package/lib/assets/docs/article/getting-start-tdd/php/09-solid-principles-and-module-design.md +0 -468
  329. package/lib/assets/docs/article/getting-start-tdd/php/10-higher-order-functions-and-composition.md +0 -190
  330. package/lib/assets/docs/article/getting-start-tdd/php/11-immutable-data-and-pipeline.md +0 -274
  331. package/lib/assets/docs/article/getting-start-tdd/php/12-error-handling-and-type-safety.md +0 -316
  332. package/lib/assets/docs/article/getting-start-tdd/php/index.md +0 -84
  333. package/lib/assets/docs/article/getting-start-tdd/python/01-todo-list-and-first-test.md +0 -210
  334. package/lib/assets/docs/article/getting-start-tdd/python/02-fake-it-and-triangulation.md +0 -256
  335. package/lib/assets/docs/article/getting-start-tdd/python/03-obvious-implementation-and-refactoring.md +0 -208
  336. package/lib/assets/docs/article/getting-start-tdd/python/04-version-control-and-conventional-commits.md +0 -96
  337. package/lib/assets/docs/article/getting-start-tdd/python/05-package-management-and-static-analysis.md +0 -283
  338. package/lib/assets/docs/article/getting-start-tdd/python/06-task-runner-and-ci-cd.md +0 -199
  339. package/lib/assets/docs/article/getting-start-tdd/python/07-encapsulation-and-polymorphism.md +0 -217
  340. package/lib/assets/docs/article/getting-start-tdd/python/08-design-patterns.md +0 -181
  341. package/lib/assets/docs/article/getting-start-tdd/python/09-solid-principles-and-module-design.md +0 -139
  342. package/lib/assets/docs/article/getting-start-tdd/python/10-higher-order-functions-and-composition.md +0 -131
  343. package/lib/assets/docs/article/getting-start-tdd/python/11-immutable-data-and-pipeline.md +0 -125
  344. package/lib/assets/docs/article/getting-start-tdd/python/12-error-handling-and-type-safety.md +0 -135
  345. package/lib/assets/docs/article/getting-start-tdd/python/index.md +0 -55
  346. package/lib/assets/docs/article/getting-start-tdd/ruby/01-todo-list-and-first-test.md +0 -240
  347. package/lib/assets/docs/article/getting-start-tdd/ruby/02-fake-it-and-triangulation.md +0 -247
  348. package/lib/assets/docs/article/getting-start-tdd/ruby/03-obvious-implementation-and-refactoring.md +0 -237
  349. package/lib/assets/docs/article/getting-start-tdd/ruby/04-version-control-and-conventional-commits.md +0 -121
  350. package/lib/assets/docs/article/getting-start-tdd/ruby/05-package-management-and-static-analysis.md +0 -296
  351. package/lib/assets/docs/article/getting-start-tdd/ruby/06-task-runner-and-ci-cd.md +0 -257
  352. package/lib/assets/docs/article/getting-start-tdd/ruby/07-encapsulation-and-polymorphism.md +0 -288
  353. package/lib/assets/docs/article/getting-start-tdd/ruby/08-design-patterns.md +0 -338
  354. package/lib/assets/docs/article/getting-start-tdd/ruby/09-solid-principles-and-module-design.md +0 -205
  355. package/lib/assets/docs/article/getting-start-tdd/ruby/10-higher-order-functions-and-composition.md +0 -184
  356. package/lib/assets/docs/article/getting-start-tdd/ruby/11-immutable-data-and-pipeline.md +0 -246
  357. package/lib/assets/docs/article/getting-start-tdd/ruby/12-error-handling-and-type-safety.md +0 -407
  358. package/lib/assets/docs/article/getting-start-tdd/ruby/index.md +0 -83
  359. package/lib/assets/docs/article/getting-start-tdd/rust/01-todo-list-and-first-test.md +0 -220
  360. package/lib/assets/docs/article/getting-start-tdd/rust/02-fake-it-and-triangulation.md +0 -273
  361. package/lib/assets/docs/article/getting-start-tdd/rust/03-obvious-implementation-and-refactoring.md +0 -242
  362. package/lib/assets/docs/article/getting-start-tdd/rust/04-version-control-and-conventional-commits.md +0 -101
  363. package/lib/assets/docs/article/getting-start-tdd/rust/05-package-management-and-static-analysis.md +0 -221
  364. package/lib/assets/docs/article/getting-start-tdd/rust/06-task-runner-and-ci-cd.md +0 -173
  365. package/lib/assets/docs/article/getting-start-tdd/rust/07-encapsulation-and-polymorphism.md +0 -151
  366. package/lib/assets/docs/article/getting-start-tdd/rust/08-design-patterns.md +0 -154
  367. package/lib/assets/docs/article/getting-start-tdd/rust/09-solid-principles-and-module-design.md +0 -119
  368. package/lib/assets/docs/article/getting-start-tdd/rust/10-higher-order-functions-and-composition.md +0 -103
  369. package/lib/assets/docs/article/getting-start-tdd/rust/11-immutable-data-and-pipeline.md +0 -114
  370. package/lib/assets/docs/article/getting-start-tdd/rust/12-error-handling-and-type-safety.md +0 -121
  371. package/lib/assets/docs/article/getting-start-tdd/rust/index.md +0 -83
  372. package/lib/assets/docs/article/getting-start-tdd/scala/01-todo-list-and-first-test.md +0 -120
  373. package/lib/assets/docs/article/getting-start-tdd/scala/02-fake-it-and-triangulation.md +0 -116
  374. package/lib/assets/docs/article/getting-start-tdd/scala/03-obvious-implementation-and-refactoring.md +0 -108
  375. package/lib/assets/docs/article/getting-start-tdd/scala/04-version-control-and-conventional-commits.md +0 -132
  376. package/lib/assets/docs/article/getting-start-tdd/scala/05-package-management-and-static-analysis.md +0 -205
  377. package/lib/assets/docs/article/getting-start-tdd/scala/06-task-runner-and-ci-cd.md +0 -195
  378. package/lib/assets/docs/article/getting-start-tdd/scala/07-case-classes-and-traits.md +0 -148
  379. package/lib/assets/docs/article/getting-start-tdd/scala/08-pattern-matching-and-sealed-traits.md +0 -115
  380. package/lib/assets/docs/article/getting-start-tdd/scala/09-packages-and-module-design.md +0 -84
  381. package/lib/assets/docs/article/getting-start-tdd/scala/10-higher-order-functions-and-composition.md +0 -113
  382. package/lib/assets/docs/article/getting-start-tdd/scala/11-collections-and-lazy-evaluation.md +0 -103
  383. package/lib/assets/docs/article/getting-start-tdd/scala/12-error-handling-and-type-safety.md +0 -101
  384. package/lib/assets/docs/article/getting-start-tdd/scala/index.md +0 -65
  385. package/lib/assets/docs/article/grokking-concurrency/all/index.md +0 -403
  386. package/lib/assets/docs/article/grokking-concurrency/all/part-1-ch02-sequential.md +0 -563
  387. package/lib/assets/docs/article/grokking-concurrency/all/part-2-ch04-05-threads.md +0 -478
  388. package/lib/assets/docs/article/grokking-concurrency/all/part-3-ch06-multitasking.md +0 -529
  389. package/lib/assets/docs/article/grokking-concurrency/all/part-4-ch07-parallel-patterns.md +0 -429
  390. package/lib/assets/docs/article/grokking-concurrency/all/part-5-ch08-09-synchronization.md +0 -519
  391. package/lib/assets/docs/article/grokking-concurrency/all/part-6-ch10-11-nonblocking-io.md +0 -444
  392. package/lib/assets/docs/article/grokking-concurrency/all/part-7-ch12-async.md +0 -474
  393. package/lib/assets/docs/article/grokking-concurrency/all/part-8-ch13-mapreduce.md +0 -386
  394. package/lib/assets/docs/article/grokking-concurrency/clojure/index.md +0 -106
  395. package/lib/assets/docs/article/grokking-concurrency/clojure/part-1.md +0 -107
  396. package/lib/assets/docs/article/grokking-concurrency/clojure/part-2.md +0 -100
  397. package/lib/assets/docs/article/grokking-concurrency/clojure/part-3.md +0 -121
  398. package/lib/assets/docs/article/grokking-concurrency/clojure/part-4.md +0 -122
  399. package/lib/assets/docs/article/grokking-concurrency/clojure/part-5.md +0 -117
  400. package/lib/assets/docs/article/grokking-concurrency/clojure/part-6.md +0 -88
  401. package/lib/assets/docs/article/grokking-concurrency/clojure/part-7.md +0 -99
  402. package/lib/assets/docs/article/grokking-concurrency/clojure/part-8.md +0 -119
  403. package/lib/assets/docs/article/grokking-concurrency/csharp/index.md +0 -91
  404. package/lib/assets/docs/article/grokking-concurrency/csharp/part-1.md +0 -96
  405. package/lib/assets/docs/article/grokking-concurrency/csharp/part-2.md +0 -122
  406. package/lib/assets/docs/article/grokking-concurrency/csharp/part-3.md +0 -100
  407. package/lib/assets/docs/article/grokking-concurrency/csharp/part-4.md +0 -111
  408. package/lib/assets/docs/article/grokking-concurrency/csharp/part-5.md +0 -98
  409. package/lib/assets/docs/article/grokking-concurrency/csharp/part-6.md +0 -60
  410. package/lib/assets/docs/article/grokking-concurrency/csharp/part-7.md +0 -83
  411. package/lib/assets/docs/article/grokking-concurrency/csharp/part-8.md +0 -91
  412. package/lib/assets/docs/article/grokking-concurrency/fsharp/index.md +0 -55
  413. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-1.md +0 -79
  414. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-2.md +0 -102
  415. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-3.md +0 -93
  416. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-4.md +0 -109
  417. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-5.md +0 -103
  418. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-6.md +0 -92
  419. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-7.md +0 -120
  420. package/lib/assets/docs/article/grokking-concurrency/fsharp/part-8.md +0 -106
  421. package/lib/assets/docs/article/grokking-concurrency/haskell/index.md +0 -238
  422. package/lib/assets/docs/article/grokking-concurrency/haskell/part-1.md +0 -95
  423. package/lib/assets/docs/article/grokking-concurrency/haskell/part-2.md +0 -95
  424. package/lib/assets/docs/article/grokking-concurrency/haskell/part-3.md +0 -90
  425. package/lib/assets/docs/article/grokking-concurrency/haskell/part-4.md +0 -105
  426. package/lib/assets/docs/article/grokking-concurrency/haskell/part-5.md +0 -98
  427. package/lib/assets/docs/article/grokking-concurrency/haskell/part-6.md +0 -94
  428. package/lib/assets/docs/article/grokking-concurrency/haskell/part-7.md +0 -110
  429. package/lib/assets/docs/article/grokking-concurrency/haskell/part-8.md +0 -117
  430. package/lib/assets/docs/article/grokking-concurrency/index.md +0 -66
  431. package/lib/assets/docs/article/grokking-concurrency/java/index.md +0 -92
  432. package/lib/assets/docs/article/grokking-concurrency/java/part-1.md +0 -307
  433. package/lib/assets/docs/article/grokking-concurrency/java/part-2.md +0 -333
  434. package/lib/assets/docs/article/grokking-concurrency/java/part-3.md +0 -220
  435. package/lib/assets/docs/article/grokking-concurrency/java/part-4.md +0 -212
  436. package/lib/assets/docs/article/grokking-concurrency/java/part-5.md +0 -111
  437. package/lib/assets/docs/article/grokking-concurrency/java/part-6.md +0 -68
  438. package/lib/assets/docs/article/grokking-concurrency/java/part-7.md +0 -100
  439. package/lib/assets/docs/article/grokking-concurrency/java/part-8.md +0 -100
  440. package/lib/assets/docs/article/grokking-concurrency/python/index.md +0 -303
  441. package/lib/assets/docs/article/grokking-concurrency/python/part-1.md +0 -238
  442. package/lib/assets/docs/article/grokking-concurrency/python/part-2.md +0 -417
  443. package/lib/assets/docs/article/grokking-concurrency/python/part-3.md +0 -226
  444. package/lib/assets/docs/article/grokking-concurrency/python/part-4.md +0 -298
  445. package/lib/assets/docs/article/grokking-concurrency/python/part-5.md +0 -314
  446. package/lib/assets/docs/article/grokking-concurrency/python/part-6.md +0 -296
  447. package/lib/assets/docs/article/grokking-concurrency/python/part-7.md +0 -313
  448. package/lib/assets/docs/article/grokking-concurrency/python/part-8.md +0 -359
  449. package/lib/assets/docs/article/grokking-concurrency/rust/index.md +0 -260
  450. package/lib/assets/docs/article/grokking-concurrency/rust/part-1.md +0 -107
  451. package/lib/assets/docs/article/grokking-concurrency/rust/part-2.md +0 -119
  452. package/lib/assets/docs/article/grokking-concurrency/rust/part-3.md +0 -125
  453. package/lib/assets/docs/article/grokking-concurrency/rust/part-4.md +0 -174
  454. package/lib/assets/docs/article/grokking-concurrency/rust/part-5.md +0 -157
  455. package/lib/assets/docs/article/grokking-concurrency/rust/part-6.md +0 -93
  456. package/lib/assets/docs/article/grokking-concurrency/rust/part-7.md +0 -132
  457. package/lib/assets/docs/article/grokking-concurrency/rust/part-8.md +0 -154
  458. package/lib/assets/docs/article/grokking-concurrency/scala/index.md +0 -59
  459. package/lib/assets/docs/article/grokking-concurrency/scala/part-1.md +0 -77
  460. package/lib/assets/docs/article/grokking-concurrency/scala/part-2.md +0 -111
  461. package/lib/assets/docs/article/grokking-concurrency/scala/part-3.md +0 -92
  462. package/lib/assets/docs/article/grokking-concurrency/scala/part-4.md +0 -109
  463. package/lib/assets/docs/article/grokking-concurrency/scala/part-5.md +0 -118
  464. package/lib/assets/docs/article/grokking-concurrency/scala/part-6.md +0 -82
  465. package/lib/assets/docs/article/grokking-concurrency/scala/part-7.md +0 -130
  466. package/lib/assets/docs/article/grokking-concurrency/scala/part-8.md +0 -128
  467. package/lib/assets/docs/article/grokkingfp/all/index.md +0 -368
  468. package/lib/assets/docs/article/grokkingfp/all/part-1-ch01-fp-introduction.md +0 -539
  469. package/lib/assets/docs/article/grokkingfp/all/part-1-ch02-pure-functions.md +0 -932
  470. package/lib/assets/docs/article/grokkingfp/all/part-2-ch03-immutable-data.md +0 -1137
  471. package/lib/assets/docs/article/grokkingfp/all/part-2-ch04-higher-order-functions.md +0 -1113
  472. package/lib/assets/docs/article/grokkingfp/all/part-2-ch05-flatmap.md +0 -1035
  473. package/lib/assets/docs/article/grokkingfp/all/part-3-ch06-option.md +0 -794
  474. package/lib/assets/docs/article/grokkingfp/all/part-3-ch07-either-adt.md +0 -880
  475. package/lib/assets/docs/article/grokkingfp/all/part-4-ch08-io-monad.md +0 -981
  476. package/lib/assets/docs/article/grokkingfp/all/part-4-ch09-streams.md +0 -935
  477. package/lib/assets/docs/article/grokkingfp/all/part-5-ch10-concurrency.md +0 -879
  478. package/lib/assets/docs/article/grokkingfp/all/part-6-ch11-application.md +0 -724
  479. package/lib/assets/docs/article/grokkingfp/all/part-6-ch12-testing.md +0 -635
  480. package/lib/assets/docs/article/grokkingfp/all/writing-plan.md +0 -721
  481. package/lib/assets/docs/article/grokkingfp/clojure/index.md +0 -276
  482. package/lib/assets/docs/article/grokkingfp/clojure/part-1.md +0 -676
  483. package/lib/assets/docs/article/grokkingfp/clojure/part-2.md +0 -652
  484. package/lib/assets/docs/article/grokkingfp/clojure/part-3.md +0 -629
  485. package/lib/assets/docs/article/grokkingfp/clojure/part-4.md +0 -706
  486. package/lib/assets/docs/article/grokkingfp/clojure/part-5.md +0 -760
  487. package/lib/assets/docs/article/grokkingfp/clojure/part-6.md +0 -730
  488. package/lib/assets/docs/article/grokkingfp/csharp/index.md +0 -246
  489. package/lib/assets/docs/article/grokkingfp/csharp/part-1.md +0 -820
  490. package/lib/assets/docs/article/grokkingfp/csharp/part-2.md +0 -980
  491. package/lib/assets/docs/article/grokkingfp/csharp/part-3.md +0 -990
  492. package/lib/assets/docs/article/grokkingfp/csharp/part-4.md +0 -958
  493. package/lib/assets/docs/article/grokkingfp/csharp/part-5.md +0 -956
  494. package/lib/assets/docs/article/grokkingfp/csharp/part-6.md +0 -748
  495. package/lib/assets/docs/article/grokkingfp/elixir/index.md +0 -203
  496. package/lib/assets/docs/article/grokkingfp/elixir/part-1.md +0 -721
  497. package/lib/assets/docs/article/grokkingfp/elixir/part-2.md +0 -847
  498. package/lib/assets/docs/article/grokkingfp/elixir/part-3.md +0 -994
  499. package/lib/assets/docs/article/grokkingfp/elixir/part-4.md +0 -983
  500. package/lib/assets/docs/article/grokkingfp/elixir/part-5.md +0 -1295
  501. package/lib/assets/docs/article/grokkingfp/elixir/part-6.md +0 -1058
  502. package/lib/assets/docs/article/grokkingfp/fsharp/index.md +0 -210
  503. package/lib/assets/docs/article/grokkingfp/fsharp/part-1.md +0 -723
  504. package/lib/assets/docs/article/grokkingfp/fsharp/part-2.md +0 -970
  505. package/lib/assets/docs/article/grokkingfp/fsharp/part-3.md +0 -981
  506. package/lib/assets/docs/article/grokkingfp/fsharp/part-4.md +0 -841
  507. package/lib/assets/docs/article/grokkingfp/fsharp/part-5.md +0 -920
  508. package/lib/assets/docs/article/grokkingfp/fsharp/part-6.md +0 -931
  509. package/lib/assets/docs/article/grokkingfp/haskell/index.md +0 -234
  510. package/lib/assets/docs/article/grokkingfp/haskell/part-1.md +0 -600
  511. package/lib/assets/docs/article/grokkingfp/haskell/part-2.md +0 -875
  512. package/lib/assets/docs/article/grokkingfp/haskell/part-3.md +0 -924
  513. package/lib/assets/docs/article/grokkingfp/haskell/part-4.md +0 -887
  514. package/lib/assets/docs/article/grokkingfp/haskell/part-5.md +0 -854
  515. package/lib/assets/docs/article/grokkingfp/haskell/part-6.md +0 -853
  516. package/lib/assets/docs/article/grokkingfp/index.md +0 -143
  517. package/lib/assets/docs/article/grokkingfp/java/index.md +0 -211
  518. package/lib/assets/docs/article/grokkingfp/java/part-1.md +0 -657
  519. package/lib/assets/docs/article/grokkingfp/java/part-2.md +0 -684
  520. package/lib/assets/docs/article/grokkingfp/java/part-3.md +0 -681
  521. package/lib/assets/docs/article/grokkingfp/java/part-4.md +0 -780
  522. package/lib/assets/docs/article/grokkingfp/java/part-5.md +0 -968
  523. package/lib/assets/docs/article/grokkingfp/java/part-6.md +0 -1337
  524. package/lib/assets/docs/article/grokkingfp/python/index.md +0 -258
  525. package/lib/assets/docs/article/grokkingfp/python/part-1.md +0 -452
  526. package/lib/assets/docs/article/grokkingfp/python/part-2.md +0 -967
  527. package/lib/assets/docs/article/grokkingfp/python/part-3.md +0 -1013
  528. package/lib/assets/docs/article/grokkingfp/python/part-4.md +0 -774
  529. package/lib/assets/docs/article/grokkingfp/python/part-5.md +0 -756
  530. package/lib/assets/docs/article/grokkingfp/python/part-6.md +0 -870
  531. package/lib/assets/docs/article/grokkingfp/ruby/index.md +0 -330
  532. package/lib/assets/docs/article/grokkingfp/ruby/part-1.md +0 -764
  533. package/lib/assets/docs/article/grokkingfp/ruby/part-2.md +0 -947
  534. package/lib/assets/docs/article/grokkingfp/ruby/part-3.md +0 -955
  535. package/lib/assets/docs/article/grokkingfp/ruby/part-4.md +0 -930
  536. package/lib/assets/docs/article/grokkingfp/ruby/part-5.md +0 -917
  537. package/lib/assets/docs/article/grokkingfp/ruby/part-6.md +0 -1421
  538. package/lib/assets/docs/article/grokkingfp/rust/index.md +0 -242
  539. package/lib/assets/docs/article/grokkingfp/rust/part-1.md +0 -643
  540. package/lib/assets/docs/article/grokkingfp/rust/part-2.md +0 -1069
  541. package/lib/assets/docs/article/grokkingfp/rust/part-3.md +0 -1003
  542. package/lib/assets/docs/article/grokkingfp/rust/part-4.md +0 -582
  543. package/lib/assets/docs/article/grokkingfp/rust/part-5.md +0 -714
  544. package/lib/assets/docs/article/grokkingfp/rust/part-6.md +0 -517
  545. package/lib/assets/docs/article/grokkingfp/scala/index.md +0 -171
  546. package/lib/assets/docs/article/grokkingfp/scala/part-1.md +0 -552
  547. package/lib/assets/docs/article/grokkingfp/scala/part-2.md +0 -955
  548. package/lib/assets/docs/article/grokkingfp/scala/part-3.md +0 -928
  549. package/lib/assets/docs/article/grokkingfp/scala/part-4.md +0 -751
  550. package/lib/assets/docs/article/grokkingfp/scala/part-5.md +0 -731
  551. package/lib/assets/docs/article/grokkingfp/scala/part-6.md +0 -876
  552. package/lib/assets/docs/article/grokkingfp/typescript/index.md +0 -273
  553. package/lib/assets/docs/article/grokkingfp/typescript/part-1.md +0 -570
  554. package/lib/assets/docs/article/grokkingfp/typescript/part-2.md +0 -1138
  555. package/lib/assets/docs/article/grokkingfp/typescript/part-3.md +0 -851
  556. package/lib/assets/docs/article/grokkingfp/typescript/part-4.md +0 -1096
  557. package/lib/assets/docs/article/grokkingfp/typescript/part-5.md +0 -726
  558. package/lib/assets/docs/article/grokkingfp/typescript/part-6.md +0 -991
  559. package/lib/assets/docs/article/practical-database-design/index.md +0 -121
  560. package/lib/assets/docs/article/practical-database-design/part1/chapter01.md +0 -297
  561. package/lib/assets/docs/article/practical-database-design/part1/chapter02.md +0 -527
  562. package/lib/assets/docs/article/practical-database-design/part1/chapter03.md +0 -566
  563. package/lib/assets/docs/article/practical-database-design/part2/chapter04.md +0 -933
  564. package/lib/assets/docs/article/practical-database-design/part2/chapter05.md +0 -1636
  565. package/lib/assets/docs/article/practical-database-design/part2/chapter06.md +0 -2725
  566. package/lib/assets/docs/article/practical-database-design/part2/chapter07.md +0 -2091
  567. package/lib/assets/docs/article/practical-database-design/part2/chapter08.md +0 -2114
  568. package/lib/assets/docs/article/practical-database-design/part2/chapter09.md +0 -2040
  569. package/lib/assets/docs/article/practical-database-design/part2/chapter10.md +0 -1396
  570. package/lib/assets/docs/article/practical-database-design/part2/chapter11.md +0 -1686
  571. package/lib/assets/docs/article/practical-database-design/part2/chapter12.md +0 -1426
  572. package/lib/assets/docs/article/practical-database-design/part2/chapter13.md +0 -1443
  573. package/lib/assets/docs/article/practical-database-design/part3/chapter14.md +0 -676
  574. package/lib/assets/docs/article/practical-database-design/part3/chapter15.md +0 -1634
  575. package/lib/assets/docs/article/practical-database-design/part3/chapter16.md +0 -1924
  576. package/lib/assets/docs/article/practical-database-design/part3/chapter17.md +0 -1717
  577. package/lib/assets/docs/article/practical-database-design/part3/chapter18.md +0 -2104
  578. package/lib/assets/docs/article/practical-database-design/part3/chapter19.md +0 -1132
  579. package/lib/assets/docs/article/practical-database-design/part3/chapter20.md +0 -1040
  580. package/lib/assets/docs/article/practical-database-design/part3/chapter21.md +0 -1391
  581. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter14-orm.md +0 -1000
  582. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter15-orm.md +0 -1309
  583. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter16-orm.md +0 -1175
  584. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter17-orm.md +0 -1593
  585. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter18-orm.md +0 -1192
  586. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter19-orm.md +0 -1025
  587. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter20-orm.md +0 -1762
  588. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter21-orm.md +0 -1456
  589. package/lib/assets/docs/article/practical-database-design/part3-orm/chapter22-orm.md +0 -1887
  590. package/lib/assets/docs/article/practical-database-design/part4/chapter22.md +0 -974
  591. package/lib/assets/docs/article/practical-database-design/part4/chapter23.md +0 -2078
  592. package/lib/assets/docs/article/practical-database-design/part4/chapter24.md +0 -2448
  593. package/lib/assets/docs/article/practical-database-design/part4/chapter25.md +0 -3670
  594. package/lib/assets/docs/article/practical-database-design/part4/chapter26.md +0 -2925
  595. package/lib/assets/docs/article/practical-database-design/part4/chapter27.md +0 -3114
  596. package/lib/assets/docs/article/practical-database-design/part4/chapter28.md +0 -2706
  597. package/lib/assets/docs/article/practical-database-design/part4/chapter29.md +0 -2552
  598. package/lib/assets/docs/article/practical-database-design/part4/chapter30.md +0 -2188
  599. package/lib/assets/docs/article/practical-database-design/part4/chapter31.md +0 -1200
  600. package/lib/assets/docs/article/practical-database-design/part4/chapter32.md +0 -2109
  601. package/lib/assets/docs/article/practical-database-design/part5/chapter33.md +0 -1041
  602. package/lib/assets/docs/article/practical-database-design/part5/chapter34.md +0 -1618
  603. package/lib/assets/docs/article/practical-database-design/part5/chapter35.md +0 -1462
  604. package/lib/assets/docs/article/practical-database-design/part5/chapter36.md +0 -1301
  605. package/lib/assets/docs/article/practical-database-design/part5/chapter37.md +0 -1479
  606. package/lib/assets/docs/article/practical-database-design/part5/chapter38.md +0 -1707
  607. package/lib/assets/docs/article/practical-database-design/part5/chapter39.md +0 -2343
  608. package/lib/assets/docs/article/practical-database-design/study/study2-1.md +0 -1702
  609. package/lib/assets/docs/article/practical-database-design/study/study2-2.md +0 -1356
  610. package/lib/assets/docs/article/practical-database-design/study/study2-3.md +0 -2053
  611. package/lib/assets/docs/article/practical-database-design/study/study2-4.md +0 -2238
  612. package/lib/assets/docs/article/practical-database-design/study/study2-5.md +0 -2427
  613. package/lib/assets/docs/article/practical-database-design/study/study3-1.md +0 -2214
  614. package/lib/assets/docs/article/practical-database-design/study/study3-2.md +0 -2230
  615. package/lib/assets/docs/article/practical-database-design/study/study3-3.md +0 -2262
  616. package/lib/assets/docs/article/practical-database-design/study/study3-4.md +0 -2115
  617. package/lib/assets/docs/article/practical-database-design/study/study3-5.md +0 -2516
  618. package/lib/assets/docs/article/practical-database-design/study/study4-1.md +0 -2596
  619. package/lib/assets/docs/article/practical-database-design/study/study4-2.md +0 -2084
  620. package/lib/assets/docs/article/practical-database-design/study/study4-3.md +0 -1814
  621. package/lib/assets/docs/article/practical-database-design/study/study4-4.md +0 -1904
  622. package/lib/assets/docs/article/practical-database-design/study/study4-5.md +0 -2887
@@ -1,2516 +0,0 @@
1
- ---
2
- type: Article
3
- title: "実践データベース設計:財務会計システム 研究 5 - Axon CQRS/ES の実装"
4
- description: "本研究では、REST API(第21章)、gRPC(研究 3)、GraphQL(研究 4)とは異なるアプローチとして、CQRS(Command Query Responsibility Segregation) と Event Sourci"
5
- tags: [article, practical-database-design, study]
6
- status: stable
7
- generated: { by: human:kakimomokuri, at: 2026-03-30T08:34:28Z }
8
- ---
9
-
10
- # 実践データベース設計:財務会計システム 研究 5 - Axon CQRS/ES の実装
11
-
12
- ## はじめに
13
-
14
- 本研究では、REST API(第21章)、gRPC(研究 3)、GraphQL(研究 4)とは異なるアプローチとして、**CQRS(Command Query Responsibility Segregation)** と **Event Sourcing** による財務会計システムを実装します。Axon Framework を使用し、コマンド(書き込み)とクエリ(読み取り)を分離し、すべての状態変更をイベントとして記録するアーキテクチャを構築します。
15
-
16
- 研究 1 で構築したヘキサゴナルアーキテクチャの考え方を踏襲しつつ、**ドメインモデルをフレームワークから分離**し、Axon Aggregate Adapter を介して連携させます。Read Model の永続化には **MyBatis** を使用します。
17
-
18
- 財務会計システムでは、**仕訳(Journal Entry)** の作成から承認、転記までのワークフローを CQRS/ES で実装し、すべての取引履歴を完全に追跡可能な形で保存します。
19
-
20
- ---
21
-
22
- ## 第26章:Axon CQRS/ES アーキテクチャの基礎
23
-
24
- ### 26.1 CQRS とは
25
-
26
- CQRS(Command Query Responsibility Segregation)は、コマンド(書き込み)とクエリ(読み取り)の責務を分離するアーキテクチャパターンです。従来の CRUD 操作を単一のモデルで処理するのではなく、書き込み用のモデルと読み取り用のモデルを分けることで、それぞれに最適化された設計が可能になります。
27
-
28
- ```plantuml
29
- @startuml cqrs_architecture
30
- skinparam componentStyle rectangle
31
- skinparam backgroundColor #FEFEFE
32
-
33
- title CQRS アーキテクチャ(財務会計システム)
34
-
35
- package "Command Side (Write)" {
36
- [REST Controller] --> [CommandGateway]
37
- [CommandGateway] --> [Aggregate]
38
- [Aggregate] --> [Event Store]
39
- }
40
-
41
- package "Query Side (Read)" {
42
- [Event Store] --> [Projection]
43
- [Projection] --> [Read Model DB]
44
- [REST Controller] --> [MyBatis Mapper]
45
- [MyBatis Mapper] --> [Read Model DB]
46
- }
47
-
48
- @enduml
49
- ```
50
-
51
- **CQRS の利点:**
52
-
53
- | 観点 | 説明 |
54
- |------|------|
55
- | **スケーラビリティ** | 読み取りと書き込みを独立してスケール可能 |
56
- | **パフォーマンス** | 読み取りに最適化されたモデルで高速クエリ |
57
- | **複雑性の分離** | 書き込みロジックと読み取りロジックを独立して開発 |
58
- | **監査対応** | Event Sourcing と組み合わせて完全な履歴を保持 |
59
-
60
- **財務会計システムでの CQRS の意義:**
61
-
62
- - **監査証跡**: すべての仕訳変更履歴を完全に保持
63
- - **複雑なクエリ**: 試算表、財務諸表など多様なビューを効率的に生成
64
- - **データ整合性**: イベントベースで一貫性を保証
65
-
66
- ---
67
-
68
- ### 26.2 Event Sourcing とは
69
-
70
- Event Sourcing は、アプリケーションの状態をイベントの連続として保存するパターンです。現在の状態を直接保存するのではなく、状態変更を表すイベントを記録し、必要に応じてイベントを再生して現在の状態を再構築します。
71
-
72
- ```plantuml
73
- @startuml event_sourcing
74
- skinparam componentStyle rectangle
75
- skinparam backgroundColor #FEFEFE
76
-
77
- [Command] --> [Aggregate] : 1. コマンド受信
78
- [Aggregate] --> [Domain Model] : 2. ビジネスロジック実行
79
- [Domain Model] --> [Event] : 3. イベント生成
80
- [Event] --> [Event Store] : 4. イベント永続化
81
- [Event Store] --> [Aggregate] : 5. イベント再生(状態復元)
82
-
83
- @enduml
84
- ```
85
-
86
- **Event Sourcing の利点:**
87
-
88
- | 観点 | 説明 |
89
- |------|------|
90
- | **完全な履歴** | すべての状態変更が記録される |
91
- | **監査証跡** | いつ、誰が、何を変更したかが明確 |
92
- | **時間旅行** | 過去の任意の時点の状態を再構築可能 |
93
- | **イベント駆動** | 他システムとの連携が容易 |
94
- | **デバッグ** | 問題発生時にイベントを追跡可能 |
95
-
96
- **財務会計システムでの Event Sourcing の意義:**
97
-
98
- - **訂正仕訳の追跡**: 修正履歴を完全に保持
99
- - **期末決算処理**: 任意の時点での残高を再計算可能
100
- - **内部統制**: SOX 法対応の監査証跡を自動生成
101
-
102
- ---
103
-
104
- ### 26.3 ヘキサゴナルアーキテクチャとの統合
105
-
106
- 本実装では、ヘキサゴナルアーキテクチャ(Ports & Adapters)を採用し、ビジネスロジックを外部依存から分離します。
107
-
108
- ```plantuml
109
- @startuml hexagonal_cqrs
110
- !define RECTANGLE class
111
- skinparam backgroundColor #FEFEFE
112
-
113
- package "Hexagonal Architecture (CQRS/ES 版)" {
114
-
115
- package "Inbound Adapters" {
116
- [REST Controller]
117
- }
118
-
119
- package "Application Core" {
120
- package "Application Layer" {
121
- [Aggregate Adapter]
122
- [Projection]
123
- [Policy Handler]
124
- }
125
-
126
- package "Domain Layer" {
127
- [Domain Model]
128
- [Commands]
129
- [Value Objects]
130
- }
131
-
132
- package "API Layer" {
133
- [Events]
134
- }
135
- }
136
-
137
- package "Outbound Adapters" {
138
- [MyBatis Mapper]
139
- [Event Store]
140
- }
141
- }
142
-
143
- [REST Controller] --> [Aggregate Adapter]
144
- [Aggregate Adapter] --> [Domain Model]
145
- [Projection] --> [MyBatis Mapper]
146
-
147
- note top of [Domain Model]
148
- 純粋なドメインモデル
149
- Axon Framework に依存しない
150
- ビジネスロジックのみ
151
- end note
152
-
153
- note left of [Aggregate Adapter]
154
- Axon 用アダプター
155
- @Aggregate, @CommandHandler
156
- フレームワーク依存を吸収
157
- end note
158
-
159
- @enduml
160
- ```
161
-
162
- **設計原則:**
163
-
164
- 1. **ドメインモデルの純粋性**: ドメインモデルは Axon に依存しない純粋な Java コード
165
- 2. **Aggregate Adapter**: Axon Framework 用のアダプターを Application Layer に配置
166
- 3. **イベントは公開 API**: イベントは他の Context から参照される公開 API として定義
167
-
168
- ---
169
-
170
- ### 26.4 技術スタック
171
-
172
- | カテゴリ | 技術 | バージョン |
173
- |---------|------|-----------|
174
- | 言語 | Java | 21 |
175
- | フレームワーク | Spring Boot | 3.4.1 |
176
- | CQRS/ES | Axon Framework | 4.10.3 |
177
- | ORM | MyBatis | 3.0.4 |
178
- | API ドキュメント | SpringDoc OpenAPI | 2.7.0 |
179
- | データベース | H2 (開発) / PostgreSQL (本番) | - |
180
-
181
- #### build.gradle.kts
182
-
183
- <details>
184
- <summary>コード例: build.gradle.kts</summary>
185
-
186
- ```kotlin
187
- dependencies {
188
- // Spring Boot
189
- implementation("org.springframework.boot:spring-boot-starter-web")
190
- implementation("org.springframework.boot:spring-boot-starter-validation")
191
-
192
- // Axon Framework
193
- implementation("org.axonframework:axon-spring-boot-starter:4.10.3") {
194
- exclude(group = "org.axonframework", module = "axon-server-connector")
195
- }
196
-
197
- // MyBatis
198
- implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4")
199
-
200
- // OpenAPI
201
- implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0")
202
-
203
- // Database
204
- runtimeOnly("com.h2database:h2")
205
- runtimeOnly("org.postgresql:postgresql")
206
-
207
- // Test
208
- testImplementation("org.springframework.boot:spring-boot-starter-test")
209
- testImplementation("org.axonframework:axon-test:4.10.3")
210
- testImplementation("org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.4")
211
- }
212
- ```
213
-
214
- </details>
215
-
216
- ---
217
-
218
- ### 26.5 ディレクトリ構造
219
-
220
- <details>
221
- <summary>コード例: ディレクトリ構成</summary>
222
-
223
- ```
224
- src/main/java/com/example/accounting/
225
- ├── app/ # アプリケーション共通
226
- │ └── RootController.java
227
- ├── config/ # 設定クラス
228
- │ ├── AxonConfig.java
229
- │ ├── MyBatisConfig.java
230
- │ └── OpenApiConfig.java
231
- ├── journal/ # Journal Bounded Context(仕訳)
232
- │ ├── api/
233
- │ │ └── events/ # 公開イベント API
234
- │ │ ├── JournalEntryEvent.java # sealed interface
235
- │ │ ├── JournalEntryCreatedEvent.java
236
- │ │ ├── JournalEntryApprovedEvent.java
237
- │ │ └── JournalEntryPostedEvent.java
238
- │ ├── adapter/
239
- │ │ ├── inbound/rest/journals/ # Inbound Adapter (REST)
240
- │ │ │ ├── JournalsController.java
241
- │ │ │ └── protocol/ # Request/Response DTO
242
- │ │ └── outbound/persistence/ # Outbound Adapter (MyBatis)
243
- │ │ ├── entity/
244
- │ │ │ └── JournalEntryEntity.java
245
- │ │ └── mapper/
246
- │ │ └── JournalEntryMapper.java
247
- │ ├── application/
248
- │ │ ├── aggregate/ # Axon Aggregate Adapter
249
- │ │ │ └── JournalEntryAggregateAdapter.java
250
- │ │ ├── policy/ # イベントハンドラー(Choreography)
251
- │ │ │ └── AccountEventHandler.java
252
- │ │ └── query/ # Projection
253
- │ │ └── JournalEntryProjection.java
254
- │ └── domain/
255
- │ └── model/aggregate/journal/ # 純粋なドメインモデル
256
- │ ├── JournalEntry.java
257
- │ ├── JournalEntryCommands.java
258
- │ ├── JournalEntryStatus.java
259
- │ └── JournalLine.java
260
- └── account/ # Account Bounded Context(勘定科目)
261
- ├── api/events/
262
- │ ├── AccountEvent.java
263
- │ ├── AccountBalanceUpdatedEvent.java
264
- │ └── AccountClosedEvent.java
265
- ├── application/
266
- │ ├── aggregate/
267
- │ │ └── AccountAggregateAdapter.java
268
- │ └── policy/
269
- │ └── JournalEventHandler.java
270
- └── domain/model/aggregate/account/
271
- ├── Account.java
272
- └── AccountCommands.java
273
-
274
- src/main/resources/
275
- ├── application.yml
276
- ├── schema.sql # Read Model スキーマ
277
- └── mapper/
278
- └── JournalEntryMapper.xml # MyBatis マッパー XML
279
- ```
280
-
281
- </details>
282
-
283
- ---
284
-
285
- ### 26.6 Axon 設定クラス
286
-
287
- <details>
288
- <summary>コード例: AxonConfig.java</summary>
289
-
290
- ```java
291
- package com.example.accounting.config;
292
-
293
- import org.axonframework.eventsourcing.eventstore.EmbeddedEventStore;
294
- import org.axonframework.eventsourcing.eventstore.EventStorageEngine;
295
- import org.axonframework.eventsourcing.eventstore.EventStore;
296
- import org.axonframework.eventsourcing.eventstore.jdbc.JdbcEventStorageEngine;
297
- import org.axonframework.serialization.Serializer;
298
- import org.axonframework.serialization.json.JacksonSerializer;
299
- import org.springframework.context.annotation.Bean;
300
- import org.springframework.context.annotation.Configuration;
301
-
302
- import javax.sql.DataSource;
303
-
304
- /**
305
- * Axon Framework 設定
306
- */
307
- @Configuration
308
- public class AxonConfig {
309
-
310
- /**
311
- * JDBC ベースの Event Storage Engine
312
- */
313
- @Bean
314
- public EventStorageEngine eventStorageEngine(
315
- DataSource dataSource,
316
- Serializer serializer) {
317
- return JdbcEventStorageEngine.builder()
318
- .snapshotSerializer(serializer)
319
- .eventSerializer(serializer)
320
- .dataSource(dataSource)
321
- .build();
322
- }
323
-
324
- /**
325
- * Event Store
326
- */
327
- @Bean
328
- public EventStore eventStore(EventStorageEngine eventStorageEngine) {
329
- return EmbeddedEventStore.builder()
330
- .storageEngine(eventStorageEngine)
331
- .build();
332
- }
333
-
334
- /**
335
- * JSON シリアライザー
336
- */
337
- @Bean
338
- public Serializer eventSerializer() {
339
- return JacksonSerializer.defaultSerializer();
340
- }
341
- }
342
- ```
343
-
344
- </details>
345
-
346
- ### 26.7 MyBatis 設定クラス
347
-
348
- <details>
349
- <summary>コード例: MyBatisConfig.java</summary>
350
-
351
- ```java
352
- package com.example.accounting.config;
353
-
354
- import org.apache.ibatis.session.SqlSessionFactory;
355
- import org.mybatis.spring.SqlSessionFactoryBean;
356
- import org.mybatis.spring.annotation.MapperScan;
357
- import org.springframework.context.annotation.Bean;
358
- import org.springframework.context.annotation.Configuration;
359
- import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
360
-
361
- import javax.sql.DataSource;
362
-
363
- /**
364
- * MyBatis 設定
365
- */
366
- @Configuration
367
- @MapperScan(basePackages = {
368
- "com.example.accounting.journal.adapter.outbound.persistence.mapper",
369
- "com.example.accounting.account.adapter.outbound.persistence.mapper"
370
- })
371
- public class MyBatisConfig {
372
-
373
- @Bean
374
- public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
375
- SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
376
- factoryBean.setDataSource(dataSource);
377
- factoryBean.setMapperLocations(
378
- new PathMatchingResourcePatternResolver()
379
- .getResources("classpath:mapper/**/*.xml")
380
- );
381
- factoryBean.setTypeAliasesPackage(
382
- "com.example.accounting.journal.adapter.outbound.persistence.entity," +
383
- "com.example.accounting.account.adapter.outbound.persistence.entity"
384
- );
385
-
386
- org.apache.ibatis.session.Configuration configuration =
387
- new org.apache.ibatis.session.Configuration();
388
- configuration.setMapUnderscoreToCamelCase(true);
389
- factoryBean.setConfiguration(configuration);
390
-
391
- return factoryBean.getObject();
392
- }
393
- }
394
- ```
395
-
396
- </details>
397
-
398
- ---
399
-
400
- ## 第27章:ドメインモデルとイベント設計
401
-
402
- ### 27.1 Journal Bounded Context
403
-
404
- 仕訳の作成から転記完了までのライフサイクルを管理します。
405
-
406
- #### 状態遷移図
407
-
408
- ```plantuml
409
- @startuml journal_state
410
- skinparam backgroundColor #FEFEFE
411
-
412
- title 仕訳(Journal Entry)の状態遷移
413
-
414
- [*] --> DRAFT : JournalEntryCreated
415
- DRAFT --> PENDING_APPROVAL : JournalEntrySubmitted
416
- DRAFT --> CANCELLED : JournalEntryCancelled
417
- PENDING_APPROVAL --> APPROVED : JournalEntryApproved
418
- PENDING_APPROVAL --> REJECTED : JournalEntryRejected
419
- PENDING_APPROVAL --> CANCELLED : JournalEntryCancelled
420
- APPROVED --> POSTED : JournalEntryPosted
421
- REJECTED --> DRAFT : JournalEntryRevised
422
-
423
- @enduml
424
- ```
425
-
426
- #### イベント一覧
427
-
428
- | イベント | 説明 |
429
- |---------|------|
430
- | `JournalEntryCreatedEvent` | 仕訳が作成された |
431
- | `JournalEntrySubmittedEvent` | 仕訳が承認申請された |
432
- | `JournalEntryApprovedEvent` | 仕訳が承認された |
433
- | `JournalEntryRejectedEvent` | 仕訳が却下された |
434
- | `JournalEntryRevisedEvent` | 却下された仕訳が修正された |
435
- | `JournalEntryPostedEvent` | 仕訳が転記された(元帳に反映) |
436
- | `JournalEntryCancelledEvent` | 仕訳がキャンセルされた |
437
-
438
- ---
439
-
440
- ### 27.2 イベント定義(API Layer)
441
-
442
- #### sealed interface によるイベントの型安全性
443
-
444
- <details>
445
- <summary>コード例: JournalEntryEvent.java</summary>
446
-
447
- ```java
448
- package com.example.accounting.journal.api.events;
449
-
450
- /**
451
- * JournalEntry イベントの sealed interface
452
- * すべての JournalEntry イベントの親インターフェース
453
- */
454
- public sealed interface JournalEntryEvent permits
455
- JournalEntryCreatedEvent,
456
- JournalEntrySubmittedEvent,
457
- JournalEntryApprovedEvent,
458
- JournalEntryRejectedEvent,
459
- JournalEntryRevisedEvent,
460
- JournalEntryPostedEvent,
461
- JournalEntryCancelledEvent {
462
-
463
- String journalEntryId();
464
- }
465
- ```
466
-
467
- </details>
468
-
469
- **sealed interface の利点:**
470
-
471
- - **網羅性チェック**: switch 式でコンパイル時に全ケースをチェック
472
- - **型安全性**: 許可されたクラスのみが実装可能
473
- - **ドキュメント**: 存在するイベントが一目でわかる
474
-
475
- #### イベント record の実装
476
-
477
- <details>
478
- <summary>コード例: JournalEntryCreatedEvent.java</summary>
479
-
480
- ```java
481
- package com.example.accounting.journal.api.events;
482
-
483
- import java.math.BigDecimal;
484
- import java.time.LocalDate;
485
- import java.time.LocalDateTime;
486
- import java.util.List;
487
-
488
- /**
489
- * 仕訳作成イベント
490
- */
491
- public record JournalEntryCreatedEvent(
492
- String journalEntryId,
493
- String journalNumber,
494
- LocalDate transactionDate,
495
- String description,
496
- List<JournalLineData> lines,
497
- String createdBy,
498
- LocalDateTime createdAt
499
- ) implements JournalEntryEvent {
500
-
501
- /**
502
- * 仕訳明細データ
503
- */
504
- public record JournalLineData(
505
- String accountCode,
506
- String accountName,
507
- BigDecimal debitAmount,
508
- BigDecimal creditAmount
509
- ) {
510
- }
511
- }
512
- ```
513
-
514
- </details>
515
-
516
- <details>
517
- <summary>コード例: JournalEntryApprovedEvent.java</summary>
518
-
519
- ```java
520
- package com.example.accounting.journal.api.events;
521
-
522
- import java.time.LocalDateTime;
523
-
524
- /**
525
- * 仕訳承認イベント
526
- */
527
- public record JournalEntryApprovedEvent(
528
- String journalEntryId,
529
- String approvedBy,
530
- String comment,
531
- LocalDateTime approvedAt
532
- ) implements JournalEntryEvent {
533
- }
534
- ```
535
-
536
- </details>
537
-
538
- <details>
539
- <summary>コード例: JournalEntryPostedEvent.java</summary>
540
-
541
- ```java
542
- package com.example.accounting.journal.api.events;
543
-
544
- import java.time.LocalDateTime;
545
-
546
- /**
547
- * 仕訳転記イベント
548
- */
549
- public record JournalEntryPostedEvent(
550
- String journalEntryId,
551
- String fiscalPeriod,
552
- String postedBy,
553
- LocalDateTime postedAt
554
- ) implements JournalEntryEvent {
555
- }
556
- ```
557
-
558
- </details>
559
-
560
- <details>
561
- <summary>コード例: JournalEntryCancelledEvent.java</summary>
562
-
563
- ```java
564
- package com.example.accounting.journal.api.events;
565
-
566
- import java.time.LocalDateTime;
567
-
568
- /**
569
- * 仕訳キャンセルイベント
570
- */
571
- public record JournalEntryCancelledEvent(
572
- String journalEntryId,
573
- String cancelledBy,
574
- String reason,
575
- LocalDateTime cancelledAt
576
- ) implements JournalEntryEvent {
577
- }
578
- ```
579
-
580
- </details>
581
-
582
- ---
583
-
584
- ### 27.3 コマンド定義(Domain Layer)
585
-
586
- <details>
587
- <summary>コード例: JournalEntryCommands.java</summary>
588
-
589
- ```java
590
- package com.example.accounting.journal.domain.model.aggregate.journal;
591
-
592
- import org.axonframework.modelling.command.TargetAggregateIdentifier;
593
- import java.math.BigDecimal;
594
- import java.time.LocalDate;
595
- import java.util.List;
596
-
597
- /**
598
- * JournalEntry 集約へのコマンド定義
599
- */
600
- public final class JournalEntryCommands {
601
-
602
- private JournalEntryCommands() {
603
- }
604
-
605
- /**
606
- * 仕訳作成コマンド
607
- */
608
- public record CreateJournalEntryCommand(
609
- @TargetAggregateIdentifier
610
- String journalEntryId,
611
- LocalDate transactionDate,
612
- String description,
613
- List<JournalLineInput> lines,
614
- String createdBy
615
- ) {
616
- public record JournalLineInput(
617
- String accountCode,
618
- String accountName,
619
- BigDecimal debitAmount,
620
- BigDecimal creditAmount
621
- ) {
622
- }
623
- }
624
-
625
- /**
626
- * 仕訳承認申請コマンド
627
- */
628
- public record SubmitJournalEntryCommand(
629
- @TargetAggregateIdentifier
630
- String journalEntryId,
631
- String submittedBy
632
- ) {
633
- }
634
-
635
- /**
636
- * 仕訳承認コマンド
637
- */
638
- public record ApproveJournalEntryCommand(
639
- @TargetAggregateIdentifier
640
- String journalEntryId,
641
- String approvedBy,
642
- String comment
643
- ) {
644
- }
645
-
646
- /**
647
- * 仕訳却下コマンド
648
- */
649
- public record RejectJournalEntryCommand(
650
- @TargetAggregateIdentifier
651
- String journalEntryId,
652
- String rejectedBy,
653
- String reason
654
- ) {
655
- }
656
-
657
- /**
658
- * 仕訳修正コマンド
659
- */
660
- public record ReviseJournalEntryCommand(
661
- @TargetAggregateIdentifier
662
- String journalEntryId,
663
- LocalDate transactionDate,
664
- String description,
665
- List<CreateJournalEntryCommand.JournalLineInput> lines,
666
- String revisedBy
667
- ) {
668
- }
669
-
670
- /**
671
- * 仕訳転記コマンド
672
- */
673
- public record PostJournalEntryCommand(
674
- @TargetAggregateIdentifier
675
- String journalEntryId,
676
- String fiscalPeriod,
677
- String postedBy
678
- ) {
679
- }
680
-
681
- /**
682
- * 仕訳キャンセルコマンド
683
- */
684
- public record CancelJournalEntryCommand(
685
- @TargetAggregateIdentifier
686
- String journalEntryId,
687
- String cancelledBy,
688
- String reason
689
- ) {
690
- }
691
- }
692
- ```
693
-
694
- </details>
695
-
696
- **@TargetAggregateIdentifier の役割:**
697
-
698
- - Axon がコマンドをどの集約インスタンスにルーティングするかを決定
699
- - 集約の一意識別子となるフィールドに付与
700
-
701
- ---
702
-
703
- ### 27.4 ドメインモデル(純粋な Java)
704
-
705
- <details>
706
- <summary>コード例: JournalEntry.java</summary>
707
-
708
- ```java
709
- package com.example.accounting.journal.domain.model.aggregate.journal;
710
-
711
- import com.example.accounting.journal.api.events.*;
712
- import com.example.accounting.journal.api.events.JournalEntryCreatedEvent.JournalLineData;
713
- import java.math.BigDecimal;
714
- import java.time.LocalDate;
715
- import java.time.LocalDateTime;
716
- import java.util.List;
717
-
718
- /**
719
- * JournalEntry ドメインモデル(Axon 非依存)
720
- * 純粋なビジネスロジックのみを含む
721
- */
722
- public record JournalEntry(
723
- String journalEntryId,
724
- String journalNumber,
725
- LocalDate transactionDate,
726
- String description,
727
- List<JournalLine> lines,
728
- JournalEntryStatus status
729
- ) {
730
-
731
- // ======== ファクトリメソッド ========
732
-
733
- /**
734
- * 仕訳作成
735
- */
736
- public static JournalEntryCreatedEvent create(
737
- String journalEntryId,
738
- String journalNumber,
739
- LocalDate transactionDate,
740
- String description,
741
- List<JournalLineData> lines,
742
- String createdBy
743
- ) {
744
- // バリデーション: 借方・貸方の合計が一致すること
745
- BigDecimal totalDebit = lines.stream()
746
- .map(JournalLineData::debitAmount)
747
- .reduce(BigDecimal.ZERO, BigDecimal::add);
748
- BigDecimal totalCredit = lines.stream()
749
- .map(JournalLineData::creditAmount)
750
- .reduce(BigDecimal.ZERO, BigDecimal::add);
751
-
752
- if (totalDebit.compareTo(totalCredit) != 0) {
753
- throw new IllegalArgumentException(
754
- "Debit and credit amounts must be equal. Debit: " + totalDebit + ", Credit: " + totalCredit
755
- );
756
- }
757
-
758
- if (lines.isEmpty()) {
759
- throw new IllegalArgumentException("Journal entry must have at least one line");
760
- }
761
-
762
- return new JournalEntryCreatedEvent(
763
- journalEntryId,
764
- journalNumber,
765
- transactionDate,
766
- description,
767
- lines,
768
- createdBy,
769
- LocalDateTime.now()
770
- );
771
- }
772
-
773
- /**
774
- * イベントからの再構築
775
- */
776
- public static JournalEntry from(JournalEntryCreatedEvent event) {
777
- List<JournalLine> lines = event.lines().stream()
778
- .map(data -> new JournalLine(
779
- data.accountCode(),
780
- data.accountName(),
781
- data.debitAmount(),
782
- data.creditAmount()
783
- ))
784
- .toList();
785
-
786
- return new JournalEntry(
787
- event.journalEntryId(),
788
- event.journalNumber(),
789
- event.transactionDate(),
790
- event.description(),
791
- lines,
792
- JournalEntryStatus.DRAFT
793
- );
794
- }
795
-
796
- // ======== 状態遷移メソッド ========
797
-
798
- /**
799
- * 承認申請
800
- */
801
- public JournalEntrySubmittedEvent submit(String submittedBy) {
802
- if (status != JournalEntryStatus.DRAFT) {
803
- throw new IllegalStateException("Only draft journal entries can be submitted");
804
- }
805
- return new JournalEntrySubmittedEvent(journalEntryId, submittedBy, LocalDateTime.now());
806
- }
807
-
808
- /**
809
- * 承認
810
- */
811
- public JournalEntryApprovedEvent approve(String approvedBy, String comment) {
812
- if (status != JournalEntryStatus.PENDING_APPROVAL) {
813
- throw new IllegalStateException("Only pending journal entries can be approved");
814
- }
815
- return new JournalEntryApprovedEvent(journalEntryId, approvedBy, comment, LocalDateTime.now());
816
- }
817
-
818
- /**
819
- * 却下
820
- */
821
- public JournalEntryRejectedEvent reject(String rejectedBy, String reason) {
822
- if (status != JournalEntryStatus.PENDING_APPROVAL) {
823
- throw new IllegalStateException("Only pending journal entries can be rejected");
824
- }
825
- return new JournalEntryRejectedEvent(journalEntryId, rejectedBy, reason, LocalDateTime.now());
826
- }
827
-
828
- /**
829
- * 修正
830
- */
831
- public JournalEntryRevisedEvent revise(
832
- LocalDate transactionDate,
833
- String description,
834
- List<JournalLineData> lines,
835
- String revisedBy
836
- ) {
837
- if (status != JournalEntryStatus.REJECTED) {
838
- throw new IllegalStateException("Only rejected journal entries can be revised");
839
- }
840
-
841
- // バリデーション
842
- BigDecimal totalDebit = lines.stream()
843
- .map(JournalLineData::debitAmount)
844
- .reduce(BigDecimal.ZERO, BigDecimal::add);
845
- BigDecimal totalCredit = lines.stream()
846
- .map(JournalLineData::creditAmount)
847
- .reduce(BigDecimal.ZERO, BigDecimal::add);
848
-
849
- if (totalDebit.compareTo(totalCredit) != 0) {
850
- throw new IllegalArgumentException("Debit and credit amounts must be equal");
851
- }
852
-
853
- return new JournalEntryRevisedEvent(
854
- journalEntryId,
855
- transactionDate,
856
- description,
857
- lines,
858
- revisedBy,
859
- LocalDateTime.now()
860
- );
861
- }
862
-
863
- /**
864
- * 転記
865
- */
866
- public JournalEntryPostedEvent post(String fiscalPeriod, String postedBy) {
867
- if (status != JournalEntryStatus.APPROVED) {
868
- throw new IllegalStateException("Only approved journal entries can be posted");
869
- }
870
- return new JournalEntryPostedEvent(journalEntryId, fiscalPeriod, postedBy, LocalDateTime.now());
871
- }
872
-
873
- /**
874
- * キャンセル
875
- */
876
- public JournalEntryCancelledEvent cancel(String cancelledBy, String reason) {
877
- if (status == JournalEntryStatus.POSTED) {
878
- throw new IllegalStateException("Posted journal entries cannot be cancelled");
879
- }
880
- if (status == JournalEntryStatus.CANCELLED) {
881
- throw new IllegalStateException("Journal entry is already cancelled");
882
- }
883
- return new JournalEntryCancelledEvent(journalEntryId, cancelledBy, reason, LocalDateTime.now());
884
- }
885
-
886
- // ======== イベント適用メソッド ========
887
-
888
- /**
889
- * イベントを適用して新しい状態を生成
890
- */
891
- public JournalEntry apply(JournalEntryEvent event) {
892
- return switch (event) {
893
- case JournalEntryCreatedEvent e -> from(e);
894
- case JournalEntrySubmittedEvent e -> withStatus(JournalEntryStatus.PENDING_APPROVAL);
895
- case JournalEntryApprovedEvent e -> withStatus(JournalEntryStatus.APPROVED);
896
- case JournalEntryRejectedEvent e -> withStatus(JournalEntryStatus.REJECTED);
897
- case JournalEntryRevisedEvent e -> revised(e);
898
- case JournalEntryPostedEvent e -> withStatus(JournalEntryStatus.POSTED);
899
- case JournalEntryCancelledEvent e -> withStatus(JournalEntryStatus.CANCELLED);
900
- };
901
- }
902
-
903
- /**
904
- * 状態更新ヘルパー
905
- */
906
- private JournalEntry withStatus(JournalEntryStatus newStatus) {
907
- return new JournalEntry(journalEntryId, journalNumber, transactionDate, description, lines, newStatus);
908
- }
909
-
910
- /**
911
- * 修正適用
912
- */
913
- private JournalEntry revised(JournalEntryRevisedEvent event) {
914
- List<JournalLine> newLines = event.lines().stream()
915
- .map(data -> new JournalLine(
916
- data.accountCode(),
917
- data.accountName(),
918
- data.debitAmount(),
919
- data.creditAmount()
920
- ))
921
- .toList();
922
-
923
- return new JournalEntry(
924
- journalEntryId,
925
- journalNumber,
926
- event.transactionDate(),
927
- event.description(),
928
- newLines,
929
- JournalEntryStatus.DRAFT
930
- );
931
- }
932
- }
933
- ```
934
-
935
- </details>
936
-
937
- <details>
938
- <summary>コード例: JournalEntryStatus.java</summary>
939
-
940
- ```java
941
- package com.example.accounting.journal.domain.model.aggregate.journal;
942
-
943
- /**
944
- * 仕訳ステータス
945
- */
946
- public enum JournalEntryStatus {
947
- DRAFT, // 下書き
948
- PENDING_APPROVAL, // 承認待ち
949
- APPROVED, // 承認済み
950
- REJECTED, // 却下
951
- POSTED, // 転記済み
952
- CANCELLED // キャンセル
953
- }
954
- ```
955
-
956
- </details>
957
-
958
- <details>
959
- <summary>コード例: JournalLine.java</summary>
960
-
961
- ```java
962
- package com.example.accounting.journal.domain.model.aggregate.journal;
963
-
964
- import java.math.BigDecimal;
965
-
966
- /**
967
- * 仕訳明細
968
- */
969
- public record JournalLine(
970
- String accountCode,
971
- String accountName,
972
- BigDecimal debitAmount,
973
- BigDecimal creditAmount
974
- ) {
975
- public JournalLine {
976
- if (debitAmount == null) debitAmount = BigDecimal.ZERO;
977
- if (creditAmount == null) creditAmount = BigDecimal.ZERO;
978
-
979
- // 借方と貸方は排他的
980
- if (debitAmount.compareTo(BigDecimal.ZERO) > 0 &&
981
- creditAmount.compareTo(BigDecimal.ZERO) > 0) {
982
- throw new IllegalArgumentException(
983
- "A journal line cannot have both debit and credit amounts"
984
- );
985
- }
986
- }
987
- }
988
- ```
989
-
990
- </details>
991
-
992
- **ドメインモデル設計原則:**
993
-
994
- | 原則 | 説明 |
995
- |------|------|
996
- | **Axon 非依存** | ドメインモデルにフレームワーク依存を持たせない |
997
- | **イミュータブル** | record でイミュータブルに設計 |
998
- | **イベントを返す** | 状態遷移メソッドはイベントを返す |
999
- | **最小限のフィールド** | 状態遷移の判定に必要な最小限のみ保持 |
1000
- | **ビジネスルール** | 借方・貸方の一致検証をドメインモデル内で実施 |
1001
-
1002
- ---
1003
-
1004
- ### 27.5 Account Bounded Context
1005
-
1006
- 勘定科目の残高管理を行います。
1007
-
1008
- #### イベント定義
1009
-
1010
- <details>
1011
- <summary>コード例: AccountEvent.java</summary>
1012
-
1013
- ```java
1014
- package com.example.accounting.account.api.events;
1015
-
1016
- /**
1017
- * Account イベントの sealed interface
1018
- */
1019
- public sealed interface AccountEvent permits
1020
- AccountCreatedEvent,
1021
- AccountBalanceUpdatedEvent,
1022
- AccountClosedEvent {
1023
-
1024
- String accountCode();
1025
- }
1026
- ```
1027
-
1028
- </details>
1029
-
1030
- <details>
1031
- <summary>コード例: AccountBalanceUpdatedEvent.java</summary>
1032
-
1033
- ```java
1034
- package com.example.accounting.account.api.events;
1035
-
1036
- import java.math.BigDecimal;
1037
- import java.time.LocalDateTime;
1038
-
1039
- /**
1040
- * 勘定残高更新イベント
1041
- */
1042
- public record AccountBalanceUpdatedEvent(
1043
- String accountCode,
1044
- String journalEntryId,
1045
- BigDecimal debitAmount,
1046
- BigDecimal creditAmount,
1047
- BigDecimal balanceAfter,
1048
- LocalDateTime updatedAt
1049
- ) implements AccountEvent {
1050
- }
1051
- ```
1052
-
1053
- </details>
1054
-
1055
- ---
1056
-
1057
- ## 第28章:Aggregate Adapter と Policy Handler
1058
-
1059
- ### 28.1 Aggregate Adapter パターン
1060
-
1061
- Aggregate Adapter は、純粋なドメインモデルと Axon Framework を繋ぐアダプター層です。フレームワーク固有のアノテーションやライフサイクル処理をドメインモデルから分離します。
1062
-
1063
- ```plantuml
1064
- @startuml aggregate_adapter
1065
- skinparam backgroundColor #FEFEFE
1066
-
1067
- package "Application Layer" {
1068
- class JournalEntryAggregateAdapter << @Aggregate >> {
1069
- - journalEntryId: String
1070
- - journalEntry: JournalEntry
1071
- + @CommandHandler: handle(CreateJournalEntryCommand)
1072
- + @CommandHandler: handle(ApproveJournalEntryCommand)
1073
- + @EventSourcingHandler: on(JournalEntryCreatedEvent)
1074
- + @EventSourcingHandler: on(JournalEntryApprovedEvent)
1075
- }
1076
- }
1077
-
1078
- package "Domain Layer" {
1079
- class JournalEntry << record >> {
1080
- + create(): JournalEntryCreatedEvent
1081
- + approve(): JournalEntryApprovedEvent
1082
- + apply(event): JournalEntry
1083
- }
1084
- }
1085
-
1086
- JournalEntryAggregateAdapter --> JournalEntry : "委譲"
1087
-
1088
- note bottom of JournalEntryAggregateAdapter
1089
- Axon Framework 用アダプター
1090
- フレームワーク依存を吸収
1091
- end note
1092
-
1093
- note bottom of JournalEntry
1094
- 純粋なドメインモデル
1095
- Axon に依存しない
1096
- end note
1097
-
1098
- @enduml
1099
- ```
1100
-
1101
- ---
1102
-
1103
- ### 28.2 JournalEntry Aggregate Adapter
1104
-
1105
- <details>
1106
- <summary>コード例: JournalEntryAggregateAdapter.java</summary>
1107
-
1108
- ```java
1109
- package com.example.accounting.journal.application.aggregate;
1110
-
1111
- import com.example.accounting.journal.api.events.*;
1112
- import com.example.accounting.journal.api.events.JournalEntryCreatedEvent.JournalLineData;
1113
- import com.example.accounting.journal.domain.model.aggregate.journal.JournalEntry;
1114
- import com.example.accounting.journal.domain.model.aggregate.journal.JournalEntryCommands.*;
1115
- import org.axonframework.commandhandling.CommandHandler;
1116
- import org.axonframework.eventsourcing.EventSourcingHandler;
1117
- import org.axonframework.modelling.command.AggregateIdentifier;
1118
- import org.axonframework.modelling.command.AggregateLifecycle;
1119
- import org.axonframework.spring.stereotype.Aggregate;
1120
-
1121
- import java.util.UUID;
1122
-
1123
- /**
1124
- * JournalEntry Aggregate Adapter(Axon Framework 用)
1125
- */
1126
- @Aggregate
1127
- public class JournalEntryAggregateAdapter {
1128
-
1129
- @AggregateIdentifier
1130
- private String journalEntryId;
1131
-
1132
- private JournalEntry journalEntry;
1133
-
1134
- /**
1135
- * Axon が使用するデフォルトコンストラクタ
1136
- */
1137
- protected JournalEntryAggregateAdapter() {
1138
- }
1139
-
1140
- // ======== Command Handlers ========
1141
-
1142
- /**
1143
- * 集約作成コマンドハンドラー(コンストラクタ)
1144
- */
1145
- @CommandHandler
1146
- public JournalEntryAggregateAdapter(CreateJournalEntryCommand command) {
1147
- // 仕訳番号を生成
1148
- String journalNumber = generateJournalNumber();
1149
-
1150
- // 明細データを変換
1151
- var lines = command.lines().stream()
1152
- .map(input -> new JournalLineData(
1153
- input.accountCode(),
1154
- input.accountName(),
1155
- input.debitAmount(),
1156
- input.creditAmount()
1157
- ))
1158
- .toList();
1159
-
1160
- // ドメインモデルのファクトリメソッドを呼び出し
1161
- var event = JournalEntry.create(
1162
- command.journalEntryId(),
1163
- journalNumber,
1164
- command.transactionDate(),
1165
- command.description(),
1166
- lines,
1167
- command.createdBy()
1168
- );
1169
- // イベントを発行
1170
- AggregateLifecycle.apply(event);
1171
- }
1172
-
1173
- /**
1174
- * 承認申請コマンドハンドラー
1175
- */
1176
- @CommandHandler
1177
- public void handle(SubmitJournalEntryCommand command) {
1178
- var event = journalEntry.submit(command.submittedBy());
1179
- AggregateLifecycle.apply(event);
1180
- }
1181
-
1182
- /**
1183
- * 承認コマンドハンドラー
1184
- */
1185
- @CommandHandler
1186
- public void handle(ApproveJournalEntryCommand command) {
1187
- var event = journalEntry.approve(command.approvedBy(), command.comment());
1188
- AggregateLifecycle.apply(event);
1189
- }
1190
-
1191
- /**
1192
- * 却下コマンドハンドラー
1193
- */
1194
- @CommandHandler
1195
- public void handle(RejectJournalEntryCommand command) {
1196
- var event = journalEntry.reject(command.rejectedBy(), command.reason());
1197
- AggregateLifecycle.apply(event);
1198
- }
1199
-
1200
- /**
1201
- * 修正コマンドハンドラー
1202
- */
1203
- @CommandHandler
1204
- public void handle(ReviseJournalEntryCommand command) {
1205
- var lines = command.lines().stream()
1206
- .map(input -> new JournalLineData(
1207
- input.accountCode(),
1208
- input.accountName(),
1209
- input.debitAmount(),
1210
- input.creditAmount()
1211
- ))
1212
- .toList();
1213
-
1214
- var event = journalEntry.revise(
1215
- command.transactionDate(),
1216
- command.description(),
1217
- lines,
1218
- command.revisedBy()
1219
- );
1220
- AggregateLifecycle.apply(event);
1221
- }
1222
-
1223
- /**
1224
- * 転記コマンドハンドラー
1225
- */
1226
- @CommandHandler
1227
- public void handle(PostJournalEntryCommand command) {
1228
- var event = journalEntry.post(command.fiscalPeriod(), command.postedBy());
1229
- AggregateLifecycle.apply(event);
1230
- }
1231
-
1232
- /**
1233
- * キャンセルコマンドハンドラー
1234
- */
1235
- @CommandHandler
1236
- public void handle(CancelJournalEntryCommand command) {
1237
- var event = journalEntry.cancel(command.cancelledBy(), command.reason());
1238
- AggregateLifecycle.apply(event);
1239
- }
1240
-
1241
- // ======== Event Sourcing Handlers(状態復元)========
1242
-
1243
- @EventSourcingHandler
1244
- public void on(JournalEntryCreatedEvent event) {
1245
- this.journalEntryId = event.journalEntryId();
1246
- this.journalEntry = JournalEntry.from(event);
1247
- }
1248
-
1249
- @EventSourcingHandler
1250
- public void on(JournalEntrySubmittedEvent event) {
1251
- this.journalEntry = journalEntry.apply(event);
1252
- }
1253
-
1254
- @EventSourcingHandler
1255
- public void on(JournalEntryApprovedEvent event) {
1256
- this.journalEntry = journalEntry.apply(event);
1257
- }
1258
-
1259
- @EventSourcingHandler
1260
- public void on(JournalEntryRejectedEvent event) {
1261
- this.journalEntry = journalEntry.apply(event);
1262
- }
1263
-
1264
- @EventSourcingHandler
1265
- public void on(JournalEntryRevisedEvent event) {
1266
- this.journalEntry = journalEntry.apply(event);
1267
- }
1268
-
1269
- @EventSourcingHandler
1270
- public void on(JournalEntryPostedEvent event) {
1271
- this.journalEntry = journalEntry.apply(event);
1272
- }
1273
-
1274
- @EventSourcingHandler
1275
- public void on(JournalEntryCancelledEvent event) {
1276
- this.journalEntry = journalEntry.apply(event);
1277
- }
1278
-
1279
- // ======== ヘルパーメソッド ========
1280
-
1281
- private String generateJournalNumber() {
1282
- // 実際のシステムでは年月日 + 連番など
1283
- return "JE-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
1284
- }
1285
- }
1286
- ```
1287
-
1288
- </details>
1289
-
1290
- **Axon アノテーション解説:**
1291
-
1292
- | アノテーション | 説明 |
1293
- |--------------|------|
1294
- | `@Aggregate` | Event Sourcing 集約であることを宣言 |
1295
- | `@AggregateIdentifier` | 集約の識別子フィールド |
1296
- | `@CommandHandler` | コマンドを処理するメソッド |
1297
- | `@EventSourcingHandler` | イベントから状態を復元するメソッド |
1298
- | `AggregateLifecycle.apply()` | イベントを発行するメソッド |
1299
-
1300
- ---
1301
-
1302
- ### 28.3 Policy Handler(Choreography パターン)
1303
-
1304
- Policy Handler は、他の Bounded Context から発行されたイベントを購読し、自律的に反応する処理を実装します。Choreography パターンでは、各 Context が独立して動作し、イベントを介して連携します。
1305
-
1306
- ```plantuml
1307
- @startuml choreography
1308
- skinparam backgroundColor #FEFEFE
1309
-
1310
- participant "Journal Context" as Journal
1311
- participant "Event Bus" as Bus
1312
- participant "Account Context" as Account
1313
-
1314
- == 仕訳転記フロー ==
1315
- Journal -> Bus : JournalEntryPostedEvent
1316
- Bus -> Account : JournalEntryPostedEvent
1317
- Account -> Account : updateBalance()
1318
- Account -> Bus : AccountBalanceUpdatedEvent
1319
-
1320
- @enduml
1321
- ```
1322
-
1323
- ---
1324
-
1325
- ### 28.4 JournalEventHandler(Account Context 内)
1326
-
1327
- <details>
1328
- <summary>コード例: JournalEventHandler.java</summary>
1329
-
1330
- ```java
1331
- package com.example.accounting.account.application.policy;
1332
-
1333
- import com.example.accounting.account.domain.model.aggregate.account.AccountCommands.*;
1334
- import com.example.accounting.journal.api.events.JournalEntryPostedEvent;
1335
- import org.axonframework.commandhandling.gateway.CommandGateway;
1336
- import org.axonframework.eventhandling.EventHandler;
1337
- import org.springframework.stereotype.Component;
1338
-
1339
- /**
1340
- * Journal イベントに反応して勘定残高を更新する Policy Handler
1341
- */
1342
- @Component
1343
- public class JournalEventHandler {
1344
-
1345
- private final CommandGateway commandGateway;
1346
-
1347
- public JournalEventHandler(CommandGateway commandGateway) {
1348
- this.commandGateway = commandGateway;
1349
- }
1350
-
1351
- /**
1352
- * Policy: "When a journal entry is posted, update account balances"
1353
- */
1354
- @EventHandler
1355
- public void on(JournalEntryPostedEvent event) {
1356
- // 仕訳の各明細に対して勘定残高を更新
1357
- // 実際の実装では、イベントから明細情報を取得して処理
1358
- var command = new UpdateAccountBalanceCommand(
1359
- event.journalEntryId(),
1360
- event.fiscalPeriod()
1361
- );
1362
- commandGateway.send(command);
1363
- }
1364
- }
1365
- ```
1366
-
1367
- </details>
1368
-
1369
- **Policy Handler の設計原則:**
1370
-
1371
- | 原則 | 説明 |
1372
- |------|------|
1373
- | **単一責任** | 1 つの Policy Handler は 1 つの関心事のみを扱う |
1374
- | **疎結合** | 他の Context のイベントを購読し、自 Context のコマンドを発行 |
1375
- | **自律性** | 他のサービスに依存せず独立して動作 |
1376
- | **冪等性** | 同じイベントを複数回受信しても問題ないよう設計 |
1377
-
1378
- ---
1379
-
1380
- ## 第29章:Projection と Read Model
1381
-
1382
- ### 29.1 Projection の役割
1383
-
1384
- Projection は、イベントを購読して Read Model(クエリ用のデータモデル)を更新するコンポーネントです。CQRS では、書き込みモデル(Event Store)と読み取りモデル(Read Model DB)を分離し、それぞれに最適化されたデータ構造を使用します。
1385
-
1386
- ```plantuml
1387
- @startuml projection
1388
- skinparam backgroundColor #FEFEFE
1389
-
1390
- participant "CommandHandler" as CH
1391
- participant "Event Store" as ES
1392
- participant "Projection" as P
1393
- participant "MyBatis Mapper" as MB
1394
- participant "Read Model DB" as DB
1395
- participant "REST Controller" as RC
1396
-
1397
- == 書き込み(Command Side)==
1398
- CH -> ES : イベント保存
1399
- ES -> P : イベント通知
1400
-
1401
- == 読み取りモデル更新 ==
1402
- P -> MB : insert/update
1403
- MB -> DB : SQL 実行
1404
-
1405
- == 読み取り(Query Side)==
1406
- RC -> MB : findById
1407
- MB -> DB : SELECT
1408
- DB --> MB : 結果
1409
- MB --> RC : Entity
1410
-
1411
- note right of P
1412
- Projection は
1413
- イベントを購読して
1414
- MyBatis 経由で
1415
- Read Model を更新
1416
- end note
1417
-
1418
- @enduml
1419
- ```
1420
-
1421
- ---
1422
-
1423
- ### 29.2 JournalEntry Projection(MyBatis 版)
1424
-
1425
- <details>
1426
- <summary>コード例: JournalEntryProjection.java</summary>
1427
-
1428
- ```java
1429
- package com.example.accounting.journal.application.query;
1430
-
1431
- import com.example.accounting.journal.adapter.outbound.persistence.entity.JournalEntryEntity;
1432
- import com.example.accounting.journal.adapter.outbound.persistence.entity.JournalLineEntity;
1433
- import com.example.accounting.journal.adapter.outbound.persistence.mapper.JournalEntryMapper;
1434
- import com.example.accounting.journal.api.events.*;
1435
- import org.axonframework.eventhandling.EventHandler;
1436
- import org.springframework.stereotype.Component;
1437
-
1438
- import java.util.List;
1439
-
1440
- /**
1441
- * JournalEntry Projection(MyBatis を使用した Read Model の更新)
1442
- */
1443
- @Component
1444
- public class JournalEntryProjection {
1445
-
1446
- private final JournalEntryMapper journalEntryMapper;
1447
-
1448
- public JournalEntryProjection(JournalEntryMapper journalEntryMapper) {
1449
- this.journalEntryMapper = journalEntryMapper;
1450
- }
1451
-
1452
- @EventHandler
1453
- public void on(JournalEntryCreatedEvent event) {
1454
- var entity = new JournalEntryEntity();
1455
- entity.setJournalEntryId(event.journalEntryId());
1456
- entity.setJournalNumber(event.journalNumber());
1457
- entity.setTransactionDate(event.transactionDate());
1458
- entity.setDescription(event.description());
1459
- entity.setStatus("DRAFT");
1460
- entity.setCreatedBy(event.createdBy());
1461
- entity.setCreatedAt(event.createdAt());
1462
-
1463
- journalEntryMapper.insert(entity);
1464
-
1465
- // 明細を保存
1466
- List<JournalLineEntity> lines = event.lines().stream()
1467
- .map(data -> {
1468
- var line = new JournalLineEntity();
1469
- line.setJournalEntryId(event.journalEntryId());
1470
- line.setAccountCode(data.accountCode());
1471
- line.setAccountName(data.accountName());
1472
- line.setDebitAmount(data.debitAmount());
1473
- line.setCreditAmount(data.creditAmount());
1474
- return line;
1475
- })
1476
- .toList();
1477
-
1478
- journalEntryMapper.insertLines(lines);
1479
- }
1480
-
1481
- @EventHandler
1482
- public void on(JournalEntrySubmittedEvent event) {
1483
- journalEntryMapper.updateStatus(event.journalEntryId(), "PENDING_APPROVAL");
1484
- journalEntryMapper.updateSubmission(event.journalEntryId(), event.submittedBy(), event.submittedAt());
1485
- }
1486
-
1487
- @EventHandler
1488
- public void on(JournalEntryApprovedEvent event) {
1489
- journalEntryMapper.updateStatus(event.journalEntryId(), "APPROVED");
1490
- journalEntryMapper.updateApproval(
1491
- event.journalEntryId(),
1492
- event.approvedBy(),
1493
- event.comment(),
1494
- event.approvedAt()
1495
- );
1496
- }
1497
-
1498
- @EventHandler
1499
- public void on(JournalEntryRejectedEvent event) {
1500
- journalEntryMapper.updateStatus(event.journalEntryId(), "REJECTED");
1501
- journalEntryMapper.updateRejection(
1502
- event.journalEntryId(),
1503
- event.rejectedBy(),
1504
- event.reason(),
1505
- event.rejectedAt()
1506
- );
1507
- }
1508
-
1509
- @EventHandler
1510
- public void on(JournalEntryRevisedEvent event) {
1511
- journalEntryMapper.updateStatus(event.journalEntryId(), "DRAFT");
1512
- journalEntryMapper.updateRevision(
1513
- event.journalEntryId(),
1514
- event.transactionDate(),
1515
- event.description(),
1516
- event.revisedBy(),
1517
- event.revisedAt()
1518
- );
1519
-
1520
- // 明細を削除して再作成
1521
- journalEntryMapper.deleteLines(event.journalEntryId());
1522
- List<JournalLineEntity> lines = event.lines().stream()
1523
- .map(data -> {
1524
- var line = new JournalLineEntity();
1525
- line.setJournalEntryId(event.journalEntryId());
1526
- line.setAccountCode(data.accountCode());
1527
- line.setAccountName(data.accountName());
1528
- line.setDebitAmount(data.debitAmount());
1529
- line.setCreditAmount(data.creditAmount());
1530
- return line;
1531
- })
1532
- .toList();
1533
- journalEntryMapper.insertLines(lines);
1534
- }
1535
-
1536
- @EventHandler
1537
- public void on(JournalEntryPostedEvent event) {
1538
- journalEntryMapper.updateStatus(event.journalEntryId(), "POSTED");
1539
- journalEntryMapper.updatePosting(
1540
- event.journalEntryId(),
1541
- event.fiscalPeriod(),
1542
- event.postedBy(),
1543
- event.postedAt()
1544
- );
1545
- }
1546
-
1547
- @EventHandler
1548
- public void on(JournalEntryCancelledEvent event) {
1549
- journalEntryMapper.updateStatus(event.journalEntryId(), "CANCELLED");
1550
- journalEntryMapper.updateCancellation(
1551
- event.journalEntryId(),
1552
- event.cancelledBy(),
1553
- event.reason(),
1554
- event.cancelledAt()
1555
- );
1556
- }
1557
- }
1558
- ```
1559
-
1560
- </details>
1561
-
1562
- ---
1563
-
1564
- ### 29.3 Read Model Entity
1565
-
1566
- <details>
1567
- <summary>コード例: JournalEntryEntity.java</summary>
1568
-
1569
- ```java
1570
- package com.example.accounting.journal.adapter.outbound.persistence.entity;
1571
-
1572
- import java.math.BigDecimal;
1573
- import java.time.LocalDate;
1574
- import java.time.LocalDateTime;
1575
- import java.util.List;
1576
-
1577
- /**
1578
- * JournalEntry Read Model(表示用のすべてのフィールドを保持)
1579
- */
1580
- public class JournalEntryEntity {
1581
-
1582
- private String journalEntryId;
1583
- private String journalNumber;
1584
- private LocalDate transactionDate;
1585
- private String description;
1586
- private String status;
1587
-
1588
- // 合計金額(集計用)
1589
- private BigDecimal totalDebitAmount;
1590
- private BigDecimal totalCreditAmount;
1591
-
1592
- // 監査情報
1593
- private String createdBy;
1594
- private LocalDateTime createdAt;
1595
- private String submittedBy;
1596
- private LocalDateTime submittedAt;
1597
- private String approvedBy;
1598
- private String approvalComment;
1599
- private LocalDateTime approvedAt;
1600
- private String rejectedBy;
1601
- private String rejectionReason;
1602
- private LocalDateTime rejectedAt;
1603
- private String revisedBy;
1604
- private LocalDateTime revisedAt;
1605
- private String fiscalPeriod;
1606
- private String postedBy;
1607
- private LocalDateTime postedAt;
1608
- private String cancelledBy;
1609
- private String cancellationReason;
1610
- private LocalDateTime cancelledAt;
1611
-
1612
- // 明細
1613
- private List<JournalLineEntity> lines;
1614
-
1615
- // Getters and Setters
1616
- public String getJournalEntryId() { return journalEntryId; }
1617
- public void setJournalEntryId(String journalEntryId) { this.journalEntryId = journalEntryId; }
1618
-
1619
- public String getJournalNumber() { return journalNumber; }
1620
- public void setJournalNumber(String journalNumber) { this.journalNumber = journalNumber; }
1621
-
1622
- public LocalDate getTransactionDate() { return transactionDate; }
1623
- public void setTransactionDate(LocalDate transactionDate) { this.transactionDate = transactionDate; }
1624
-
1625
- public String getDescription() { return description; }
1626
- public void setDescription(String description) { this.description = description; }
1627
-
1628
- public String getStatus() { return status; }
1629
- public void setStatus(String status) { this.status = status; }
1630
-
1631
- public BigDecimal getTotalDebitAmount() { return totalDebitAmount; }
1632
- public void setTotalDebitAmount(BigDecimal totalDebitAmount) { this.totalDebitAmount = totalDebitAmount; }
1633
-
1634
- public BigDecimal getTotalCreditAmount() { return totalCreditAmount; }
1635
- public void setTotalCreditAmount(BigDecimal totalCreditAmount) { this.totalCreditAmount = totalCreditAmount; }
1636
-
1637
- public String getCreatedBy() { return createdBy; }
1638
- public void setCreatedBy(String createdBy) { this.createdBy = createdBy; }
1639
-
1640
- public LocalDateTime getCreatedAt() { return createdAt; }
1641
- public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
1642
-
1643
- public String getSubmittedBy() { return submittedBy; }
1644
- public void setSubmittedBy(String submittedBy) { this.submittedBy = submittedBy; }
1645
-
1646
- public LocalDateTime getSubmittedAt() { return submittedAt; }
1647
- public void setSubmittedAt(LocalDateTime submittedAt) { this.submittedAt = submittedAt; }
1648
-
1649
- public String getApprovedBy() { return approvedBy; }
1650
- public void setApprovedBy(String approvedBy) { this.approvedBy = approvedBy; }
1651
-
1652
- public String getApprovalComment() { return approvalComment; }
1653
- public void setApprovalComment(String approvalComment) { this.approvalComment = approvalComment; }
1654
-
1655
- public LocalDateTime getApprovedAt() { return approvedAt; }
1656
- public void setApprovedAt(LocalDateTime approvedAt) { this.approvedAt = approvedAt; }
1657
-
1658
- public String getRejectedBy() { return rejectedBy; }
1659
- public void setRejectedBy(String rejectedBy) { this.rejectedBy = rejectedBy; }
1660
-
1661
- public String getRejectionReason() { return rejectionReason; }
1662
- public void setRejectionReason(String rejectionReason) { this.rejectionReason = rejectionReason; }
1663
-
1664
- public LocalDateTime getRejectedAt() { return rejectedAt; }
1665
- public void setRejectedAt(LocalDateTime rejectedAt) { this.rejectedAt = rejectedAt; }
1666
-
1667
- public String getRevisedBy() { return revisedBy; }
1668
- public void setRevisedBy(String revisedBy) { this.revisedBy = revisedBy; }
1669
-
1670
- public LocalDateTime getRevisedAt() { return revisedAt; }
1671
- public void setRevisedAt(LocalDateTime revisedAt) { this.revisedAt = revisedAt; }
1672
-
1673
- public String getFiscalPeriod() { return fiscalPeriod; }
1674
- public void setFiscalPeriod(String fiscalPeriod) { this.fiscalPeriod = fiscalPeriod; }
1675
-
1676
- public String getPostedBy() { return postedBy; }
1677
- public void setPostedBy(String postedBy) { this.postedBy = postedBy; }
1678
-
1679
- public LocalDateTime getPostedAt() { return postedAt; }
1680
- public void setPostedAt(LocalDateTime postedAt) { this.postedAt = postedAt; }
1681
-
1682
- public String getCancelledBy() { return cancelledBy; }
1683
- public void setCancelledBy(String cancelledBy) { this.cancelledBy = cancelledBy; }
1684
-
1685
- public String getCancellationReason() { return cancellationReason; }
1686
- public void setCancellationReason(String cancellationReason) { this.cancellationReason = cancellationReason; }
1687
-
1688
- public LocalDateTime getCancelledAt() { return cancelledAt; }
1689
- public void setCancelledAt(LocalDateTime cancelledAt) { this.cancelledAt = cancelledAt; }
1690
-
1691
- public List<JournalLineEntity> getLines() { return lines; }
1692
- public void setLines(List<JournalLineEntity> lines) { this.lines = lines; }
1693
- }
1694
- ```
1695
-
1696
- </details>
1697
-
1698
- <details>
1699
- <summary>コード例: JournalLineEntity.java</summary>
1700
-
1701
- ```java
1702
- package com.example.accounting.journal.adapter.outbound.persistence.entity;
1703
-
1704
- import java.math.BigDecimal;
1705
-
1706
- /**
1707
- * JournalLine Read Model
1708
- */
1709
- public class JournalLineEntity {
1710
-
1711
- private Long lineId;
1712
- private String journalEntryId;
1713
- private String accountCode;
1714
- private String accountName;
1715
- private BigDecimal debitAmount;
1716
- private BigDecimal creditAmount;
1717
-
1718
- // Getters and Setters
1719
- public Long getLineId() { return lineId; }
1720
- public void setLineId(Long lineId) { this.lineId = lineId; }
1721
-
1722
- public String getJournalEntryId() { return journalEntryId; }
1723
- public void setJournalEntryId(String journalEntryId) { this.journalEntryId = journalEntryId; }
1724
-
1725
- public String getAccountCode() { return accountCode; }
1726
- public void setAccountCode(String accountCode) { this.accountCode = accountCode; }
1727
-
1728
- public String getAccountName() { return accountName; }
1729
- public void setAccountName(String accountName) { this.accountName = accountName; }
1730
-
1731
- public BigDecimal getDebitAmount() { return debitAmount; }
1732
- public void setDebitAmount(BigDecimal debitAmount) { this.debitAmount = debitAmount; }
1733
-
1734
- public BigDecimal getCreditAmount() { return creditAmount; }
1735
- public void setCreditAmount(BigDecimal creditAmount) { this.creditAmount = creditAmount; }
1736
- }
1737
- ```
1738
-
1739
- </details>
1740
-
1741
- ---
1742
-
1743
- ### 29.4 MyBatis Mapper インターフェース
1744
-
1745
- <details>
1746
- <summary>コード例: JournalEntryMapper.java</summary>
1747
-
1748
- ```java
1749
- package com.example.accounting.journal.adapter.outbound.persistence.mapper;
1750
-
1751
- import com.example.accounting.journal.adapter.outbound.persistence.entity.JournalEntryEntity;
1752
- import com.example.accounting.journal.adapter.outbound.persistence.entity.JournalLineEntity;
1753
- import org.apache.ibatis.annotations.Mapper;
1754
- import org.apache.ibatis.annotations.Param;
1755
-
1756
- import java.time.LocalDate;
1757
- import java.time.LocalDateTime;
1758
- import java.util.List;
1759
- import java.util.Optional;
1760
-
1761
- /**
1762
- * JournalEntry MyBatis Mapper
1763
- */
1764
- @Mapper
1765
- public interface JournalEntryMapper {
1766
-
1767
- // 挿入
1768
- void insert(JournalEntryEntity entity);
1769
-
1770
- void insertLines(List<JournalLineEntity> lines);
1771
-
1772
- // 検索
1773
- Optional<JournalEntryEntity> findById(@Param("journalEntryId") String journalEntryId);
1774
-
1775
- List<JournalEntryEntity> findAll();
1776
-
1777
- List<JournalEntryEntity> findByStatus(@Param("status") String status);
1778
-
1779
- List<JournalEntryEntity> findByFiscalPeriod(@Param("fiscalPeriod") String fiscalPeriod);
1780
-
1781
- List<JournalEntryEntity> findByTransactionDateRange(
1782
- @Param("fromDate") LocalDate fromDate,
1783
- @Param("toDate") LocalDate toDate
1784
- );
1785
-
1786
- List<JournalLineEntity> findLinesByJournalEntryId(@Param("journalEntryId") String journalEntryId);
1787
-
1788
- // 更新
1789
- void updateStatus(@Param("journalEntryId") String journalEntryId, @Param("status") String status);
1790
-
1791
- void updateSubmission(
1792
- @Param("journalEntryId") String journalEntryId,
1793
- @Param("submittedBy") String submittedBy,
1794
- @Param("submittedAt") LocalDateTime submittedAt
1795
- );
1796
-
1797
- void updateApproval(
1798
- @Param("journalEntryId") String journalEntryId,
1799
- @Param("approvedBy") String approvedBy,
1800
- @Param("comment") String comment,
1801
- @Param("approvedAt") LocalDateTime approvedAt
1802
- );
1803
-
1804
- void updateRejection(
1805
- @Param("journalEntryId") String journalEntryId,
1806
- @Param("rejectedBy") String rejectedBy,
1807
- @Param("reason") String reason,
1808
- @Param("rejectedAt") LocalDateTime rejectedAt
1809
- );
1810
-
1811
- void updateRevision(
1812
- @Param("journalEntryId") String journalEntryId,
1813
- @Param("transactionDate") LocalDate transactionDate,
1814
- @Param("description") String description,
1815
- @Param("revisedBy") String revisedBy,
1816
- @Param("revisedAt") LocalDateTime revisedAt
1817
- );
1818
-
1819
- void updatePosting(
1820
- @Param("journalEntryId") String journalEntryId,
1821
- @Param("fiscalPeriod") String fiscalPeriod,
1822
- @Param("postedBy") String postedBy,
1823
- @Param("postedAt") LocalDateTime postedAt
1824
- );
1825
-
1826
- void updateCancellation(
1827
- @Param("journalEntryId") String journalEntryId,
1828
- @Param("cancelledBy") String cancelledBy,
1829
- @Param("reason") String reason,
1830
- @Param("cancelledAt") LocalDateTime cancelledAt
1831
- );
1832
-
1833
- // 削除
1834
- void deleteLines(@Param("journalEntryId") String journalEntryId);
1835
- }
1836
- ```
1837
-
1838
- </details>
1839
-
1840
- ---
1841
-
1842
- ### 29.5 MyBatis Mapper XML
1843
-
1844
- <details>
1845
- <summary>コード例: JournalEntryMapper.xml</summary>
1846
-
1847
- ```xml
1848
- <?xml version="1.0" encoding="UTF-8" ?>
1849
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
1850
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
1851
-
1852
- <mapper namespace="com.example.accounting.journal.adapter.outbound.persistence.mapper.JournalEntryMapper">
1853
-
1854
- <resultMap id="journalEntryResultMap" type="JournalEntryEntity">
1855
- <id property="journalEntryId" column="journal_entry_id"/>
1856
- <result property="journalNumber" column="journal_number"/>
1857
- <result property="transactionDate" column="transaction_date"/>
1858
- <result property="description" column="description"/>
1859
- <result property="status" column="status"/>
1860
- <result property="totalDebitAmount" column="total_debit_amount"/>
1861
- <result property="totalCreditAmount" column="total_credit_amount"/>
1862
- <result property="createdBy" column="created_by"/>
1863
- <result property="createdAt" column="created_at"/>
1864
- <result property="submittedBy" column="submitted_by"/>
1865
- <result property="submittedAt" column="submitted_at"/>
1866
- <result property="approvedBy" column="approved_by"/>
1867
- <result property="approvalComment" column="approval_comment"/>
1868
- <result property="approvedAt" column="approved_at"/>
1869
- <result property="rejectedBy" column="rejected_by"/>
1870
- <result property="rejectionReason" column="rejection_reason"/>
1871
- <result property="rejectedAt" column="rejected_at"/>
1872
- <result property="revisedBy" column="revised_by"/>
1873
- <result property="revisedAt" column="revised_at"/>
1874
- <result property="fiscalPeriod" column="fiscal_period"/>
1875
- <result property="postedBy" column="posted_by"/>
1876
- <result property="postedAt" column="posted_at"/>
1877
- <result property="cancelledBy" column="cancelled_by"/>
1878
- <result property="cancellationReason" column="cancellation_reason"/>
1879
- <result property="cancelledAt" column="cancelled_at"/>
1880
- <collection property="lines" ofType="JournalLineEntity"
1881
- select="findLinesByJournalEntryId" column="journal_entry_id"/>
1882
- </resultMap>
1883
-
1884
- <resultMap id="journalLineResultMap" type="JournalLineEntity">
1885
- <id property="lineId" column="line_id"/>
1886
- <result property="journalEntryId" column="journal_entry_id"/>
1887
- <result property="accountCode" column="account_code"/>
1888
- <result property="accountName" column="account_name"/>
1889
- <result property="debitAmount" column="debit_amount"/>
1890
- <result property="creditAmount" column="credit_amount"/>
1891
- </resultMap>
1892
-
1893
- <insert id="insert" parameterType="JournalEntryEntity">
1894
- INSERT INTO journal_entries (
1895
- journal_entry_id, journal_number, transaction_date, description,
1896
- status, created_by, created_at
1897
- ) VALUES (
1898
- #{journalEntryId}, #{journalNumber}, #{transactionDate}, #{description},
1899
- #{status}, #{createdBy}, #{createdAt}
1900
- )
1901
- </insert>
1902
-
1903
- <insert id="insertLines" parameterType="list">
1904
- INSERT INTO journal_lines (
1905
- journal_entry_id, account_code, account_name, debit_amount, credit_amount
1906
- ) VALUES
1907
- <foreach collection="list" item="line" separator=",">
1908
- (#{line.journalEntryId}, #{line.accountCode}, #{line.accountName},
1909
- #{line.debitAmount}, #{line.creditAmount})
1910
- </foreach>
1911
- </insert>
1912
-
1913
- <select id="findById" resultMap="journalEntryResultMap">
1914
- SELECT je.*,
1915
- (SELECT COALESCE(SUM(debit_amount), 0) FROM journal_lines WHERE journal_entry_id = je.journal_entry_id) as total_debit_amount,
1916
- (SELECT COALESCE(SUM(credit_amount), 0) FROM journal_lines WHERE journal_entry_id = je.journal_entry_id) as total_credit_amount
1917
- FROM journal_entries je
1918
- WHERE je.journal_entry_id = #{journalEntryId}
1919
- </select>
1920
-
1921
- <select id="findAll" resultMap="journalEntryResultMap">
1922
- SELECT je.*,
1923
- (SELECT COALESCE(SUM(debit_amount), 0) FROM journal_lines WHERE journal_entry_id = je.journal_entry_id) as total_debit_amount,
1924
- (SELECT COALESCE(SUM(credit_amount), 0) FROM journal_lines WHERE journal_entry_id = je.journal_entry_id) as total_credit_amount
1925
- FROM journal_entries je
1926
- ORDER BY je.created_at DESC
1927
- </select>
1928
-
1929
- <select id="findByStatus" resultMap="journalEntryResultMap">
1930
- SELECT je.*,
1931
- (SELECT COALESCE(SUM(debit_amount), 0) FROM journal_lines WHERE journal_entry_id = je.journal_entry_id) as total_debit_amount,
1932
- (SELECT COALESCE(SUM(credit_amount), 0) FROM journal_lines WHERE journal_entry_id = je.journal_entry_id) as total_credit_amount
1933
- FROM journal_entries je
1934
- WHERE je.status = #{status}
1935
- ORDER BY je.created_at DESC
1936
- </select>
1937
-
1938
- <select id="findByFiscalPeriod" resultMap="journalEntryResultMap">
1939
- SELECT je.*,
1940
- (SELECT COALESCE(SUM(debit_amount), 0) FROM journal_lines WHERE journal_entry_id = je.journal_entry_id) as total_debit_amount,
1941
- (SELECT COALESCE(SUM(credit_amount), 0) FROM journal_lines WHERE journal_entry_id = je.journal_entry_id) as total_credit_amount
1942
- FROM journal_entries je
1943
- WHERE je.fiscal_period = #{fiscalPeriod}
1944
- ORDER BY je.transaction_date, je.journal_number
1945
- </select>
1946
-
1947
- <select id="findByTransactionDateRange" resultMap="journalEntryResultMap">
1948
- SELECT je.*,
1949
- (SELECT COALESCE(SUM(debit_amount), 0) FROM journal_lines WHERE journal_entry_id = je.journal_entry_id) as total_debit_amount,
1950
- (SELECT COALESCE(SUM(credit_amount), 0) FROM journal_lines WHERE journal_entry_id = je.journal_entry_id) as total_credit_amount
1951
- FROM journal_entries je
1952
- WHERE je.transaction_date BETWEEN #{fromDate} AND #{toDate}
1953
- ORDER BY je.transaction_date, je.journal_number
1954
- </select>
1955
-
1956
- <select id="findLinesByJournalEntryId" resultMap="journalLineResultMap">
1957
- SELECT * FROM journal_lines WHERE journal_entry_id = #{journalEntryId} ORDER BY line_id
1958
- </select>
1959
-
1960
- <update id="updateStatus">
1961
- UPDATE journal_entries SET status = #{status} WHERE journal_entry_id = #{journalEntryId}
1962
- </update>
1963
-
1964
- <update id="updateSubmission">
1965
- UPDATE journal_entries
1966
- SET submitted_by = #{submittedBy}, submitted_at = #{submittedAt}
1967
- WHERE journal_entry_id = #{journalEntryId}
1968
- </update>
1969
-
1970
- <update id="updateApproval">
1971
- UPDATE journal_entries
1972
- SET approved_by = #{approvedBy}, approval_comment = #{comment}, approved_at = #{approvedAt}
1973
- WHERE journal_entry_id = #{journalEntryId}
1974
- </update>
1975
-
1976
- <update id="updateRejection">
1977
- UPDATE journal_entries
1978
- SET rejected_by = #{rejectedBy}, rejection_reason = #{reason}, rejected_at = #{rejectedAt}
1979
- WHERE journal_entry_id = #{journalEntryId}
1980
- </update>
1981
-
1982
- <update id="updateRevision">
1983
- UPDATE journal_entries
1984
- SET transaction_date = #{transactionDate}, description = #{description},
1985
- revised_by = #{revisedBy}, revised_at = #{revisedAt}
1986
- WHERE journal_entry_id = #{journalEntryId}
1987
- </update>
1988
-
1989
- <update id="updatePosting">
1990
- UPDATE journal_entries
1991
- SET fiscal_period = #{fiscalPeriod}, posted_by = #{postedBy}, posted_at = #{postedAt}
1992
- WHERE journal_entry_id = #{journalEntryId}
1993
- </update>
1994
-
1995
- <update id="updateCancellation">
1996
- UPDATE journal_entries
1997
- SET cancelled_by = #{cancelledBy}, cancellation_reason = #{reason}, cancelled_at = #{cancelledAt}
1998
- WHERE journal_entry_id = #{journalEntryId}
1999
- </update>
2000
-
2001
- <delete id="deleteLines">
2002
- DELETE FROM journal_lines WHERE journal_entry_id = #{journalEntryId}
2003
- </delete>
2004
-
2005
- </mapper>
2006
- ```
2007
-
2008
- </details>
2009
-
2010
- ---
2011
-
2012
- ### 29.6 Read Model スキーマ
2013
-
2014
- <details>
2015
- <summary>コード例: schema.sql</summary>
2016
-
2017
- ```sql
2018
- -- Read Model: Journal Entries テーブル
2019
- CREATE TABLE IF NOT EXISTS journal_entries (
2020
- journal_entry_id VARCHAR(36) PRIMARY KEY,
2021
- journal_number VARCHAR(20) NOT NULL UNIQUE,
2022
- transaction_date DATE NOT NULL,
2023
- description TEXT,
2024
- status VARCHAR(20) NOT NULL,
2025
-
2026
- -- 監査情報
2027
- created_by VARCHAR(100) NOT NULL,
2028
- created_at TIMESTAMP NOT NULL,
2029
- submitted_by VARCHAR(100),
2030
- submitted_at TIMESTAMP,
2031
- approved_by VARCHAR(100),
2032
- approval_comment TEXT,
2033
- approved_at TIMESTAMP,
2034
- rejected_by VARCHAR(100),
2035
- rejection_reason TEXT,
2036
- rejected_at TIMESTAMP,
2037
- revised_by VARCHAR(100),
2038
- revised_at TIMESTAMP,
2039
- fiscal_period VARCHAR(10),
2040
- posted_by VARCHAR(100),
2041
- posted_at TIMESTAMP,
2042
- cancelled_by VARCHAR(100),
2043
- cancellation_reason TEXT,
2044
- cancelled_at TIMESTAMP
2045
- );
2046
-
2047
- -- Read Model: Journal Lines テーブル
2048
- CREATE TABLE IF NOT EXISTS journal_lines (
2049
- line_id SERIAL PRIMARY KEY,
2050
- journal_entry_id VARCHAR(36) NOT NULL REFERENCES journal_entries(journal_entry_id),
2051
- account_code VARCHAR(10) NOT NULL,
2052
- account_name VARCHAR(100) NOT NULL,
2053
- debit_amount DECIMAL(15, 2) DEFAULT 0,
2054
- credit_amount DECIMAL(15, 2) DEFAULT 0
2055
- );
2056
-
2057
- -- インデックス
2058
- CREATE INDEX IF NOT EXISTS idx_journal_entries_status ON journal_entries(status);
2059
- CREATE INDEX IF NOT EXISTS idx_journal_entries_transaction_date ON journal_entries(transaction_date);
2060
- CREATE INDEX IF NOT EXISTS idx_journal_entries_fiscal_period ON journal_entries(fiscal_period);
2061
- CREATE INDEX IF NOT EXISTS idx_journal_lines_journal_entry_id ON journal_lines(journal_entry_id);
2062
- CREATE INDEX IF NOT EXISTS idx_journal_lines_account_code ON journal_lines(account_code);
2063
- ```
2064
-
2065
- </details>
2066
-
2067
- ---
2068
-
2069
- ### 29.7 最小限フィールドの原則
2070
-
2071
- Event Sourcing では、ドメインモデルは「次のコマンドを処理するために必要な最小限の状態」のみを保持します。一方、Read Model は表示に必要なすべてのフィールドを保持します。
2072
-
2073
- | フィールド | ドメインモデル | Read Model | 理由 |
2074
- |-----------|:-------------:|:----------:|------|
2075
- | journalEntryId | O | O | 識別子として必要 |
2076
- | status | O | O | 状態遷移の判定に必要 |
2077
- | lines | O | O | 借方・貸方の検証に必要 |
2078
- | approvedBy | X | O | 判定に不要、表示のみ |
2079
- | approvedAt | X | O | 判定に不要、表示のみ |
2080
- | fiscalPeriod | X | O | 判定に不要、表示のみ |
2081
- | totalDebitAmount | X | O | 集計値、表示のみ |
2082
-
2083
- ---
2084
-
2085
- ## 第30章:REST API と統合テスト
2086
-
2087
- ### 30.1 REST Controller(Inbound Adapter)
2088
-
2089
- <details>
2090
- <summary>コード例: JournalsController.java</summary>
2091
-
2092
- ```java
2093
- package com.example.accounting.journal.adapter.inbound.rest.journals;
2094
-
2095
- import com.example.accounting.journal.adapter.inbound.rest.journals.protocol.*;
2096
- import com.example.accounting.journal.adapter.outbound.persistence.mapper.JournalEntryMapper;
2097
- import com.example.accounting.journal.domain.model.aggregate.journal.JournalEntryCommands.*;
2098
- import io.swagger.v3.oas.annotations.Operation;
2099
- import io.swagger.v3.oas.annotations.tags.Tag;
2100
- import jakarta.validation.Valid;
2101
- import org.axonframework.commandhandling.gateway.CommandGateway;
2102
- import org.springframework.http.HttpStatus;
2103
- import org.springframework.http.ResponseEntity;
2104
- import org.springframework.web.bind.annotation.*;
2105
-
2106
- import java.util.UUID;
2107
- import java.util.concurrent.CompletableFuture;
2108
-
2109
- /**
2110
- * JournalEntry REST Controller(Inbound Adapter)
2111
- */
2112
- @RestController
2113
- @RequestMapping("/api/journals")
2114
- @Tag(name = "Journals", description = "Journal Entry management API")
2115
- public class JournalsController {
2116
-
2117
- private final CommandGateway commandGateway;
2118
- private final JournalEntryMapper journalEntryMapper;
2119
-
2120
- public JournalsController(CommandGateway commandGateway, JournalEntryMapper journalEntryMapper) {
2121
- this.commandGateway = commandGateway;
2122
- this.journalEntryMapper = journalEntryMapper;
2123
- }
2124
-
2125
- // ========== Command Side (Write) ==========
2126
-
2127
- @PostMapping
2128
- @Operation(summary = "Create a new journal entry")
2129
- public CompletableFuture<ResponseEntity<JournalEntryCreateResponse>> create(
2130
- @Valid @RequestBody JournalEntryCreateRequest request
2131
- ) {
2132
- var journalEntryId = UUID.randomUUID().toString();
2133
- var lines = request.lines().stream()
2134
- .map(line -> new JournalLineInput(
2135
- line.accountCode(),
2136
- line.accountName(),
2137
- line.debitAmount(),
2138
- line.creditAmount()
2139
- ))
2140
- .toList();
2141
-
2142
- var command = new CreateJournalEntryCommand(
2143
- journalEntryId,
2144
- request.transactionDate(),
2145
- request.description(),
2146
- lines,
2147
- request.createdBy()
2148
- );
2149
-
2150
- return commandGateway.send(command)
2151
- .thenApply(result -> ResponseEntity
2152
- .status(HttpStatus.CREATED)
2153
- .body(new JournalEntryCreateResponse(journalEntryId)));
2154
- }
2155
-
2156
- @PostMapping("/{id}/submit")
2157
- @Operation(summary = "Submit a journal entry for approval")
2158
- public CompletableFuture<ResponseEntity<Void>> submit(
2159
- @PathVariable String id,
2160
- @Valid @RequestBody JournalEntrySubmitRequest request
2161
- ) {
2162
- var command = new SubmitJournalEntryCommand(id, request.submittedBy());
2163
-
2164
- return commandGateway.send(command)
2165
- .thenApply(result -> ResponseEntity.ok().<Void>build());
2166
- }
2167
-
2168
- @PostMapping("/{id}/approve")
2169
- @Operation(summary = "Approve a journal entry")
2170
- public CompletableFuture<ResponseEntity<Void>> approve(
2171
- @PathVariable String id,
2172
- @Valid @RequestBody JournalEntryApproveRequest request
2173
- ) {
2174
- var command = new ApproveJournalEntryCommand(id, request.approvedBy(), request.comment());
2175
-
2176
- return commandGateway.send(command)
2177
- .thenApply(result -> ResponseEntity.ok().<Void>build());
2178
- }
2179
-
2180
- @PostMapping("/{id}/reject")
2181
- @Operation(summary = "Reject a journal entry")
2182
- public CompletableFuture<ResponseEntity<Void>> reject(
2183
- @PathVariable String id,
2184
- @Valid @RequestBody JournalEntryRejectRequest request
2185
- ) {
2186
- var command = new RejectJournalEntryCommand(id, request.rejectedBy(), request.reason());
2187
-
2188
- return commandGateway.send(command)
2189
- .thenApply(result -> ResponseEntity.ok().<Void>build());
2190
- }
2191
-
2192
- @PostMapping("/{id}/post")
2193
- @Operation(summary = "Post a journal entry to the ledger")
2194
- public CompletableFuture<ResponseEntity<Void>> post(
2195
- @PathVariable String id,
2196
- @Valid @RequestBody JournalEntryPostRequest request
2197
- ) {
2198
- var command = new PostJournalEntryCommand(id, request.fiscalPeriod(), request.postedBy());
2199
-
2200
- return commandGateway.send(command)
2201
- .thenApply(result -> ResponseEntity.ok().<Void>build());
2202
- }
2203
-
2204
- @PostMapping("/{id}/cancel")
2205
- @Operation(summary = "Cancel a journal entry")
2206
- public CompletableFuture<ResponseEntity<Void>> cancel(
2207
- @PathVariable String id,
2208
- @Valid @RequestBody JournalEntryCancelRequest request
2209
- ) {
2210
- var command = new CancelJournalEntryCommand(id, request.cancelledBy(), request.reason());
2211
-
2212
- return commandGateway.send(command)
2213
- .thenApply(result -> ResponseEntity.ok().<Void>build());
2214
- }
2215
-
2216
- // ========== Query Side (Read) ==========
2217
-
2218
- @GetMapping("/{id}")
2219
- @Operation(summary = "Get a journal entry by ID")
2220
- public ResponseEntity<JournalEntryGetResponse> get(@PathVariable String id) {
2221
- return journalEntryMapper.findById(id)
2222
- .map(entity -> ResponseEntity.ok(JournalEntryGetResponse.from(entity)))
2223
- .orElse(ResponseEntity.notFound().build());
2224
- }
2225
-
2226
- @GetMapping
2227
- @Operation(summary = "Get all journal entries")
2228
- public ResponseEntity<JournalEntryListResponse> getAll() {
2229
- var entities = journalEntryMapper.findAll();
2230
- var items = entities.stream()
2231
- .map(JournalEntryGetResponse::from)
2232
- .toList();
2233
- return ResponseEntity.ok(new JournalEntryListResponse(items));
2234
- }
2235
-
2236
- @GetMapping("/status/{status}")
2237
- @Operation(summary = "Get journal entries by status")
2238
- public ResponseEntity<JournalEntryListResponse> getByStatus(@PathVariable String status) {
2239
- var entities = journalEntryMapper.findByStatus(status);
2240
- var items = entities.stream()
2241
- .map(JournalEntryGetResponse::from)
2242
- .toList();
2243
- return ResponseEntity.ok(new JournalEntryListResponse(items));
2244
- }
2245
- }
2246
- ```
2247
-
2248
- </details>
2249
-
2250
- ---
2251
-
2252
- ### 30.2 API エンドポイント一覧
2253
-
2254
- | メソッド | パス | 説明 | 種別 |
2255
- |---------|------|------|------|
2256
- | POST | `/api/journals` | 仕訳作成 | Command |
2257
- | POST | `/api/journals/{id}/submit` | 承認申請 | Command |
2258
- | POST | `/api/journals/{id}/approve` | 承認 | Command |
2259
- | POST | `/api/journals/{id}/reject` | 却下 | Command |
2260
- | POST | `/api/journals/{id}/post` | 転記 | Command |
2261
- | POST | `/api/journals/{id}/cancel` | キャンセル | Command |
2262
- | GET | `/api/journals` | 仕訳一覧取得 | Query |
2263
- | GET | `/api/journals/{id}` | 仕訳詳細取得 | Query |
2264
- | GET | `/api/journals/status/{status}` | ステータス別一覧 | Query |
2265
-
2266
- ---
2267
-
2268
- ### 30.3 Axon Test による単体テスト
2269
-
2270
- <details>
2271
- <summary>コード例: JournalEntryAggregateTest.java</summary>
2272
-
2273
- ```java
2274
- package com.example.accounting.journal.application.aggregate;
2275
-
2276
- import com.example.accounting.journal.api.events.*;
2277
- import com.example.accounting.journal.api.events.JournalEntryCreatedEvent.JournalLineData;
2278
- import com.example.accounting.journal.domain.model.aggregate.journal.JournalEntryCommands.*;
2279
- import org.axonframework.test.aggregate.AggregateTestFixture;
2280
- import org.axonframework.test.aggregate.FixtureConfiguration;
2281
- import org.junit.jupiter.api.BeforeEach;
2282
- import org.junit.jupiter.api.DisplayName;
2283
- import org.junit.jupiter.api.Nested;
2284
- import org.junit.jupiter.api.Test;
2285
-
2286
- import java.math.BigDecimal;
2287
- import java.time.LocalDate;
2288
- import java.time.LocalDateTime;
2289
- import java.util.List;
2290
-
2291
- class JournalEntryAggregateTest {
2292
-
2293
- private FixtureConfiguration<JournalEntryAggregateAdapter> fixture;
2294
-
2295
- @BeforeEach
2296
- void setUp() {
2297
- fixture = new AggregateTestFixture<>(JournalEntryAggregateAdapter.class);
2298
- }
2299
-
2300
- @Nested
2301
- @DisplayName("仕訳作成")
2302
- class CreateJournalEntry {
2303
-
2304
- @Test
2305
- @DisplayName("借方・貸方が一致する仕訳を作成できる")
2306
- void shouldCreateBalancedJournalEntry() {
2307
- var lines = List.of(
2308
- new JournalLineInput("1000", "現金", BigDecimal.valueOf(10000), BigDecimal.ZERO),
2309
- new JournalLineInput("4000", "売上", BigDecimal.ZERO, BigDecimal.valueOf(10000))
2310
- );
2311
-
2312
- var command = new CreateJournalEntryCommand(
2313
- "je-1",
2314
- LocalDate.of(2024, 1, 15),
2315
- "売上計上",
2316
- lines,
2317
- "user-1"
2318
- );
2319
-
2320
- fixture.givenNoPriorActivity()
2321
- .when(command)
2322
- .expectSuccessfulHandlerExecution()
2323
- .expectEventsMatching(events ->
2324
- events.getPayload() instanceof JournalEntryCreatedEvent
2325
- );
2326
- }
2327
-
2328
- @Test
2329
- @DisplayName("借方・貸方が一致しない場合はエラー")
2330
- void shouldRejectUnbalancedJournalEntry() {
2331
- var lines = List.of(
2332
- new JournalLineInput("1000", "現金", BigDecimal.valueOf(10000), BigDecimal.ZERO),
2333
- new JournalLineInput("4000", "売上", BigDecimal.ZERO, BigDecimal.valueOf(5000))
2334
- );
2335
-
2336
- var command = new CreateJournalEntryCommand(
2337
- "je-1",
2338
- LocalDate.of(2024, 1, 15),
2339
- "不均衡な仕訳",
2340
- lines,
2341
- "user-1"
2342
- );
2343
-
2344
- fixture.givenNoPriorActivity()
2345
- .when(command)
2346
- .expectException(IllegalArgumentException.class);
2347
- }
2348
- }
2349
-
2350
- @Nested
2351
- @DisplayName("仕訳承認")
2352
- class ApproveJournalEntry {
2353
-
2354
- @Test
2355
- @DisplayName("承認待ちの仕訳を承認できる")
2356
- void shouldApprovePendingJournalEntry() {
2357
- var createdEvent = createSampleCreatedEvent();
2358
- var submittedEvent = new JournalEntrySubmittedEvent("je-1", "user-1", LocalDateTime.now());
2359
-
2360
- fixture.given(createdEvent, submittedEvent)
2361
- .when(new ApproveJournalEntryCommand("je-1", "approver-1", "承認します"))
2362
- .expectSuccessfulHandlerExecution()
2363
- .expectEventsMatching(events ->
2364
- events.getPayload() instanceof JournalEntryApprovedEvent
2365
- );
2366
- }
2367
-
2368
- @Test
2369
- @DisplayName("下書きの仕訳は承認できない")
2370
- void shouldNotApproveDraftJournalEntry() {
2371
- var createdEvent = createSampleCreatedEvent();
2372
-
2373
- fixture.given(createdEvent)
2374
- .when(new ApproveJournalEntryCommand("je-1", "approver-1", "承認します"))
2375
- .expectException(IllegalStateException.class);
2376
- }
2377
- }
2378
-
2379
- @Nested
2380
- @DisplayName("仕訳転記")
2381
- class PostJournalEntry {
2382
-
2383
- @Test
2384
- @DisplayName("承認済みの仕訳を転記できる")
2385
- void shouldPostApprovedJournalEntry() {
2386
- var createdEvent = createSampleCreatedEvent();
2387
- var submittedEvent = new JournalEntrySubmittedEvent("je-1", "user-1", LocalDateTime.now());
2388
- var approvedEvent = new JournalEntryApprovedEvent("je-1", "approver-1", "OK", LocalDateTime.now());
2389
-
2390
- fixture.given(createdEvent, submittedEvent, approvedEvent)
2391
- .when(new PostJournalEntryCommand("je-1", "2024-01", "poster-1"))
2392
- .expectSuccessfulHandlerExecution()
2393
- .expectEventsMatching(events ->
2394
- events.getPayload() instanceof JournalEntryPostedEvent
2395
- );
2396
- }
2397
- }
2398
-
2399
- private JournalEntryCreatedEvent createSampleCreatedEvent() {
2400
- var lines = List.of(
2401
- new JournalLineData("1000", "現金", BigDecimal.valueOf(10000), BigDecimal.ZERO),
2402
- new JournalLineData("4000", "売上", BigDecimal.ZERO, BigDecimal.valueOf(10000))
2403
- );
2404
-
2405
- return new JournalEntryCreatedEvent(
2406
- "je-1",
2407
- "JE-00000001",
2408
- LocalDate.of(2024, 1, 15),
2409
- "売上計上",
2410
- lines,
2411
- "user-1",
2412
- LocalDateTime.now()
2413
- );
2414
- }
2415
- }
2416
- ```
2417
-
2418
- </details>
2419
-
2420
- ---
2421
-
2422
- ## Part 5-F のまとめ
2423
-
2424
- ### 実装した機能一覧
2425
-
2426
- | 章 | 内容 | 主要技術 |
2427
- |---|---|---|
2428
- | **第26章: 基礎** | CQRS/ES アーキテクチャの基礎 | Axon Framework, Event Sourcing |
2429
- | **第27章: ドメイン設計** | イベント、コマンド、ドメインモデル | sealed interface, record |
2430
- | **第28章: Aggregate/Policy** | Aggregate Adapter, Choreography | @Aggregate, @EventHandler |
2431
- | **第29章: Projection** | Read Model の更新、最小限フィールド | @EventHandler, MyBatis |
2432
- | **第30章: REST API** | Inbound Adapter, テスト | CommandGateway, Axon Test |
2433
-
2434
- ### アーキテクチャの特徴
2435
-
2436
- ```plantuml
2437
- @startuml cqrs_summary
2438
- skinparam backgroundColor #FEFEFE
2439
-
2440
- title CQRS/ES アーキテクチャ(財務会計システム)
2441
-
2442
- package "Command Side" {
2443
- [REST Controller] as RC1
2444
- [CommandGateway] as CG
2445
- [Aggregate Adapter] as AA
2446
- [Domain Model] as DM
2447
- [Event Store] as ES
2448
- }
2449
-
2450
- package "Query Side" {
2451
- [REST Controller] as RC2
2452
- [MyBatis Mapper] as MB
2453
- [Read Model DB] as DB
2454
- [Projection] as P
2455
- }
2456
-
2457
- RC1 --> CG : send(command)
2458
- CG --> AA : @CommandHandler
2459
- AA --> DM : ビジネスロジック
2460
- DM --> AA : event
2461
- AA --> ES : apply(event)
2462
- ES --> P : @EventHandler
2463
- P --> MB : insert/update
2464
- MB --> DB : SQL
2465
-
2466
- RC2 --> MB : query
2467
- MB --> DB : SELECT
2468
-
2469
- note right of DM
2470
- 純粋なドメインモデル
2471
- Axon に依存しない
2472
- end note
2473
-
2474
- note right of P
2475
- イベントを購読して
2476
- MyBatis 経由で
2477
- Read Model を更新
2478
- end note
2479
-
2480
- @enduml
2481
- ```
2482
-
2483
- ### 設計上の特徴
2484
-
2485
- | 特徴 | 説明 |
2486
- |------|------|
2487
- | **ドメインモデルとフレームワークの分離** | `JournalEntry.java` は純粋なドメインモデル、`JournalEntryAggregateAdapter.java` は Axon 用アダプター |
2488
- | **Choreography による疎結合** | 各 Context は独立して動作、イベントを介した非同期連携 |
2489
- | **sealed interface による型安全性** | イベントの網羅性チェック、switch 式でコンパイル時検証 |
2490
- | **record によるイミュータブル設計** | 状態変更は常に新しいインスタンスを生成 |
2491
- | **最小限フィールドの原則** | ドメインモデルは状態遷移判定に必要な最小限のみ保持 |
2492
- | **MyBatis による柔軟な SQL 制御** | Read Model の更新に MyBatis Mapper を使用 |
2493
- | **ビジネスルールの集約** | 借方・貸方一致検証をドメインモデル内で実施 |
2494
-
2495
- ### 財務会計システムでの CQRS/ES の意義
2496
-
2497
- | 観点 | 説明 |
2498
- |------|------|
2499
- | **監査証跡** | すべての仕訳変更履歴を完全に保持(SOX 法対応) |
2500
- | **訂正仕訳** | 過去の仕訳を直接変更せず、訂正仕訳として追記 |
2501
- | **期末決算** | 任意の時点での残高を再計算可能 |
2502
- | **ワークフロー** | 仕訳の作成→承認→転記の状態管理 |
2503
- | **内部統制** | 誰が・いつ・何を承認したかの記録 |
2504
-
2505
- ### 技術スタック
2506
-
2507
- | カテゴリ | 技術 |
2508
- |---------|------|
2509
- | **言語** | Java 21 |
2510
- | **フレームワーク** | Spring Boot 3.4.1 |
2511
- | **CQRS/ES** | Axon Framework 4.10.3 |
2512
- | **ORM** | MyBatis 3.0.4 |
2513
- | **データベース** | H2 / PostgreSQL |
2514
- | **テスト** | JUnit 5, Axon Test |
2515
-
2516
- CQRS/ES は財務会計システムに特に適しています。すべての取引履歴を完全に保持し、監査要件に対応できるだけでなく、仕訳の承認ワークフローやビジネスルール(借方・貸方の一致)を明確に表現できます。MyBatis を使用することで、複雑な財務レポート(試算表、財務諸表など)のクエリにも柔軟に対応できます。