
TDD
ã€ãã³ã
該åœããã³ã³ãã³ããèŠã€ãããŸããã§ãã
ãã¬ãžã³
æè¡ããã°
ã¯ããã« ããã«ã¡ã¯ãåºå¹¹ã·ã¹ãã æ¬éšã»ãªãã¬ã€ã¹æšé²éšã»ãªãã¬ã€ã¹æšé²ãããã¯ã®å²¡æ¬ã§ãã ç§ãã¡ã®ããŒã ã§ã¯ãZOZOã®åºå¹¹ã·ã¹ãã ãªãã¬ã€ã¹ã®äžç°ãšããŠãäŒèšé åã®ã·ã¹ãã ãæ°èŠæ§ç¯ããŠããŸããã¢ãŒããã¯ãã£ã«ã¯CQRSïŒCommand Query Responsibility SegregationïŒ+ESïŒEvent SourcingïŒãæ¡çšããŸããïŒä»¥éãCQRS+ESãšç¥èšããŸãïŒã æ¬èšäºã§ã¯ãCQRS+ESãå®åãžé©çšããäžã§çŽé¢ãããå°ããªéçŽãä¿ã¡ãªããã倧éã®éçŽããŸããã æ¥ååºåãã©ãå®çŸãããããšãã課é¡ãšããã®è§£æ±ºã§åŸãããç¥èŠã玹ä»ããŸãã äŒèšã·ã¹ãã ã§ã¯ã決æžã«é¢é£ããæçްããŒã¿ã決æžIDåäœã®å°ããªéçŽïŒAggregateïŒãšããŠèšèšããŠããŸããäžæ¹ã§ãæ¶èŸŒçµæãææ¬¡ã§ãŸãšãã垳祚ãåºåãããããªãŠãŒã¹ã±ãŒã¹ã§ã¯æ°äžä»¶èŠæš¡ã®éçŽã暪æããå¿
èŠããããéçŽã®å¢çãšæ¥ååºåã®ã¹ã³ãŒãã«äžäžèŽãçããŸãããã®äžäžèŽã«ãããSagaã«ããå調ã®çµæã1ã€ã®ã€ãã³ãã§QueryåŽã«å±ããå¿
èŠãçãŸããã€ãã³ããã€ããŒãã®è¥å€§åãåé¡ãšãªããŸãããç§ãã¡ã¯ãã®åé¡ãå
±æããŒãã«ãšã·ã°ãã«ã€ãã³ããçµã¿åããããã¿ãŒã³ã§è§£æ±ºããŸããã ãªããæ¬èšäºã§è¿°ã¹ãäŒèšã·ã¹ãã ã®ä»æ§ã¯ãå®è£
äžã®å顿§é ã説æããããã«ç°¡ç¥åã»æœè±¡åãããã®ã§ãããå®éã®ã·ã¹ãã 仿§ãšã¯ç°ãªããŸããCQRS+ESãå®åã«é©çšããäžã§åæ§ã®èª²é¡ã«çŽé¢ããŠããæ¹ã
ã®äžå©ãšãªãã°å¹žãã§ãã ç®æ¬¡ ã¯ããã« ç®æ¬¡ èæ¯ åºå¹¹ã·ã¹ãã ãªãã¬ã€ã¹ã®æŠèŠ äŒèšã·ã¹ãã ã®æŠèŠ æ¬èšäºã®ã¹ã³ãŒããšæ³å®èªè
ãªãCQRS+ESãéžãã ã ã€ã³ãã©æ§æã®éžæ ââ RDB 1ã€ã§CQRS+ESãå®çŸãã éçŽã®å¢çãšæ¥ååºåã®ã¹ã³ãŒãã®äžäžèŽ å°ããªéçŽãšå€§ããªåºå ã¹ã³ãŒãã®äžäžèŽãçãèª²é¡ Sagaã§è€æ°éçŽãå調ããã Sagaã«ããåèª¿ã®æ§æ åèª¿ã®æ¬¡ã«æ¥ãåé¡ïŒQueryåŽãžã®ããŒã¿äŒé QueryåŽãžã®ããŒã¿äŒé ââ ã€ãã³ãã«èŒããããªããšã ãã¹ããã©ã¯ãã£ã¹ïŒã€ãã³ãã«å
šæ
å ±ãèŒããŠQueryåŽã«æž¡ã æ°äžä»¶èŠæš¡ã®ããŒã¿ãã€ãã³ãã«èŒããã¹ããïŒ æ¡çšãããã¿ãŒã³ïŒå
±æããŒãã«ïŒã·ã°ãã«ã€ãã³ã ãã®ãã¿ãŒã³ã®è§£é CQRS+ESãå®è·µããŠã¿ãŠ ãŸãšã èæ¯ åºå¹¹ã·ã¹ãã ãªãã¬ã€ã¹ã®æŠèŠ ZOZOã®åºå¹¹ã·ã¹ãã ã¯ã20幎以äžã«ãããæ©èœè¿œå ãéããŠããå€§èŠæš¡ã¢ããªã¹ã§ããæè¡çè² åµã®èç©ã«ããä¿å®ã»æ¡åŒµã³ã¹ããå¢å€§ããŠããããšãããçŸåšãå
šç€Ÿçãªåºå¹¹ã·ã¹ãã ãªãã¬ã€ã¹ãããžã§ã¯ããé²è¡ããŠããŸãã ãã®ãªãã¬ã€ã¹ã§ã¯ãéèŠåºŠãšç§»è¡ã³ã¹ãã®äž¡é¢ãèæ
®ããäžã§åªå
床ãã€ããã¢ããªã¹ããã®æ®µéçãªç§»è¡ãé²ããŠããŸãããªãã¬ã€ã¹ãããžã§ã¯ãã®èæ¯ãå
è¡äºäŸã«ã€ããŠã¯ã ã¢ããªã¹ãããã€ã¯ããµãŒãã¹ãžâZOZOBASEãæ¯ããçºéã·ã¹ãã ãªãã¬ã€ã¹ã®åãçµã¿ ãã§è©³ãã玹ä»ããŠããŸãã ææ°ã®åºå¹¹ã·ã¹ãã ãªãã¬ã€ã¹ã®ç¶æ³ã«ã€ããŠã¯ã 巚倧ã¢ããªã¹ã®ãªãã¬ã€ã¹ââæ©èœæŽçãšãã€ããªããã¢ãŒããã¯ãã£ã§æãã åæ§ç¯æŠç¥ ãã®çºè¡šè³æã«ãŸãšããŠããŸããçºè¡šã®æ§åã¯ã ã¢ãŒããã¯ãã£Conference 2025 åè³&åå ã¬ããŒã ãã§ç޹ä»ããŠããŸãã äŒèšã·ã¹ãã ã®æŠèŠ ç§ãã¡ãåãçµãã§ããäŒèšã·ã¹ãã ãªãã¬ã€ã¹ã¯ãçºéã·ã¹ãã ãšåæ§ã«åºå¹¹ã·ã¹ãã ããç¬ç«ãããã€ã¯ããµãŒãã¹ãšããŠæ°èŠã«æ§ç¯ããŠããŸãã äŒèšã·ã¹ãã ãæ±ããã¡ã€ã³ã®äžæ žã¯ããåŒç€Ÿã·ã¹ãã ã®å£²äžå®çžŸã®ããŒã¿ããšã決æžä»£è¡äŒç€Ÿãªã©ã®å€éšã·ã¹ãã ã®å
¥éå®çžŸã®ããŒã¿ããçªåããåŠçã§ãã äŒèšçšèªã§ãããå
¥éã®æ¶èŸŒãã«ããããŸãã売äžãšå
¥éã®æçŽ°ã¯åã
ä»»æã®ã¿ã€ãã³ã°ã§å°çããŸãããã®éœåºŠã決æžIDåäœã§æçްãç
§åãæ¶èŸŒåŠçãå®è¡ããå¿
èŠããããŸãã æ¬èšäºã®ã¹ã³ãŒããšæ³å®èªè
ãã®ã·ã¹ãã ã®ã¢ãŒããã¯ãã£ãšããŠãCQRS+ESãæ¡çšããŸãããæ¬èšäºã§ã¯CQRS+ESã®æ¡çšçç±ã«ã軜ãè§ŠããŸãããæ¬é¡ã¯ Aggregateã®æŽåæ§å¢çãšæ¥ååºåã®ã¹ã³ãŒããäžèŽããªãå Žå ã«çããèšèšèª²é¡ãšããã®è§£æ³ã§ããå
·äœçã«ã¯ãæ°äžä»¶èŠæš¡ã®ããŒã¿ãã©ã®ããã«QueryåŽã«å±ããããšããåé¡ãæ±ããŸãã æ³å®èªè
ã¯CQRS+ESã®åºæ¬çãªæŠå¿µãçè§£ããŠããæ¹ã§ããäœããã®CQRS+ESãã¬ãŒã ã¯ãŒã¯ã«è§Šããããšãããæ¹ã¯ãããè峿·±ãèªãã§ããã ããŸãã ãªãCQRS+ESãéžãã ã äŒèšã·ã¹ãã ã§ã¯ããã¹ãŠã®æ¥åæäœã®å±¥æŽãå³å¯ã«èšé²ããåŸãã远跡å¯èœã«ããããšãæ±ããããŸãã Event Sourcing ã§ã¯ãããžãã¹ãšã³ãã£ãã£ã®ç¶æ
ããç¶æ
倿Žã€ãã³ãã®åããšããŠæ°žç¶åããŸãããã®ãããæ¥åã€ãã³ãã®å±¥æŽããã®ãŸãŸç£æ»ãã°ãšããŠæ©èœãããšããæ§è³ªããäŒèšãã¡ã€ã³ã®èŠä»¶ãšåèŽããŸããã ããã§éèŠãªã®ã¯ããã°ãšã€ãã³ãã®éãã§ãããã°ãèšé²ããã ãã§ã¯ããã°ãšå®éã®ã·ã¹ãã ã®åäœãæŽåããŠããä¿èšŒã¯ãããŸãããäžæ¹ãESã§ã¯ã€ãã³ãïŒäºå®ïŒããã¹ãŠã®èµ·ç¹ã§ãããã€ãã³ããšåäœãå¿
ãæŽåããŸããäŒèšã·ã¹ãã ã«ãããŠãäœãèµ·ãããããæ£ç¢ºã«è¿œè·¡ã§ããããšã¯ãç£æ»ã®èгç¹ããæ¬è³ªçãªèŠä»¶ã§ãããã®ãããESã®æ¡çšãé©åã§ãããšå€æããŸããã ãŸããQueryã®éœåãæ°ã«ããŠãã¡ã€ã³ã¢ãã«ãæ§ç¯ãããšãæãéèŠãªCommandåŽã®ããžãã¯ç®¡çãè€éåããŸããCQRSã«ããCommandãšQueryã®ã¢ãã«ãåé¢ããããšã§ãããããã®é¢å¿äºã«éäžããèšèšãå¯èœã«ãªããŸãã 瀟å
ã®æè¡ã¹ã¿ãã¯ãJavaã«çµ±äžããŠãããJavaäžã§CQRS+ESãå®çŸãããã¬ãŒã ã¯ãŒã¯ãšã㊠Axon Framework ãæ¡çšããŸãããAxon Frameworkãéžå®ããçç±ã®1ã€ã¯ãCQRS+ESã®å®è·µã«å¿
èŠãªãã©ã¯ãã£ã¹ããã¬ãŒã ã¯ãŒã¯ã¬ãã«ã§çšæãããŠããç¹ã§ããå
·äœçã«ã¯ã以äžã®ãããªä»çµã¿ããã¬ãŒã ã¯ãŒã¯ãšããŠæäŸãããŠããŸãã ã€ãã³ãã®æ°žç¶åãšãªãã¬ã€ ã¹ãããã·ã§ããã«ããéçŽã®åŸ©å
æé©å Sagaã«ããè€æ°éçŽã®å調 Processing Groupãšã»ã°ã¡ã³ãã«ãã䞊ååŠçã®å¶åŸ¡ ããããèªåã§å®è£
ããå¿
èŠããªãããšã§ãCQRS+ESã®åºç€æ§ç¯ã§ã¯ãªãããã¡ã€ã³ã®èšèšã«éäžã§ãããšå€æããŸããã ã€ã³ãã©æ§æã®éžæ ââ RDB 1ã€ã§CQRS+ESãå®çŸãã äžè¬çãªCQRSã¢ãŒããã¯ãã£ã§ã¯ãCommandåŽãšQueryåŽãå¥ã
ã®ããŒã¿ã¹ãã¢ã«åé¢ããã¡ãã»ãŒãžãããŒã«ãŒãä»ããŠã€ãã³ããäŒéããæ§æãæ¡çšãããŸããäžå³ã¯ã Axonå
¬åŒããã¥ã¡ã³ã ã«ç€ºãããŠããäžè¬çãªCQRSã¢ããªã±ãŒã·ã§ã³ã®æè¡æŠèŠãåèã«åäœæãããã®ã§ã 1 ã å
¬åŒå³ã§ã¯ãEvent Storeã»Event Busã»QueryåŽã®ããŒã¿ããŒã¹ãããããç¬ç«ããã³ã³ããŒãã³ããšããŠæãããŠããŸãããããã®ã€ã³ãã©æ§æã«ã¯è€æ°ã®éžæè¢ããããŸããããšãã°ã€ãã³ãã¹ãã¢ãšã¡ãã»ãŒãžã«ãŒãã£ã³ã°ãäžäœã§æäŸããAxon ServerããEvent Busã«Kafkaãªã©ã®ã¡ãã»ãŒãžãããŒã«ãŒãæ¡çšããæ§æãèããããŸãã ç§ãã¡ã®ã·ã¹ãã ã§ã¯ESã®äž»ãªæ¡çšåæ©ãç£æ»ãã°ã®å®çŸã§ãããé«ãã¹ã±ãŒã©ããªãã£ãå€éšã·ã¹ãã ãžã®ã€ãã³ã飿ºã¯èŠä»¶ã§ã¯ãããŸããã§ããããã®ããããããã®éžæè¢ã以äžã®2ã€ã®èгç¹ããè©äŸ¡ããçµæãããããæ¡çšãèŠéããŸããã ééçã³ã¹ã ïŒAxon Serverã®ã¯ã©ã¹ã¿æ§æã®ã©ã€ã»ã³ã¹è²»çšããã¡ãã»ãŒãžãããŒã«ãŒã®è¿œå ã€ã³ãã©ã³ã¹ããçºçãã åŠç¿ã³ã¹ã ïŒããŒã ã«ãšã£ãŠãªãã¿ã®èãæè¡ã¹ã¿ãã¯ãå°å
¥ããå ŽåãåŠç¿ã³ã¹ããšéçšè² è·ãé«ããªã ããŒã ã«ç¥èŠã®ããRDBã®ã¿ã®æ§æã§ãèŠä»¶ãæºãããããšããããã Event Storeã»Event Busã»Read Modelããã¹ãŠåäžã®RDBäžã§å®çŸããæ§æ ãæ¡çšããŸãããäžå³ã¯ãä»åæ¡çšããåäžRDBæ§æã瀺ããŠããŸãã ä»åã®æ§æã§ã¯ãç¬ç«ããEvent Busã³ã³ããŒãã³ãã¯ååšããŸãããAxon FrameworkãEvent StoreïŒ domain_event_entry ããŒãã«ïŒãããŒãªã³ã°ããããšã§ãEvent Busã®åœ¹å²ãå®çŸããŠããŸãããŸããRDBäžã§ã®ããã©ãŒãã³ã¹ã確ä¿ããããã«ãAxonå
¬åŒã® RDBMSãã¥ãŒãã³ã°ã¬ã€ã ãåèã«ã€ã³ããã¯ã¹èšå®çã®ãã¥ãŒãã³ã°ãè¡ã£ãŠããŸãã ç§ãã¡ã®æ§æã§ã¯ãåäžããŒã¿ããŒã¹å
ã«CommandåŽããŒãã«ãQueryåŽããŒãã«ããããŠå
±æããŒãã«ãåå±
ããŠããŸããCommandåŽã®ããŒãã«ïŒ domain_event_entry ã token_entry çïŒã¯Axon Frameworkãå
éšçã«å©çšããããŒãã«ã§ããããã¬ãŒã ã¯ãŒã¯ãå¿
èŠãšããã¹ããŒãããã®ãŸãŸäœæããŠããŸããQueryåŽã®ããŒãã«ã¯Read Modelã衚ã rm_ ãã¬ãã£ãã¯ã¹ã§ç®¡çããŠããŸããå
±æããŒãã«ã¯æšæºæ§æã§ã¯ãªãç§ãã¡ãç¬èªã«å°å
¥ãããã®ã§ãããããå³äžã§ã¯ç¹ç·ã§è¡šèšããŠããŸããè©³çŽ°ã¯æ¬¡ç« 以éã§èª¬æããŸããããã®ããã¹ãŠãåäžããŒã¿ããŒã¹å
ã«ååšããããšããæ§æããå
±æããŒãã«ãã¿ãŒã³ã®åææ¡ä»¶ãšããŠéèŠãªåœ¹å²ãæãããŸãã éçŽã®å¢çãšæ¥ååºåã®ã¹ã³ãŒãã®äžäžèŽ å°ããªéçŽãšå€§ããªåºå ç§ãã¡ã®ã·ã¹ãã ã§ã¯ã AggregateïŒéçŽïŒ ãå°ããªåäœã§ä¿ã€èšèšãæ¡çšããŠããŸããVaughn Vernonæ°ã¯ãEffective Aggregate Designãã®äžã§ãéçŽã®èšèšã«ã€ããŠä»¥äžã®ããã«è¿°ã¹ãŠããŸãã Limit the Aggregate to just the Root Entity and a minimal number of attributes and/or Value-typed properties. (...) A large-cluster Aggregate will never perform or scale well. ïŒæ¥æ¬èªèš³ïŒéçŽã¯ã«ãŒããšã³ãã£ãã£ãšæå°éã®å±æ§ãValueåããããã£ã«éå®ãã¹ãã§ãããïŒäžç¥ïŒå€§ããªã¯ã©ã¹ã¿ã®éçŽã¯ãããã©ãŒãã³ã¹ãã¹ã±ãŒã©ããªãã£ã決ããŠè¯ããªããªãã ââ Vaughn Vernon, " Effective Aggregate Design Part I " ãã®æéã«åŸããç§ãã¡ã®ã·ã¹ãã ã§ãéçŽãå°ããªåäœã§ä¿ã£ãŠããŸãããèæ¯ãã§è¿°ã¹ãéãã売äžãšå
¥éã®æçŽ°ãæ±ºæžIDåäœã§ç
§åãããããåéçŽãåãç²åºŠã§èšèšããŠãããæ¯æ¥èšå€§ãªæ°ã®éçŽã€ã³ã¹ã¿ã³ã¹ãçãŸããŸãã æ±ºæžIDåäœã®å°ããªéçŽã«ããå¿
ç¶æ§ã¯ãåæçްãèªèº«ã®ç¶æ
ã«åºã¥ããŠç¬ç«ããå€æã»æ¯ãèããè¡ãå¿
èŠãããããã§ããåéçŽã¯æ¶èŸŒã«é¢ããã¹ããŒã¿ã¹ãå
éšã«ä¿æããŠããŸããããã«ãåæçްã«å¯ŸããŠã¯åé€ã³ãã³ããåãä»ããèŠä»¶ããããŸããåé€ã³ãã³ããåããéããã®æçŽ°ããã§ã«åž³ç¥šåºåæžã¿ã§ããã°æã¡æ¶ãã®åž³ç¥šãåºåããŠããåé€ãããšãã£ããæçްåäœã®ç¶æ
ïŒæ¶èŸŒã¹ããŒã¿ã¹ã垳祚åºåæž/æªæžçïŒã«å¿ããæ¯ãèãã®åå²ãæ±ããããŸãããã®ããã«ãåã
ã®æçŽ°ãèªèº«ã®ç¶æ
ã«åºã¥ããŠç¬ç«ããŠå€æããå¿
èŠããããããå°ããªéçŽãšããŠã®èšèšãå¿
ç¶ã§ãã äžæ¹ã§ã垳祚åºåãšããæ¥ååŠçã¯ããããæ°äžä»¶èŠæš¡ã®éçŽã暪æãã倧ããªã¹ã³ãŒãã§å®è¡ãããŸãã 垳祚åºåæã«ã¯æ°äžä»¶èŠæš¡ã®éçŽã®ã¹ããŒã¿ã¹ããåºåæžãã«æŽæ°ããããã«QueryåŽïŒRead ModelïŒã§ã¯ãã¹ããŒã¿ã¹ãæŽæ°ãããæ°äžä»¶èŠæš¡ã®ããŒã¿ããããªã垳祚ãšããŠåºåããå¿
èŠããããŸãã ã¹ã³ãŒãã®äžäžèŽãçãèª²é¡ äžå³ã¯ããã®ãã¹ã³ãŒãã®äžäžèŽãã瀺ããŠããŸããåéçŽã¯æ±ºæžIDåäœã®å°ããªå¢çãæã£ãŠããŸããã垳祚åºåã®ã¹ã³ãŒãã¯æ°äžä»¶èŠæš¡ã®éçŽã暪æããŸãã 1ã€ã®éçŽã®ã¹ã³ãŒããšæ¥ååºåã®ã¹ã³ãŒãã«ã¯å€§ããªã®ã£ãããååšããŸãããã®æ§é ã¯ãå°ããªéçŽãšããèšèšãæ£ããããããçãŸããåé¡ã§ããéçŽã倧ããããã°è§£æ¶ã§ããŸãããããã¯Vernonæ°ãææããã倧ããªéçŽã®ã¢ã³ããã¿ãŒã³ãã«é¥ãããšãæå³ããŸãããããã£ãŠãéçŽã®å¢çã¯ãã®ãŸãŸç¶æããäžã§ãæ°äžä»¶èŠæš¡ã®éçŽã暪æçã«å調ãããä»çµã¿ãå¿
èŠã«ãªããŸãã Sagaã§è€æ°éçŽãå調ããã Sagaã«ããåèª¿ã®æ§æ åç« ã§ç€ºãããæ°äžä»¶èŠæš¡ã®éçŽã暪æçã«å調ãããããšãã課é¡ã«å¯ŸããŠã Saga ãæ¡çšããŸãããSagaã¯ãè€æ°ã®ããŒã«ã«ãã©ã³ã¶ã¯ã·ã§ã³ãå調ããããã¿ãŒã³ã§ã 2 ã ç§ãã¡ã®æ§æã§ã¯ãSagaãæ°äžä»¶èŠæš¡ã®éçŽã«Commandãéä¿¡ããåéçŽãåŠçå®äºåŸã«Eventãè¿åŽããSagaãããããåéããŠå
šäœã®å®äºã倿ããŸããå®éã«ã¯Sagaã芪åã«éå±€åãã芪SagaãåSagaãè€æ°èµ·åããŠãåSagaããããåäœã§éçŽã管çããæ§æãæ¡çšããŠããŸããããã«ããã䞊ååŠçã®æµéå¶åŸ¡ãå®çŸããŠããŸããäžå³ã¯ããã®å調ãããŒã®æŠå¿µã瀺ããŠããŸãã åSagaã¯åéçŽããã®å®äºã€ãã³ããåãåããã³ã«åŠçæžã¿ã®ä»¶æ°ãã«ãŠã³ããããã¹ãŠã®éçŽã®åŠçãå®äºããæç¹ã§èŠªSagaã«å®äºãéç¥ããŸãããªããéçŽãå¥ã®ãŠãŒã¹ã±ãŒã¹ã§å逿žã¿ããŸãã¯ãã§ã«åž³ç¥šåºåæžã¿ã§ãã£ãå Žåã¯ã垳祚åºåã®å¯Ÿè±¡å€ã§ããããšã瀺ãã€ãã³ããè¿åŽããŸããSagaã¯ãã®ã€ãã³ããåŠçæžã¿ãšããŠã«ãŠã³ããã垳祚ã«ã¯åºåããªããã®ãšããŠæ±ããŸãã芪Sagaã¯ãã¹ãŠã®åSagaã®å®äºããã£ãŠãå
šäœå®äºããšå€æããŸããæ°äžä»¶èŠæš¡ã®éçŽã暪æçã«å調ããããšãã課é¡èªäœã¯ããã®Sagaã®éå±€æ§é ã§è§£æ±ºã§ããŸãã åèª¿ã®æ¬¡ã«æ¥ãåé¡ïŒQueryåŽãžã®ããŒã¿äŒé Sagaããå
šéçŽã®åŠçãå®äºããããšå€æããæ¬¡ã®ã¹ãããã§ãæ°ããªåé¡ãçãŸããŸããæ°äžä»¶èŠæš¡ã®åŠççµæããQueryåŽã«ã©ã®ããã«å±ããã°ããã®ã§ããããã QueryåŽãžã®ããŒã¿äŒé ââ ã€ãã³ãã«èŒããããªããšã ãã¹ããã©ã¯ãã£ã¹ïŒã€ãã³ãã«å
šæ
å ±ãèŒããŠQueryåŽã«æž¡ã CQRS+ESã«ããããã¹ããã©ã¯ãã£ã¹ã¯ã ã€ãã³ãã«å¿
èŠãªæ
å ±ããã¹ãŠèŒããŠQueryåŽã«æž¡ã ããšã§ãã Microsoftã® CQRS Patternã¬ã€ã ã§ã¯ãCommandåŽãšQueryåŽã®åæã«ã€ããŠæ¬¡ã®ããã«è¿°ã¹ãŠããŸãã When you use separate data stores, you must ensure that both remain synchronized. A common pattern is to have the write model publish events when it updates the database, which the read model uses to refresh its data. ïŒæ¥æ¬èªèš³ïŒå¥ã
ã®ããŒã¿ã¹ãã¢ã䜿çšããå Žåãäž¡æ¹ã®åæãä¿ã€å¿
èŠããããŸããäžè¬çãªãã¿ãŒã³ã¯ãæžã蟌ã¿ã¢ãã«ãããŒã¿ããŒã¹ãæŽæ°ããéã«ã€ãã³ããçºè¡ããèªã¿åãã¢ãã«ããã®ã€ãã³ãã䜿çšããŠããŒã¿ãæŽæ°ãããšãããã®ã§ãã ââ Microsoft Azure Architecture Center, "CQRS Pattern" ã€ãã³ãããã¹ãŠã®æ
å ±ãéã¶ããšã«ãããQueryåŽã¯CommandåŽã®ããŒã¿ã¹ãã¢ãçŽæ¥åç
§ããå¿
èŠããªããªããŸãããã®ãã€ãã³ããéããççµåããããCQRSã®æ ¹å¹¹ã§ããQueryåŽã®ProjectionïŒã€ãã³ãããRead Modelãå°åºããåŠçïŒã¯ãåä¿¡ããã€ãã³ãã®ãã€ããŒãã ãã§Read Modelãæ§ç¯ã§ããŸãããã®ãããCommandåŽãšQueryåŽã®ç¬ç«æ§ãä¿ãããŸãã æ°äžä»¶èŠæš¡ã®ããŒã¿ãã€ãã³ãã«èŒããã¹ããïŒ ç§ãã¡ã®ã±ãŒã¹ã§ãã®ãã¹ããã©ã¯ãã£ã¹ããã®ãŸãŸé©çšã§ããã§ãããããåç« ã§ç€ºããéããSagaãå
šéçŽã®å®äºãæ€ç¥ããæç¹ã§æ°äžä»¶èŠæš¡ã®åŠççµæãQueryåŽã«å±ããå¿
èŠããããŸãããã¹ããã©ã¯ãã£ã¹ã«åŸãã°ãããããã¹ãŠã®ããŒã¿ãå®äºã€ãã³ãã®ãã€ããŒãã«å«ããã¹ãã§ãã ããããããã«ã¯2ã€ã®åé¡ããããŸãã1ã€ç®ã¯ ãã€ããŒãã®è¥å€§å ã§ããæ°äžä»¶èŠæš¡ã®éçŽã«é¢ããããŒã¿ã1ã€ã®ã€ãã³ãã«è©°ã蟌ãããšã¯ãã·ãªã¢ã©ã€ãºã»ãã·ãªã¢ã©ã€ãºã®ã³ã¹ããã¡ã¢ãªäœ¿çšéã®èгç¹ããéå¹çã§ãã2ã€ç®ã¯ QueryåŽã§ã®å©çšåœ¢æ
ãšã®äžäžèŽ ã§ãã垳祚åºåã®åŸç¶åŠçã§ã¯ãåæ®µã®Projectionã§æ§ç¯æžã¿ã® rm_ ããŒãã«ãšã®JOINãå¿
èŠã§ããä»®ã«ã€ãã³ããã€ããŒãã«ããŒã¿ãåãããããšããŠããQueryåŽã§çµå±ããŒãã«ã«å±éããŠJOINããããšã«ãªããããã€ãã³ãçµç±ã§éã¶å©ç¹ã¯èããŸãã æ¡çšãããã¿ãŒã³ïŒå
±æããŒãã«ïŒã·ã°ãã«ã€ãã³ã å
è¿°ã®åé¡ã«å¯ŸããŠãããã€ãã®æ¹éãæ€èšããŸããã 1ã€ç®ã¯ QueryåŽã®Projectionã§å®çµãããã¢ãããŒã ã§ããåéçŽã®åŠçå®äºã€ãã³ããProjectionãåä¿¡ã㊠rm_ ããŒãã«ã«æžã蟌ã¿ããã¹ãŠã®æžã蟌ã¿ãçµãã£ãåŸã«åž³ç¥šãåºåããæ¹åŒã§ãã ããããæ°äžä»¶èŠæš¡ã®ã€ãã³ããå®çšçãªæéå
ã«åŠçããã«ã¯Projectionã®äžŠååãå¿
é ã§ããAxon Frameworkã®Tracking Processorã§ã¯ãè€æ°ã®ã»ã°ã¡ã³ããã€ãã³ããåæ
ããŠäžŠåã«åŠçããŸããåäžã»ã°ã¡ã³ãå
ã§ã¯ã€ãã³ãã®åŠçé åºãä¿èšŒãããŸãããå®äºã€ãã³ãïŒã·ã°ãã«ã€ãã³ãïŒãšåéçŽã®åŠçå®äºã€ãã³ãã¯ç°ãªãã»ã°ã¡ã³ãã«æ¯ãåãããããããšãåé¡ã§ãã ç°ãªãã»ã°ã¡ã³ãéã§ã¯åŠçã®é²è¡åºŠãç°ãªããããããã»ã°ã¡ã³ããå®äºã€ãã³ããåŠçããæç¹ã§ãå¥ã»ã°ã¡ã³ãã§ã¯ãŸã åŠçãå®äºããŠããªãå¯èœæ§ããããŸãã ã€ãŸããã·ã°ãã«ã€ãã³ããProjectionã«å±ããæç¹ã§ rm_ ããŒãã«ãžã®æžã蟌ã¿ãå®äºããŠããªãå¯èœæ§ããããããŒã¿ã®æ¬ æãçããŸãããããé²ãã«ã¯Projectionã«å調ããžãã¯ãå¿
èŠã§ãããããã¯Sagaã®è²¬åã§ãããProjectionã®é¢å¿äºã®åé¢ã厩ããããèŠéããŸããã 2ã€ç®ã¯ ã€ãã³ãã®åå²éä¿¡ ïŒãã£ã³ã¯åïŒã§ããæ°äžä»¶ã®ããŒã¿ãNä»¶ãã€è€æ°ã®ã€ãã³ãã«åå²ããŠéä¿¡ããæ¹åŒã§ãããããããã®æ¹åŒã§ã¯QueryåŽã®Projectionãããã¹ãŠã®ãã£ã³ã¯ãå±ãããããå€å®ããå調ããžãã¯ãæã€å¿
èŠãããã1ã€ç®ãšåãå顿§é ãæ±ãããããèŠéããŸããã 3ã€ç®ã¯ Claim Checkãã¿ãŒã³ ã§ããã€ãã³ãã«ã¯ããŒã¿æ¬äœãèŒãããå€éšã¹ãã¬ãŒãžãžã®åç
§ã®ã¿ãå«ããæ¹åŒã§ããæè¡çã«ã¯å®çŸå¯èœã§ããã以äžã®çç±ããèŠéããŸããã å€éšã¹ãã¬ãŒãžã®å°å
¥ã¯ãã€ã³ãã©æ§æã®éžæãã§è¿°ã¹ãåäžRDBæ§æã®æ¹éã厩ã å€éšã¹ãã¬ãŒãžãžã®æžã蟌ã¿ã¯Event Storeãšå¥ãã©ã³ã¶ã¯ã·ã§ã³ã«ãªããé害æã®æŽåæ§æ
ä¿ãè€éåãã ãããã®æ€èšãçµãŠãç§ãã¡ã¯åäžRDBæ§æã®å©ç¹ã掻ããã å
±æããŒãã«ãšã·ã°ãã«ã€ãã³ããçµã¿åããããã¿ãŒã³ ãæ¡çšããŸãããåè¿°ã®éããåã
ã®æçŽ°ããŒã¿ã¯éåžžã®Projectionã§Read Modelã«æ§ç¯æžã¿ã§ããäžè¶³ããŠããã®ã¯ãã©ã®æçްãã©ã®åž³ç¥šã«å±ããããšãã察å¿é¢ä¿ã§ãããã®ãã¿ãŒã³ã®æ§æã¯ä»¥äžã®éãã§ãã Sagaã¯åž³ç¥šåºåãããŒã®éå§æã«åž³ç¥šIDãæ¡çªããåéçŽã«Commandãéä¿¡ãããåŠçå®äºã€ãã³ããåä¿¡ãããã³ã«ã åäžãã©ã³ã¶ã¯ã·ã§ã³å
㧠垳祚IDãšæçŽ°IDã®å¯Ÿå¿é¢ä¿ã å
±æããŒãã« ã«é次æžã蟌ã ãã¹ãŠã®éçŽã®åŠçãå®äºããããSaga㯠å®äºã€ãã³ã ãçºè¡ããïŒãã€ããŒãã¯æå°éã®ã·ã°ãã«ã®ã¿ïŒ QueryåŽã®Projectionã¯å®äºã€ãã³ããããªã¬ãŒãšããŠåä¿¡ãã垳祚åºåãå¯èœã«ãªã£ãããšã瀺ãRead ModelïŒ rm_ ããŒãã«ïŒãäœæãã åŸç¶ã®ã¬ããŒãçæåŠçããã®Read Modelãæ€ç¥ãã垳祚ã®Read Modelã»å
±æããŒãã«ã»æçްã®Read Modelãé ã«JOINããŠåž³ç¥šããŒã¿ãååŸãã ã¹ããã1ã®ãã€ã³ãã¯ãAxon Frameworkã®Sagaãã€ãã³ããã³ãã©ã®åŠçãUnit of WorkïŒUoWïŒãã¿ãŒã³ã§ç®¡çããŠããç¹ã§ããã€ãã³ãã®åä¿¡ãšå
±æããŒãã«ãžã®æžã蟌ã¿ãåããã©ã³ã¶ã¯ã·ã§ã³ã§å®è¡ãããããããã¹ãŠã®éçŽã®åŠçãå®äºããæç¹ã§ã¯ã察å¿ããããŒã¿ãå
±æããŒãã«äžã«ã確å®ã«ããã£ãŠããŸãã ããã§éèŠãªã®ã¯ããã€ã³ãã©æ§æã®éžæãã§èª¬æãã åäžRDBæ§æ ã§ããCommandåŽããŒãã«ãQueryåŽããŒãã«ããããŠå
±æããŒãã«ããã¹ãŠåäžã®ããŒã¿ããŒã¹å
ã«ååšãããããå
±æããŒãã«ãžã®æžã蟌ã¿ãšJOINã«ããèªã¿åããèªç¶ã«å®çŸã§ããŸããããCommandåŽãšQueryåŽãç°ãªãããŒã¿ã¹ãã¢ã«åé¢ãããŠãããããã®ãã¿ãŒã³ã¯æç«ããŸããã å
è¿°ã®Projectionå®çµã¢ãããŒãã§åé¡ãšãªã£ãã»ã°ã¡ã³ãéã®é²è¡åºŠã®å·®ã¯ãæ¬ãã¿ãŒã³ã§ã¯æ§é çã«çºçããŸãããå
±æããŒãã«ãžã®æžã蟌ã¿ãSagaãæ
ãããã¹ãŠã®æžã蟌ã¿ãå®äºããåŸã«åããŠå®äºã€ãã³ããçºè¡ããããã§ãã ãã®ãã¿ãŒã³ã®è§£é ãã®ãã¿ãŒã³ã§ã¯æåéãCommandåŽãšQueryåŽã§ããŒãã«ãå
±æããŠããŸããããã¯CQRSã®ååãCommandåŽãšQueryåŽã¯ã€ãã³ããéããŠã®ã¿æ
å ±ãããåããããããã®æå³çãªéžè±ã§ããå°æ¥çãªããŒã¿ã¹ãã¢ã®ç©çåé¢ãé£ãããªããã¬ãŒããªãã¯ãããŸããã以äžã®2ç¹ãèæ
®ãæ¡çšããŸããã çŸæç¹ã§CommandåŽãšQueryåŽã®ç©çåé¢ã¯æ³å®ãããªãããš å
±æããŒãã«ã¯æç€ºçã«èšèšã»ç®¡çãããŠãããæé»ã®äŸåã§ã¯ãªãããšãå°æ¥çã«ç©çåé¢ãå¿
èŠã«ãªã£ãå Žåããå
±æããŒãã«ã®åç
§ç®æãæç¢ºã§ãããããæ®µéçãªç§»è¡ãå¯èœã§ããããš å®éã«ãã®èšèšã§éçšããŠã¿ãŠãProjectionã®ããžãã¯ãã·ã³ãã«ã«ä¿ãããEvent Storeã®ãã€ããŒãè¥å€§åãåé¿ã§ããŠããç¹ã«æå¿ããæããŠããŸããäžæ¹ã§ãå
±æããŒãã«ã®ã¹ããŒã倿ŽãCommandåŽãšQueryåŽã®äž¡æ¹ã«åœ±é¿ããç¹ã«ã¯æ³šæãå¿
èŠã§ããéåžžã®CQRSã§ã¯ãCommandåŽãšQueryåŽã®ã¹ããŒããç¬ç«ã«å€æŽã§ããããšãå©ç¹ã®1ã€ã§ãããå
±æããŒãã«ã«é¢ããŠã¯ãã®å©ç¹ã倱ãããŸãã CQRS+ESãå®è·µããŠã¿ãŠ æ¬èšäºã§ç޹ä»ããSagaã«ããæ°äžä»¶èŠæš¡ã®éçŽã®å調ã¯ãAxon Frameworkã®SagaãµããŒãããªããã°å®çŸãå°é£ã§ããããã®å ŽåãSagaã®ç¶æ
管çãã€ãã³ããšã®çŽä»ããšãã£ãåºç€éšåã®å®è£
ããå§ããå¿
èŠããããŸãããåæ§ã«ãã¹ãããã·ã§ããã«ããéçŽã®åŸ©å
æé©åãProjectionã®é²æç®¡çïŒTracking ProcessorïŒããèªåã§å®è£
ããŠãããå€å€§ãªå·¥æ°ãè²»ãããŠãããšèããããŸããåè¿°ãããããã®åºç€ãæã£ãŠãããããããã¢ãŒããã¯ãã£ã¬ãã«ã®èšèšèª²é¡ã«å¯ŸããŠæ€èšãšè©Šè¡é¯èª€ã®æéã確ä¿ã§ããŸããã å ããŠãAxon Frameworkã§ESãå®çŸããäžã§ãéçŽå
éšã®ããžãã¯ã颿°çãªæ§é ã«ãªãç¹ã«ãè¯ããæããŠããŸããéçŽã®Command Handlerã¯ãCommandãåãåã£ãŠEventãçºè¡ããEvent Sourcing Handlerã¯ãEventãåãåã£ãŠéçŽã®ç¶æ
ãæŽæ°ããŸãããã¹ãããAxon FrameworkãæäŸãã ãã¹ããã£ã¯ã¹ã㣠ãçšããŠãGivenïŒéå»ã®ã€ãã³ãåïŒâ WhenïŒã³ãã³ãïŒâ ThenïŒæåŸ
ãããã€ãã³ãïŒããšãã宣èšçãªåœ¢åŒã§èšè¿°ã§ããŸãããã®æ§é ã¯ãAIã«ãããã¹ãé§åéçºãšçžæ§ãè¯ããšæããŠããŸããå
¥åãšåºåãæç¢ºã«å®çŸ©ãããŠãããããAIããã¹ãã±ãŒã¹ãçæããããããŸããã¹ãã®æå³ã宣èšçã«è¡šçŸããããããAIãçæãããã¹ãã³ãŒãã®ã¬ãã¥ãŒããããããšãã宿ããããŸãã äžæ¹ã§ãESãæ¬æ Œçã«éçšããé£ããã宿ããŠããŸãã ESã§ã¯ãã¹ãŠã®ç¶æ
倿Žããã³ãã³ã â éçŽ â ã€ãã³ããã®ãã€ãã©ã€ã³ãéããŸããã¹ããŒããœãŒã·ã³ã°ã§ããã°äžæ¬æŽæ°ã§æžãåŠçããéçŽããšã«ã³ãã³ããéä¿¡ããåå¥ã«ã€ãã³ããçºè¡ããªããã°ãªããŸããã æ¬èšäºã§æ±ã£ãéçŽæšªæã®å調ã¯ããŸãã«ãã®å¶çŽããçãŸããèšèšèª²é¡ã§ãã ãã®èª²é¡ã«é¢é£ããŠãè¿å¹Žæå±ãããŠãã Dynamic Consistency BoundaryïŒDCBïŒ ãšããæŠå¿µã«æ³šç®ããŠããŸããDCBã¯ãäžè²«æ§ã®å¢çãéçŽã«åºå®ãããã€ãã³ããžä»äžããã¿ã°ã«åºã¥ããŠåçã«äŒžçž®ãããã¢ãããŒãã§ããåŸæ¥ã®ESã§ã¯éçŽã®å¢çãèšèšæã«åºå®ããããããæ¬èšäºã§æ±ã£ããããªSagaã«ããå調ãé¿ããããŸããã§ããããDCBã«ãã£ãŠãã®è€éãã軜æžã§ããå¯èœæ§ããããŸããç§ãã¡ã®ãŠãŒã¹ã±ãŒã¹ã«ã©ããŸã§é©çšã§ãããã¯ãŸã æªç¥æ°ã§ãããESã®å®è·µçãªèª²é¡ãæ§é çã«è§£æ±ºãããã¢ãããŒããšããŠãä»åŸã®ååã远ã£ãŠããŸãã ãŸãšã æ¬èšäºã§ã¯ãäŒèšã·ã¹ãã ãžã®CQRS+ESé©çšã«ãããŠãå°ããªéçŽãä¿ã¡ãªãã倧éã®éçŽããŸããã æ¥ååºåãå®çŸããéçšã§åŸãããç¥èŠã玹ä»ããŸããã å°ããªéçŽãæ£ããèšèšããã»ã©ãæ¥ååºåã®ã¹ã³ãŒããšã®äžäžèŽãé¡åšåããŸããSagaã§æ°äžä»¶èŠæš¡ã®éçŽãå調ãããããšã¯ã§ããŸããããã®çµæãQueryåŽã«å±ããæ®µéã§ãã€ãã³ãã«èŒããããªãããšããå£ã«ã¶ã€ãããŸãããå
±æããŒãã«ãšã·ã°ãã«ã€ãã³ããçµã¿åããããã¿ãŒã³ãæ¡çšããCQRSã®ååããã¯éžè±ãã€ã€ããå®çšçãªè§£æ±ºçã«ãã©ãçããŸããã CQRS+ESã®å®è£
äºäŸã¯ãŸã å€ããªããä»åã®å®è£
ã«ã€ããŠãæ£ãããã®ã§ããããšããäžå®ãšåãåããªããé²ããŠããŸããããªãªãŒã¹ããŠã¿ãŠå€§ããªåé¡ã¯çºçããŠããããããžãã£ããªç¶æ³ã§ãããšæããŠããŸãããããããã¹ããã©ã¯ãã£ã¹ãããã«ç¢ºç«ãããŠããéã«ã¯ãããã«é©å¿ããŠããå§¿å¢ãæã¡ç¶ããããšèããŠããŸãã æ¬èšäºã§ã¯äŒèšé åã®ãªãã¬ã€ã¹ã玹ä»ããŸããããåãåºå¹¹ã·ã¹ãã ãªãã¬ã€ã¹ã®ç©æµé åã§ãã¡ã³ããŒãåéããŠããŸããå€§èŠæš¡ã¢ããªã¹ããã®ãµãŒãã¹åå²ã«åãçµãããžã·ã§ã³ã§ããã¡ã€ã³é§åèšèšãã€ãã³ãé§åã¢ãŒããã¯ãã£ã®ç¥èãæŽ»ãããç°å¢ã§ããç©æµã·ã¹ãã ã®å·æ°ã«èå³ã®ããæ¹ã¯ããã²ã芧ãã ããã hrmos.co ããã«ZOZOã§ã¯ãäžç·ã«ãµãŒãã¹ãäœãäžããŠããã仲éãåºãåéäžã§ãããèå³ã®ããæ¹ã¯ã以äžã®ãªã³ã¯ãããã²ã芧ãã ããã corp.zozo.com ãã®å³ã¯Axonå
¬åŒããã¥ã¡ã³ããArchitecture Overviewãã®å³ãåèã«ãæ¬èšäºã§å¿
èŠãªæ§æèŠçŽ ã«çµã£ãŠåäœæãããã®ã§ãã ↩ å³å¯ã«ã¯ãSagaãšProcess Manager㯠ç°ãªãæŠå¿µ ã§ããSagaã¯è£åãã©ã³ã¶ã¯ã·ã§ã³ã«çŠç¹ãåœãŠããã¿ãŒã³ã§ããã®ã«å¯ŸããProcess Managerã¯ç¶æ
ãã·ã³ãšããŠã¢ããªã³ã°ãããåä¿¡ã€ãã³ããšçŸåšã®ç¶æ
ã«åºã¥ããŠå€æãäžããŸããAxon Frameworkã§ã¯ @Saga ã¢ãããŒã·ã§ã³ã䜿çšããŠãOrchestrationæ¹åŒã®Process Managerãå®è£
ããŠããŸããæ¬èšäºã§ã¯ããã¬ãŒã ã¯ãŒã¯ã®æ
£äŸã«åãããŠãSagaããšè¡šèšããŸãã ↩
ã¯ããã« ããã«ã¡ã¯ïŒãµã€ãªã¹ãã¯ãããžãŒã®ãªãŒãã§ããååã¯Google Cloudã®Vertex AIãAzureãã䜿çšããããã®æé ãšããããšã§äž»ã«ã€ã³ãã©é¢é£ã®å
å®¹ãæžããŸããããä»åã¯AIã³ãŒãã£ã³ã°ãšãŒãžã§ã³ãã®éçºããã»ã¹ã匷åãããã¬ãŒã ã¯ãŒã¯ãobra/superpowersãã«ã€ããŠæžãããšæããŸãã AIãšãŒãžã§ã³ãã¯æ¬åœã«äŸ¿å©ãªã®ã§ããã䜿ã蟌ãã§ãããšãããããã¹ãæžããã«ãããªãå®è£
ããŠãâŠããåå 調æ»ãªãã«ãšããããããããåœãŠãããšããŠãâŠããšããå Žé¢ã«æ°ã¥ãããšããããŸãããšãŒãžã§ã³ãã¯é«æ§èœã§ãããããã»ã¹ãçç¥ããã¯ã»ããããã§ãããã ãããªæ©ã¿ã解決ããŠãããã®ããä»å玹ä»ãã obra/superpowers ã§ãã92K以äžã®GitHubã¹ã¿ãŒãç²åŸããŠããæ³šç®ã®ãã¬ãŒã ã¯ãŒã¯ã§ãAIã³ãŒãã£ã³ã°ãšãŒãžã§ã³ãã«èŠåŸããéçºããã»ã¹ã泚å
¥ããŠãããŸãã ä»åã¯ãsuperpowersã®æŠèŠããã€ã³ã¹ããŒã«æ¹æ³ãå®éã®æŽ»çšã·ããªãªãŸã§ã詳ãã玹ä»ããŸãã obra/superpowers ãšã¯ obra/superpowers ã¯ãAIã³ãŒãã£ã³ã°ãšãŒãžã§ã³ãåãã® ã¢ãžã£ã€ã«ã¹ãã«ãã¬ãŒã ã¯ãŒã¯ ã§ãã ãã¹ãã«ããšåŒã°ããMarkdownããŒã¹ã®æç€ºãã¡ã€ã«ããšãŒãžã§ã³ãã«èªã¿èŸŒãŸããããšã§ããã¹ãé§åéçºã»äœç³»çãªãããã°ã»ã³ãŒãã¬ãã¥ãŒãšãã£ãèŠåŸããéçºããã»ã¹ã匷å¶ããŸãã “coding agents produce better results when they follow systematic processes rather than ad-hoc approaches” ãã®ææ³ããsuperpowersã®æ ¹å¹¹ã«ãããŸãããšãŒãžã§ã³ãã®èœåãåŒãåºãã®ã§ã¯ãªãã ããã»ã¹ãã¬ã€ããã ãšããã¢ãããŒãã§ãã èªçã®èæ¯ éçºè
ã® Jesse Vincent æ°ïŒPrime RadiantïŒããAIãšãŒãžã§ã³ãã以äžã®ãããªãè¿éããåãããšããåé¡ã«çŽé¢ããããšããã£ããã§ãã ãã¹ããåŸåãã«ããŠå®è£
ãå
ã«æžã æ ¹æ¬åå ã調æ»ããã«ããããåœãŠã å®äºç¢ºèªãªãã«ãã§ããŸããããšå ±åãã superpowers ã¯ãããã®ã¢ã³ããã¿ãŒã³ãæç€ºçã«ããã¥ã¡ã³ãåãããšãŒãžã§ã³ããèªåã§ãããèªèã»æåŠã§ããããã«èšèšãããŠããŸãã åç
§: obra/superpowers – GitHub 察å¿ãã©ãããã©ãŒã superpowers ã¯äž»èŠãªAIã³ãŒãã£ã³ã°ãã©ãããã©ãŒã ãã¹ãŠã«å¯Ÿå¿ããŠããŸãã ãã©ãããã©ãŒã 察å¿ç¶æ³ Claude Code å
¬åŒããŒã±ãããã¬ã€ã¹å¯Ÿå¿ Cursor ãã©ã°ã€ã³ããŒã±ãããã¬ã€ã¹å¯Ÿå¿ Gemini CLI Extensionå¯Ÿå¿ Codex .codex/ ãã£ã¬ã¯ããªæ¹åŒ OpenCode .opencode/ ãã£ã¬ã¯ããªæ¹åŒ ã¹ãã«äžèЧãšã¢ãŒããã¯ã㣠14çš®é¡ã®ã¹ãã«äžèЧ superpowers ã«ã¯14çš®é¡ã®ã¹ãã«ãå«ãŸããŠããã4ã€ã®ã«ããŽãªã«åé¡ãããŠããŸãã ã«ããŽãª ã¹ãã«å ç®ç ãã¹ãã»å質 test-driven-development ãã¹ãå
è¡ã® RED-GREEN-REFACTOR ãµã€ã¯ã«ãåŒ·å¶ systematic-debugging 4ãã§ãŒãºã®æ ¹æ¬åå åæãåŒ·å¶ verification-before-completion 蚌æ ãªãå®äºå®£èšãçŠæ¢ èšç»ã»å®è¡ brainstorming å®è£
åã®èšèšå¯Ÿè©±ã»ä»æ§ã¬ãã¥ãŒã宿œ writing-plans 2ã5åã¿ã¹ã¯åäœã®å®è£
èšç»ãäœæ executing-plans å®è£
èšç»ãé åºéãã«å®è¡ subagent-driven-development ãµããšãŒãžã§ã³ãÃ2段éã¬ãã¥ãŒã§é«å質å®è£
dispatching-parallel-agents ç¬ç«ã¿ã¹ã¯ã䞊åãµããšãŒãžã§ã³ããžå§è² Gitã»ã³ã©ãã¬ãŒã·ã§ã³ using-git-worktrees ç¬ç«ããGit worktreeã§å®å
šã«äžŠåéçº finishing-a-development-branch ãã¹ã確èªâããŒãž/PR/ç Žæ£ã®æ§é åãã㌠requesting-code-review é©åãªã¿ã€ãã³ã°ã§ã³ãŒãã¬ãã¥ãŒãäŸé Œ receiving-code-review ã¬ãã¥ãŒãã£ãŒãããã¯ãæè¡çã«è©äŸ¡ ã¡ã¿ using-superpowers ã¹ãã«ã®çºèŠã»åŒã³åºãã®ã¡ã¿ãããã³ã« writing-skills æ°ããã¹ãã«ãTDDã§äœæ ã³ã¢ã¹ãã«12çš®ã«å ãããã¬ãŒã ã¯ãŒã¯èªäœãæ¡åŒµããã¡ã¿ã¹ãã«2çš®ïŒ using-superpowers ã» writing-skills ïŒãå«ãŸããŠããŸãã ãã£ã¬ã¯ããªæ§æ ãªããžããªã®ãããã¬ãã«æ§æã¯ä»¥äžã®éãã§ãã superpowers/ âââ skills/ # 14çš®é¡ã®ã¹ãã«ã¢ãžã¥ãŒã« âââ agents/ # ãšãŒãžã§ã³ãèšå®ã»æ¯ãèãå®çŸ© âââ commands/ # CLIã³ãã³ãå®è£
âââ hooks/ # ãã©ãããã©ãŒã çµ±åãã㯠âââ docs/ # ãã©ãããã©ãŒã å¥ããã¥ã¡ã³ã âââ tests/ # ãã¹ãã¹ã€ãŒã âââ .claude-plugin/ # Claude Code çµ±å âââ .cursor-plugin/ # Cursor çµ±å âââ .codex/ # Codex çµ±å âââ .opencode/ # OpenCode çµ±å åã¹ãã«ã¯ skills/<ã¹ãã«å>/ ãã£ã¬ã¯ããªã«æ ŒçŽãããŠãããå¿
é ãã¡ã€ã« SKILL.md ã®ä»ã«å¿
èŠã«å¿ããŠãã³ãã¬ãŒããã¹ã¯ãªãããå«ãŸããŸãã ã¢ãŒããã¯ãã£å³ superpowers ã®å
šäœã¢ãŒããã¯ãã£ãå³ã§ç€ºããŸããAIã³ãŒãã£ã³ã°ãã©ãããã©ãŒã ããã¹ãã«ã¬ã€ã€ãŒãéããããã¯ã»Gitçµ±åãžãšæµããæ§é ã«ãªã£ãŠããŸãã ã¹ãã«ã¯ãããžã§ã¯ãã®ã³ã³ããã¹ãã«å¿ã㊠**èªåçã«çºç«** ããŸããç¹å¥ãªæ§æãæååŒã³åºãã¯äžèŠã§ãã æ¬¡ã«ãã¹ãã«ã®ã«ããŽãªæ§æããããã§ç€ºããŸãã SKILL.md ã®ãã©ãŒããã åã¹ãã«ã¯ SKILL.md ãã¡ã€ã«äžæã§å®çŸ©ãããŸããæ§é ã¯ä»¥äžã®éãã§ãã --- name: skill-name description: "Use when ... (æå€§1024æåãããªã¬ãŒæ¡ä»¶ãèšè¿°)" --- ## Overview ## When to Use ## Core Pattern ## Quick Reference ## Implementation ## Common Mistakes ããã³ããã¿ãŒã® description ãã£ãŒã«ããç¹ã«éèŠã§ããšãŒãžã§ã³ããã¹ãã«ãèªåéžæããéã®å€æåºæºã«ãªããŸãããUse whenâŠããšãã圢åŒã§ããªã¬ãŒæ¡ä»¶ãèšè¿°ããããšããã¹ããã©ã¯ãã£ã¹ã§ãã Tokenå¹çãèšèšã®èæ
®äºé
ã§ãé »ç¹ã«èªã¿èŸŒãŸããã¹ãã«ã¯200èªä»¥å
ã«åããããšãæšå¥šãããŠããŸãã ã€ã³ã¹ããŒã«æ¹æ³ Claude Code å
¬åŒããŒã±ãããã¬ã€ã¹çµç±ïŒæšå¥šïŒ: /plugin install superpowers@claude-plugins-official ã«ã¹ã¿ã ããŒã±ãããã¬ã€ã¹çµç±: # ã¹ããã1: ããŒã±ãããã¬ã€ã¹ã远å /plugin marketplace add obra/superpowers-marketplace # ã¹ããã2: ã€ã³ã¹ããŒã« /plugin install superpowers@superpowers-marketplace ã€ã³ã¹ããŒã«åŸãæ°ããã»ãã·ã§ã³ãéå§ããŠããã®æ©èœãèšç»ãããããªã©ãšè©±ãããããšã brainstorming ã¹ãã«ãèªåçã«çºç«ããŸãã ã¢ããããŒã: /plugin update superpowers Cursor Cursor ã®ãã©ã°ã€ã³ããŒã±ãããã¬ã€ã¹ããæ€çŽ¢ããŠã€ã³ã¹ããŒã«ãããã以äžã®ã³ãã³ãã䜿çšããŸãã /add-plugin superpowers Gemini CLI gemini extensions install https://github.com/obra/superpowers åç
§: obra/superpowers – Installation 掻çšã·ããªãª 掻çšã·ããªãªã¯ç¡éã«èããããŸãããä»åã¯äžèšã®6ã€ã®ã·ããªãªã玹ä»ããŸãã TDD ã匷å¶ãã test-driven-development ã¹ãã«ã䜿ããšããšãŒãžã§ã³ãã¯ãã¹ããªãã«å®è£
ã³ãŒããäžåæžããªããªããŸãã éå: “NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.” ãã¹ãããå
ã«ã³ãŒããæžããå Žåã¯ããã®ã³ãŒãããã¹ãŠåé€ããŠæåããããçŽãã ãµã€ã¯ã«ã¯ä»¥äžã®éãã§ãã RED â 倱æãããã¹ããæžã REDç¢ºèª ïŒå¿
é ïŒâ ãã¹ããå®éã«å€±æããããšã確èªãã GREEN â ãã¹ããéãæå°éã®ã³ãŒããæžã GREENç¢ºèª ïŒå¿
é ïŒâ ãã¹ããéãããšã確èªãã REFACTOR â ã³ãŒããæŽçãã æåãã¹ãæžã¿ã§ããããšãçç±ã«ãã¹ããçç¥ããããšãããšãã¹ãã«ããã®ã¢ã³ããã¿ãŒã³ãèªèããŠæåŠããŸãã é·æéã®èªåŸéçºã»ãã·ã§ã³ executing-plans ã¹ãã«ãš subagent-driven-development ã¹ãã«ãçµã¿åããããšã2æé以äžã®èªåŸéçºã»ãã·ã§ã³ãå¯èœã«ãªããŸãã åã¿ã¹ã¯ã¯2ã5ååäœã«åå²ããããµããšãŒãžã§ã³ãã1ã¿ã¹ã¯ãã€å®è£
ã»ã¬ãã¥ãŒãç¹°ãè¿ããŸãã人éãä»å
¥ããªããŠããã¹ãã«ãããã»ã¹ã®å質ãæ
ä¿ããŸãã è€æ°æ©èœã®äžŠåéçº dispatching-parallel-agents ã¹ãã«ã䜿ããšãäºãã«ç¬ç«ããè€æ°ã®ã¿ã¹ã¯ã䞊åã§åŠçã§ããŸãã é©çšæ¡ä»¶: 3ã€ä»¥äžã®ç¬ç«ãã倱æãã¡ã€ã«ã»ãµãã·ã¹ãã ããã ã¿ã¹ã¯éã§å
±æç¶æ
ããªã ããããç°ãªãæ ¹æ¬åå ãæã€ using-git-worktrees ãšçµã¿åãããããšã§ãåãšãŒãžã§ã³ããç¬ç«ããGit worktreeã§å®å
šã«äœæ¥ã§ããŸãã å
¬åŒããã¥ã¡ã³ãã«ãããšã6ä»¶ã®å€±æã3ã€ã®äžŠåãšãŒãžã§ã³ãã§è§£æ±ºããã±ãŒã¹ã§ã¯ãã³ã³ããªã¯ããŒãã§è§£æ±ºã§ãããšã®ããšã§ãã æ ¹æ¬åå ãç¹å®ããŠãããããã°ãã systematic-debugging ã¹ãã«ã¯ããšãŒãžã§ã³ããããšããããããããåœãŠããè¿éãé²ãã4ãã§ãŒãºã®äœç³»çãªãããã°ã匷å¶ããŸãã 4ãã§ãŒãº: Root Cause Investigation â ãšã©ãŒãèªã¿èŸŒã¿ãåçŸæé ã確èªããçŽè¿ã®å€æŽã調æ»ãã Pattern Analysis â åããŠãã䌌ãç®æãšæ¯èŒããŠå·®åãç¹å®ãã Hypothesis and Testing â 仮説ã1ã€ç«ãŠãæå°éã®å€æŽã§æ€èšŒãã Implementation â æ ¹æ¬åå ã®ã¿ãä¿®æ£ããåçºé²æ¢ã®ãã¹ãã远å ãã åå ãç¹å®ã§ããŠããªã段éã§ä¿®æ£ã³ãŒããæžãå§ããããšãããšãã¹ãã«ã® Iron LawãNO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRSTãã«åŒã£ããããPhase 1 ã«å·®ãæ»ãããŸãããçŽã£ããšæã£ãããŸãå£ããããšããç¶æ³ãæ ¹æ¬ããé²ããŸãã å®è£
åã«èšèšãåºãã brainstorming ã¹ãã«ã䜿ããšããšãŒãžã§ã³ãã¯ã³ãŒããæžãåã«èšèšå¯Ÿè©±ãã§ãŒãºã宿œããŸãã å¯Ÿè©±ã®æµã: èŠä»¶ã®ç¢ºèªãšææ§ãªç¹ã®æŽãåºã è€æ°ã®å®è£
ã¢ãããŒããæç€ºããŠæ¯èŒ ãã¬ãŒããªãïŒé床ã»ä¿å®æ§ã»è€é床ïŒãæç€º åæããæ¹éãããã¥ã¡ã³ãå ãã®ã¹ãã«ã¯ writing-plans ãšé£æºããŠããã察話ãçµãããšèªåçã«å®è£
èšç»ã®äœæã«ç§»è¡ããŸããããšããããå®è£
ããŠã¿ãŠåŸã§èããããšãããšãŒãžã§ã³ãã®åŸåããèšèšãã¡ãŒã¹ãã«åãæ¿ããŸãã 1åãã€äžå¯§ã«èŠä»¶ãæ·±æãããåæãåããæ®µéã§èšèšæžãäœæâ writing-plans ã¹ãã«ãžåŒãæž¡ããŸãã ã³ãŒãã¬ãã¥ãŒã®ãµã€ã¯ã«ãåã requesting-code-review ãš receiving-code-review ã®2ã¹ãã«ã䜿ããšããšãŒãžã§ã³ããã¬ãã¥ãŒã®äŸé Œããåæ ãŸã§äžè²«ããŠå¯Ÿå¿ããŸãã ã¬ãã¥ãŒäŸé ŒæïŒ requesting-code-review ïŒ: PR ã®ç®çã»å€æŽã®æŠèŠã»ãã¹ãç¶æ³ãæŽçããŠã¬ãã¥ã¢ãŒã«æç€º ã¬ãã¥ãŒããããç²åºŠã«å€æŽãåå² ãã£ãŒãããã¯åãåãæïŒ receiving-code-review ïŒ: ææå
容ããå¿
é ä¿®æ£ããææ¡ããè°è«ãã«åé¡ æè¡çãªæ ¹æ ãããšã«ä¿®æ£ã®èŠåŠã倿 åæ ãã倿Žãæç€ºããŠã¬ãã¥ã¢ãŒã«è¿ç ææ
çã«åå¿ãããããã¹ãŠã®ææãç¡æ¡ä»¶ã«åãå
¥ãããããã®ã§ã¯ãªããæè¡çãªè©äŸ¡ã«åºã¥ããŠå¯Ÿå¿ããŸãã æè¡çã«äžèŠãªå€æŽã¯ããã·ã¥ããã¯ããæ ¹æ ã®ããä¿®æ£ã®ã¿ãè¡ããŸãã ãããã« ä»å㯠obra/superpowers ã«ã€ããŠç޹ä»ããŸããã AIãšãŒãžã§ã³ãã¯éåžžã«é«æ§èœã§ãããããã»ã¹ãçç¥ããã¡ãšãã匱ç¹ããããŸããsuperpowers ã¯ããã«ãèŠåŸããæ³šå
¥ããããšã§ããšãŒãžã§ã³ããæ¬æ¥ã®å®åã§åããç¶ããããã®ä»çµã¿ã§ãã ç¹ã« test-driven-development ã¹ãã«ãš systematic-debugging ã¹ãã«ã¯ããšãŒãžã§ã³ããè¿éãåãããšããå
žåçãªå Žé¢ã§ç䟡ãçºæ®ããŸãããŸãã¯ãã®2ã€ããã€ã³ã¹ããŒã«ããŠè©ŠããŠã¿ãããšãããããããŸãã èå³ãæã£ãæ¹ã¯ããã²å
¬åŒãªããžããªã writing-skills ã¹ãã«ã䜿ã£ãŠãèªåã ãã®ã¹ãã«ãäœã£ãŠã¿ãŠãã ããïŒ ã芧ããã ãããããšãããããŸãïŒ ãã®æçš¿ã¯ã圹ã«ç«ã¡ãŸãããïŒ åœ¹ã«ç«ã£ã 圹ã«ç«ããªãã£ã 0人ããã®æçš¿ã¯åœ¹ã«ç«ã£ããšèšã£ãŠããŸãã The post AIã³ãŒãã£ã³ã°ãšãŒãžã§ã³ãã®åŒ±ç¹ãè£ããobra/superpowersã first appeared on SIOS Tech Lab .
ãšããœãŒãçŽ¹ä» Ep.1 – ã¯ãªãŒã³ã¢ãŒããã¯ãã£ãšã¯ Ep.2 â èªèšŒæ¹åŒã®å®è·µçãªçŽ¹ä» Ep.3 â ERèšèšãšç£æ»ãã° Ep.4 â RepoScanner ã®å®è£
ãšãã¹ã â ä»åã¯ãã¡ã Ep.5 â Copilot ããã³ãããå¹çå ãããªæ¹ãžç¹ã«ãããã ã¯ãªãŒã³ã¢ãŒããã¯ãã£ã®çå±ã¯åãã£ããã©ãã©ãããæžãå§ããã®ïŒãšçåãªæ¹ ã¯ãªãŒã³ã¢ãŒããã¯ãã£ã§å°ã㪠MVP ãå®è£
ããã¯ãŒã¯ãããŒã«èå³ãããæ¹ TDDïŒãã¹ãé§åéçºïŒãå®åã«åãå
¥ããŠãå£ãã«ããã³ãŒããæžãããæ¹ æŠèŠ ããã«ã¡ã¯ããµã€ãªã¹ãã¯ãããžãŒã®ã¯ãã¡ããã§ãïŒ ã·ãªãŒãº4æ¬ç®ãšãªãä»åã¯ãããããåŸ
æã®å®è£
ç·šã«çªå
¥ããŸãã ããã§å€§ããªåœ¹å²ãæããã®ã TDDïŒãã¹ãé§åéçºïŒ ã§ãããã¹ããã¡ãŒã¹ãã§é²ããããšã§ãäŸåæ§ã®åãé¢ããå¢çã®æç¢ºåãèªç¶ãšè¡ãããŸãã — æ¬ã·ãªãŒãºã§ã¯ãCopilotãæŽ»çšãã€ã€ãã¯ãªãŒã³ã¢ãŒããã¯ãã£ã«æ²¿ã£ãŠå°èŠæš¡ãªãããã¯ããRepoScannerããèšèšã»å®è£
ããçµç·¯ããŸãšããŸãã ãã®ãšããœãŒãã¯ãã¢ããªã®ã³ã¢æ©èœã§ããããªããžããªå
ã®ã¹ãããã·ã§ããäžèЧãååŸããæ©èœãã«çŠç¹ãåœãŠãŸããã ããã«ããAIãžã®æç€ºã®åºãæ¹ããããã¹ãã®è³ªã®å€åãã«ã€ããŠããäŒãããŸãã å®è£
åã®æºå ãããžã§ã¯ãã®å
šäœå å®è£
äœæ¥ãéå§ããåã«ããŸãã¯ãRepoScannerãã®æ§æãæŽçããŠãããŸãã ç®ç ãªããžããªã®ã¡ã¿ããŒã¿ãéèšçµæãå¹çããååŸããåæããããããããšã èŠä»¶ ã¹ãããã·ã§ããäžèЧã®ããŒãžã³ã°ïŒlimit / offsetïŒãæå€§ååŸæ°ã®å¶éã ã¯ãªãŒã³ãªãŠãŒã¹ã±ãŒã¹ãä¿ã€ã3ã€ã®èšèšã«ãŒã«ã RepoScannerã«ãããUse Caseså±€ã§ã¯ã以äžã®ã«ãŒã«ã培åºããŸããã å
¥åºå㯠DTO ïŒData Transfer ObjectïŒ HTTPãªã¯ãšã¹ããªã©ã®ãªããžã§ã¯ãã¯ããã®ãŸãŸäœ¿ããåçŽãªããŒã¿ã¯ã©ã¹ã«å€æããã äŸåã¯ã€ã³ã¿ãŒãã§ãŒã¹ãä»ããŠæ³šå
¥ïŒDIïŒ DBåŠçãªã©ã¯ãå
·äœçãªå®è£
ã§ã¯ãªããRepositoryããªã©ã®æœè±¡çãªåã«äŸåãããã å¯äœçšã®å
¥ãå£ãæç€º ãã©ãã§å€éšAPIãåŒã¶ããªã©åŠçã®æµãããŠãŒã¹ã±ãŒã¹ããåããç¶æ
ãä¿ã€ã ãã®ããã«è²¬åãåé¢ããããšã§ããŠãŒã¹ã±ãŒã¹ãçŽç²ãªããžãã¹ããžãã¯ã ãã«éäžã§ããç°å¢ãæŽããŸãã ã¯ãªãŒã³ã¢ãŒããã¯ãã£ã«ããèšèš ã³ãŒãã®ä¿å®æ§ãé«ãããããã¯ãªãŒã³ã¢ãŒããã¯ãã£ã«åŸã£ãŠè²¬åãæç¢ºã«åé¢ããŸããã Domainå±€ SnapshotSummary ãªã©ã®ãšã³ãã£ã㣠Use Caseså±€ ListSnapshotSummariesUseCase Interface Adapterså±€ HTTP ãšã³ããã€ã³ãã®å¶åŸ¡ Infrastructureå±€ SnapshotQueryRepository ããã§éèŠãªã®ã¯ãDBã¢ã¯ã»ã¹ãžã®äŸåãå¿
ããã€ã³ã¿ãŒãã§ãŒã¹ãçµç±ã«ããããšã§ãã ãã®çµæãå
åŽã®ããžãã¯ãå€åŽã®æè¡çãªè©³çްãç¥ããªããŠæžããäŸåæ§ã®é転ããæç«ããŸãã èšèšäžã®èŠç¹ æ°žç¶åïŒDBã¢ã¯ã»ã¹ïŒãžã®äŸåã¯ãå¿
ããªããžããªã®ãã€ã³ã¿ãŒãã§ãŒã¹ããçµç±ãããŸãã ããã«ãããå
åŽïŒUse Caseså±€ïŒãå€åŽïŒInfrastructureå±€ïŒã®æè¡è©³çްãç¥ããªãç¶æ
ãšãªããäŸåã®é転ãä¿ã¡ãŸãã âã詳现㯠ãšããœãŒã1ãž éå±€ããšã®ãã¹ãæŠç¥ ãã©ããããã¹ããæžãã°ãããåãããªãããšããæ©ã¿ã¯ãã¯ãªãŒã³ã¢ãŒããã¯ãã£ã§è§£æ±ºããŸãã ãªããªããåéå±€ã®åœ¹å²ãã¯ã£ããããŠããããããã¹ãã®ç®çãèªããšå®ãŸãããã§ãã æåªå
: Use Caseså±€ã®ãŠããããã¹ã ããã§ã¯ã仿§ãšããŠã®æ£ããããæ€èšŒããŸãã limit ã®äžéã¯ãªããã³ã°ãã offset ã®è² å€ãã§ãã¯ãªã©ã察象ã§ãã ãã®ãã¹ãã¯ãã¬ãŒã ã¯ãŒã¯æŽæ°ã DB ãã©ã€ã倿Žã«åœ±é¿ãããªãé«éãªãã£ãŒãããã¯ãåŸãããšãå¯èœã§ãã äŸãã°ã以äžã®ãããªæ£åžžç³»ã®ãã¹ããçšæããŠãã ããã /tests/use_cases/test_list_snapshot_summaries.py # æ£åžžç¯å²ã® limit ããã®ãŸãŸãªããžããªãžæž¡ãããããšãç¢ºèª class ListSnapshotSummariesUseCaseTest(unittest.TestCase): def test_execute_passes_limit_when_within_bounds(self) -> None: repository = FakeSnapshotQueryRepository() use_case = ListSnapshotSummariesUseCase(repository, max_read_limit=25) use_case.execute(limit=1, offset=0) self.assertEqual(1, repository.received_limit) use_case.execute(limit=25, offset=0) self.assertEqual(25, repository.received_limit) å€éšäŸåã¯ãã¹ãŠã¢ãã¯åãã ãã¡ã€ã³ã¢ãã«ã«æ£ããä»äºãä»»ããŠãããç¢ºèª åªå
: Infrastructureå±€ã®ãã¹ã 次ã«ã ã æè¡çãªæ£ãã ã ãæ€èšŒããŸãã SQLã®çµã¿ç«ãŠãæ£ããããLIMIT / OFFSETã®ãã©ã¡ãŒã¿é åºãééã£ãŠããªãããšãã£ããã©ã¡ãŒã¿é åºã DBããååŸããããŒã¿ããšã³ãã£ãã£ãžæ£ãããããã³ã°ã§ããŠãããã確èªã§ããŸãã /tests/frameworks&drivers/persistence/test_postgres_snapshot_query_repository.py # ãã£ã«ã¿ãªãã§ LIMIT/OFFSET ããã©ã¡ãŒã¿ã«å«ãŸãã`ORDER BY s.observed_at desc` ãå«ãŸããç¢ºèª class PostgresSnapshotQueryRepositoryTest(unittest.TestCase): def test_list_snapshot_summaries_without_filters(self) -> None: now = datetime(2026, 2, 24, tzinfo=UTC) cursor = _FakeCursor( rows=[ { "snapshot_id": UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"), "fetch_run_id": UUID("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"), "target_repo_id": UUID("cccccccc-cccc-cccc-cccc-cccccccccccc"), "owner": "owner", "name": "repo", "default_branch": "main", "requested_at": now, "observed_at": now, "head_sha": "deadbeef", "scan_scope": "full", "status": "succeeded", "trigger_type": "manual", } ] ) with patch( "application.backend.src.infrastructure.persistence.postgres_snapshot_query_repository.get_cursor", return_value=_CursorContext(cursor), ): repository = PostgresSnapshotQueryRepository() items = repository.list_snapshot_summaries( limit=10, offset=5, user_id=None, target_repo_id=None ) self.assertEqual([10, 5], cursor.executed_params) ã©ã®ãããªSQLïŒãŸãã¯ãªã¯ãšã¹ãïŒãçµã¿ç«ãŠããæ€èšŒ ã€ã³ã¿ãŒãã§ãŒã¹ã®ãåããšã倿ããç¢ºèª æçµ: çµ±åãã¹ãïŒE2E æåŸã«ãGitHub Actionsã§ã®ããŒã¿æœåºããDBä¿åããããŠã¢ããªã§ã®èªã¿åããŸã§ãã·ã¹ãã å
šäœã®ãããŒãæ¬çªã«è¿ãç°å¢ã§åäœãããã確ãããŸãã /tests/http/test_main_api.py # ãŠãŒã¹ã±ãŒã¹ã®ã¯ãªããã³ã°ãå©çšã㊠200 ãè¿ãããšãç¢ºèª class MainApiTest(unittest.TestCase): def test_list_snapshots_clamps_limit_and_returns_200(self) -> None: repository = _RecordingSnapshotRepository() cast(Any, main).list_snapshot_summaries_use_case = ListSnapshotSummariesUseCase( repository, max_read_limit=5 ) response = self.client.get("/snapshots?limit=999&offset=0") self.assertEqual(200, response.status_code) self.assertEqual(5, repository.received_limit) æ¬ç©ã®ããŒã¿ããŒã¹ãäœ¿çš ã»ããã¢ãããšã¯ãªãŒã³ã¢ããã®ä»çµã¿ãå¿
é å®è£
TDDïŒãã¹ãé§åéçºïŒ é²ãæ¹ TDDã¯ãåã«ãã°ãé²ãããã ãã§ãªãããèšèšãæŽç·Žãããããã®ããŒã«ãã§ãã 以äžã®3ãµã€ã¯ã«ã§é²ããŸãã Red: 倱æ 仿§ã®æå°ã±ãŒã¹ãæºããããã¹ãã³ãŒãããæžãããŸã å®è£
ããªãã®ã§åœç¶ãšã©ãŒã Green: æå ãã®ãã¹ããéãããã«ãæå°éã®ãå®è£
ã³ãŒãããæžãã Refactor: ãªãã¡ã¯ã¿ãªã³ã° ãã¹ããéãç¶æ
ãä¿ã£ããŸãŸãèšèšã«ãŒã«ãã«åãããŠã³ãŒãããããã«æŽçã ãµã€ã¯ã«ã®ã€ã¡ãŒãžå³ã§ãã äŸãã°ãã1åã®ååŸäžéã¯100ä»¶ãŸã§ããšããã«ãŒã«ã¯APIã®ä»æ§ã§ã¯ãªããã¡ã€ã³ã®èŠåã§ããããããŠãŒã¹ã±ãŒã¹å±€ã§ç¢ºå®ã«æ
ä¿ããããšã§ããã¬ãŒã ã¯ãŒã¯ã«äŸåããªãå
ç¢ãªããžãã¯ã宿ããŸãã ãå®è·µãã¹ãããã·ã§ããååŸãŠãŒã¹ã±ãŒã¹ ååèšèšãã snapshot ããŒãã«ã«å¯ŸããŠããã¹ãããã·ã§ãããååŸããããšãããŠãŒã¹ã±ãŒã¹ãTDDã§äœã£ãŠã¿ãŸãããã Use Caseså±€ã®ãŠããããã¹ã tests/use_cases/test_register_snapshot.py import unittest from uuid import UUID from datetime import UTC, datetime from application.backend.src.domain.entities.snapshot_summary import SnapshotSummary from application.backend.src.use_cases.list_snapshot_summaries import ListSnapshotSummariesUseCase class _FakeRepo: def __init__(self): self.called = False self.last_params = {} def list_snapshot_summaries(self, *, limit, offset, user_id, target_repo_id): self.called = True self.last_params = dict(limit=limit, offset=offset, user_id=user_id, target_repo_id=target_repo_id) now = datetime.now(UTC) return [SnapshotSummary( snapshot_id=UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"), fetch_run_id=UUID("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"), target_repo_id=UUID("cccccccc-cccc-cccc-cccc-cccccccccccc"), owner="owner", name="repo", default_branch="main", requested_at=now, observed_at=now, head_sha="deadbeef", scan_scope="full", status="succeeded", trigger_type="manual" )] class ListSnapshotSummariesUseCaseTDD(unittest.TestCase): def test_offset_negative_raises_and_repo_not_called(self): repo = _FakeRepo() uc = ListSnapshotSummariesUseCase(repo, max_read_limit=100) with self.assertRaises(ValueError): uc.execute(limit=10, offset=-1) self.assertFalse(repo.called) def test_limit_zero_becomes_one(self): repo = _FakeRepo() uc = ListSnapshotSummariesUseCase(repo, max_read_limit=100) uc.execute(limit=0, offset=0) self.assertEqual(1, repo.last_params["limit"]) def test_limit_clamped_to_max(self): repo = _FakeRepo() uc = ListSnapshotSummariesUseCase(repo, max_read_limit=25) uc.execute(limit=999, offset=0) self.assertEqual(25, repo.last_params["limit"]) def test_passes_filters_and_offset(self): repo = _FakeRepo() uc = ListSnapshotSummariesUseCase(repo, max_read_limit=100) user_id = UUID("11111111-2222-3333-4444-555555555555") target_repo_id = UUID("66666666-7777-8888-9999-aaaaaaaaaaaa") uc.execute(limit=10, offset=5, user_id=user_id, target_repo_id=target_repo_id) self.assertEqual(5, repo.last_params["offset"]) self.assertEqual(user_id, repo.last_params["user_id"]) self.assertEqual(target_repo_id, repo.last_params["target_repo_id"]) if __name__ == "__main__": unittest.main() ä»äžãã£ããæ³å®ãããšã©ãŒãã©ããããã¹ããèµ°ãããŠã¿ãããšããå§ãããŸãã Use Caseså±€ ãã¹ããæžããåŸã§ãåã㊠ListSnapshotSummariesUseCase ã¯ã©ã¹ãå®è£
ããŸãã python class ListSnapshotSummariesUseCase: def __init__(self, snapshot_query_repository: SnapshotQueryRepository, max_read_limit: int) -> None: self.snapshot_query_repository = snapshot_query_repository self.max_read_limit = max_read_limit def execute(self, *, limit: int, offset: int, user_id: UUID | None = None, target_repo_id: UUID | None = None) -> list[SnapshotSummary]: if offset < 0: raise ValueError("offset must be greater than or equal to 0") safe_limit = max(1, min(limit, self.max_read_limit)) return self.snapshot_query_repository.list_snapshot_summaries( limit=safe_limit, offset=offset, user_id=user_id, target_repo_id=target_repo_id ) ãã€ããŒãæ€èšŒãUse Caseså±€ã§èšè¿° Interface Adapterså±€ã®ããªããŒã·ã§ã³ã ãã«äŸåããããã¡ã€ã³ã«ãŒã«ãšããŠãã¹ãå¯èœã« ãã®ããã«1ã€ãã¹ããäœæããã1ã€å®è£
ãããµã€ã¯ã«ãäœããšãæå³ããã³ãŒãå®è£
ã«ãªããããã§ãã ãããžã§ã¯ãã«ãã£ãŠã1ãã¡ã€ã«ããšã«ãããå
šäœã®ãã¹ããå
ã«äœã£ãŠããŸããã¯èª¿æŽãã¹ãã ãšæããŸããã Interface Adapterså±€ ãŠãŒã¹ã±ãŒã¹ã®å®è£
åŸãã¢ãã¯åããŠããDBãžã®å
·äœçãªæ¥ç¶åŠçã¯ãåŸããã€ã³ã¿ãŒãã§ãŒã¹ã®äžèº«ãšããŠå·®ãæ¿ããŸãã python class PostgresSnapshotQueryRepository(SnapshotQueryRepository): def list_snapshot_summaries(self, *, limit: int, offset: int, user_id: UUID | None, target_repo_id: UUID | None) -> list[SnapshotSummary]: conditions: list[sql.Composable] = [] params: list[object] = [] if user_id is not None: conditions.append(sql.SQL("fr.user_id = %s")) params.append(user_id) if target_repo_id is not None: conditions.append(sql.SQL("fr.target_repo_id = %s")) params.append(target_repo_id) # where_clause çµã¿ç«ãŠãlimit/offset ã params ã«è¿œå ããŠå®è¡ Infrastructureå±€ ãããã«ããã€ã°ã¬ãŒã·ã§ã³ã¯ä»¥äžã®ããã«è¿œå ããŸãã create table history_event ( history_event_id uuid primary key default gen_random_uuid(), user_id uuid not null references app_user(user_id) on delete restrict, fetch_run_id uuid references fetch_run(fetch_run_id) on delete set null, event_type text not null, happened_at timestamptz not null default now(), summary text not null ); create table operation_log ( operation_log_id uuid primary key default gen_random_uuid(), actor_user_id uuid references app_user(user_id) on delete set null, action text not null, target_type text not null, target_id text, happened_at timestamptz not null default now(), result text not null check (result in ('success', 'failure')), error_code text, trace_id text not null ); ç£æ»ã»éçšèгç¹ã§ operation_log ãçšæ CI å®è¡ã®å€±æãæš©éãšã©ãŒããã远跡ã§ãã CopilotãæŽ»çšããããã³ããè¡ ã¯ãªãŒã³ã¢ãŒããã¯ãã£ã®éªšæ ŒãäœãéãAIã«ãŒãããã³ãŒããæžããããšãå±€ã®å¢çãææ§ã«ãªããã¡ã§ãã ããã§ããåããšãäŸåé¢ä¿ããæç€ºããããšã§ãäžçºã§å®çšçãªã³ãŒããåŸãããšãã§ããŸãã å®éã®ããã³ããäŸ ä»¥äžã®ããã«æç€ºãåºããšãCopilotã¯ããžãã¹ããžãã¯ãçŽç²ãªPythonã³ãŒããšããŠæœåºãããã¹ãããããæ§é ã§åºåããŠãããŸãã RepoScanner ã®ã¹ãããã·ã§ããäžèЧãè¿ã `ListSnapshotSummariesUseCase` ãå®è£
ããŠãã ããã å
¥å: limit, offset, optional filter åºå: SnapshotSummary ã®ãªã¹ããš total_count æ¢åãšã³ãã£ãã£: SnapshotSummary(id, repo_name, observed_at, summary) äŸå: SnapshotQueryRepository.list_snapshots(limit, offset, filter) -> (items, total) ãã¹ãïŒunittest ã¹ã¿ã€ã«ïŒãå
ã«ç€ºããŠãã ããã çæãããã³ãŒãïŒæç²ïŒ ããã³ããã®æç€ºéãããŸãã¯Use Caseså±€ã®ã³ãŒããçæãããŸãã APIã®æ€èšŒãšã¯å¥ã«ãããžãã¹ããžãã¯ãçŽç²ãªPythonã³ãŒããšããŠæœåºããããã¹ãããããæ§é ã«ãªããŸããã application/backend/src/use_cases/list_snapshot_summaries.py class ListSnapshotSummariesUseCase: def __init__(self, repo: SnapshotQueryRepository, max_read_limit: int = 100): self.repo = repo self.max_read_limit = max_read_limit def execute(self, limit: int, offset: int = 0): if limit <= 0: raise ValueError("limit must be > 0") if offset < 0: raise ValueError("offset must be >= 0") limit = min(limit, self.max_read_limit) return self.repo.list_snapshots(limit=limit, offset=offset) Interface Adapterså±€ã¯ãªã¯ãšã¹ããåãåãããŠãŒã¹ã±ãŒã¹ãåŒã³åºãã ãã®ãèããå±€ã«ãªããŸãã Python @app.get("/snapshots") def get_snapshots(limit: int = 20, offset: int = 0, repo=Depends(get_snapshot_repo)): items, total = ListSnapshotSummariesUseCase(repo).execute(limit=limit, offset=offset) return {"items": [i.to_dict() for i in items], "total": total} ããŸã: GitHub Actions ãšããœãŒã2ã§ã¯ãå®è¡ç°å¢ã®åé¢ãéçšã³ã¹ããããGitHub Actions ã«ããèªèšŒãšå®è¡ãã¡ã€ã³ã«æ®ããæ±ºæãããŸããã èªèšŒãŸããã®ã¯ãŒã¯ãããŒïŒæç²ïŒ permissions: contents: read pull-requests: read issues: read jobs: collect: steps: - name: Collect PR snapshot and persist env: GITHUB_TOKEN: ${{ github.token }} DATABASE_URL: ${{ secrets.DATABASE_URL }} permissions ãæç€ºãã github.token ïŒå®è¡æããŒã¯ã³ïŒãæŽ»çš å€éš DB ãžã®æžã蟌ã¿ã«ã¯ DATABASE_URL ãšãã£ã修食æžã¿ã®ã·ãŒã¯ã¬ãããå©çš ãŸãšã ä»åã¯ãããã¹ããå
ã«æžãããšã§ãèªç¶ãšäŸåãåãé¢ãèšèšã«ãªãããšãããTDDãšã¯ãªãŒã³ã¢ãŒããã¯ãã£ã®çžæ§ã®è¯ããäŒããã玹ä»ãããŸããã ãã©ããã¹ããããããèããTDDã¯èšèšãå°ãæåŒ·ã®ããŒã« ãŠãŒã¹ã±ãŒã¹ã¯å°ãããçŽç²ã«äœããå€éšã®ä»çµã¿ã«äŸåããªãçŽç²ãªããžãã¹ããžãã¯ãä¿ã€ AI ã«ã¯åãšäŸåé¢ä¿ãäŒããããšã§ãéçºå¹çãåçã«åäžãã ãšããœãŒã5ã§ã¯ãããã«äžæ©èžã¿èŸŒãã Copilotéçšè¡ã«ã€ããŠã話ãããŸãããæ¥œãã¿ã«ïŒ åè ãããããŠDTOãåŠã¶ ã芧ããã ãããããšãããããŸãïŒ ãã®æçš¿ã¯ã圹ã«ç«ã¡ãŸãããïŒ åœ¹ã«ç«ã£ã 圹ã«ç«ããªãã£ã 0人ããã®æçš¿ã¯åœ¹ã«ç«ã£ããšèšã£ãŠããŸãã The post Copilot à Clean Architecture | å®è£
ãšãã¹ã first appeared on SIOS Tech Lab .











