DDD Community JPã®ã»ãã§CQRS/Event Sourcingã«ã¤ãã¦å°ãçãä¸ãã£ãã®ã§ãã©ãããè°è«ããããã¾ã¨ããã®ã¨åæã«è£è¶³ã追å ãã¾ãããã¡ãªã¿ã«ãEvent Sourcingã主é¡ã§ãããCQRSãåæã¨ãã¦é¢ä¿ãã¾ãããã®æ³å®ã§èªãã§ããã ããã°ã¨ã
çºç«¯ã¯ãã®ãã¤ã¼ãã
ããã¯Event Sourcingãããªãã¨ç¡çã§ãããç¶æ ã«åºã¥ãéããã¹ããªã¼ã å¦çã¯é£ããã§ã https://t.co/prB16GJC5q
— ãã¨ãã ã (@j5ik2o) 2020å¹´9æ14æ¥
åãå¼ç¨ãããã¤ã¼ãã¯æ¾å²¡ããã®è³ªåç®±ã«å¯¾ãããªã¢ã¯ã·ã§ã³ã§ãããã®è³ªåç®±ã«å¯ãããã質åã¯ä»¥ä¸ã
ã¹ããªã¼ã ãéãã¦ããéããã¾ã§ã®ãã¼ã¿ãå¤åããæ¯ã«UIã§è¡¨ç¤ºãããå ´åãDDDã§ã¯ã©ã®ããã«è¨è¨ãããè¯ãã§ããããï¼ DDDã®ãªãã¸ããªã¯1ã¤ã®ãªã¯ã¨ã¹ãã«å¯¾ãã¦1ã¤ã®ãªã¯ã¨ã¹ããè¿ãã¤ã¡ã¼ã¸ããããã¹ããªã¼ã ãã©ã®ãããªå½¢ã§æ±ã£ããè¯ãã®ãã¤ãããã«ãã¾ãã
ããªãæ½è±¡çãªè³ªåãªã®ã§ãããã確èªããªãã¨ããããªãã®ã§ãããCQRS/Event Sourcingãªãã©ã解決ãããèãã¦ã¿ã¾ããããããã£ã説æãæ½è±¡çã§ãããã«ããã¨ãã声ãããèãã®ã§ãå ·ä½çãªã³ã¼ã*1ã交ãã説æã«ãªã£ã¦ãã¾ãã 注æäºé ã¨ãã¦ã¯ãCQRS/Event Sourcingã«ã¯å®è£ ã³ã¹ããæããã¾ã*2ãããã»ã©ã®ã³ã¹ããæãã価å¤ãããã·ã¹ãã ãªã®ãããèãã¦ãã ããã¨ããã®ã¯åæã¨ãã¦ããã¾ãã®ã§ããã®ç¹ã¯æ³¨æãã¦èªãã§ãã ããã
CRUDã¯ææ°ç¶æ ã«åºã¥ãè¨è¨ã¹ã¿ã¤ã«
ãã¡ã¤ã³é§åè¨è¨ãåæã«APIãµã¼ããªã©ãè¨è¨ããå ´åãAPIã¯ãªã¯ã¨ã¹ãã»ã¬ã¹ãã³ã¹ã§CRUDãããã¨ãå¤ãã§ãããã®å ´åãã¢ããªã±ã¼ã·ã§ã³å é¨ã§ãªãã¸ããªã使ã£ã¦ãã¡ã¤ã³ãªãã¸ã§ã¯ããåãåºããã¸ãã¯ã«ãªãã®ã§ãªãã¸ããªæä½ããªã¯ã¨ã¹ãã»ã¬ã¹ãã³ã¹ã«å¯¾å¿ãã¾ããããã¦CRUDã¯ææ°ç¶æ ã«åºã¥ãè¨è¨ã¹ã¿ã¤ã«ã§ãã
ã·ã§ããã³ã°ã«ã¼ã*3ã«ååã追å ããã¦ã¼ã¹ã±ã¼ã¹ã§èããã¨ä»¥ä¸ã®ããã«ãªãã¾ãããã¸ãã¹ã«ã¼ã«ãå®ãã®ããã¡ã¤ã³ãªãã¸ã§ã¯ãã§éè¦ãªå½¹å²ãæã£ã¦ãã¾ãã
class AddCartItemUseCase(cartRepository: CartRepository) { def execute(cartId: CartId, itemId: ItemId, num: ItemNum): Unit = { // ææ°ã®éç´(ã°ãã¼ãã«ãªã¨ã³ãã£ãã£)ãã¹ãã¬ã¼ã¸ããåå¾ãã val cart = cartRepository.findById(cartId) // ãã¸ãã¯å®è¡: äºç®è¶ éãªãã«ã¼ããªãã¸ã§ã¯ããååã®è¿½å ãæå¦ããï¼ val newCart = cart.addItem(itemId, num) // æ´æ°ãããææ°ç¶æ ãã¹ãã¬ã¼ã¸ã«ä¿åãã cartRepository.store(newCart) } }
ã«ã¼ãå ã®ååãåå¾ããã¦ã¼ã¹ã±ã¼ã¹ã¯ä»¥ä¸ã®ããã«ãªãã§ãããã
class GetCartItemsUseCase(cartRepository: CartRepository) { def execute(userAccountId: UserAccountId): Vector[CartItem] = { cartRepository.findByUserAccountId(userAccountId).map{ cart => cart.items } } }
質åã«ãã£ãããã«ããªã¯ã¨ã¹ãã«å¯¾ãã¦ã¬ã¹ãã³ã¹ãè¿ãã¹ã¿ã¤ã«ã«ãªã£ã¦ãã¾ããããããPULLå´ã®APIã§ããREST APIã§ããã°ããã§ä½ãåé¡ã§ãããã
ã¤ãã³ããPub/Subãã
ä»åã®è³ªåã¯ãã¹ããªã¼ã æ¥ç¶ãããã¨ãããã¨ã§ããæ¼ ç¶ã¨ãã¹ããªã¼ã ã§ãã¨ãã話ã§ãããæ±ããã¼ã¿ã¯ä½ã§ãããããä¸è¨ã®è³ªåã®å ´åã¯ããã¡ã¤ã³ã®ææ°ç¶æ ããªã¯ã¨ã¹ãã»ã¬ã¹ãã³ã¹åã§è¿ãã¾ãããã¹ããªã¼ã ã§ã¯ææ°ç¶æ ãè¿ãã®ã§ããããï¼ã»ã¨ãã©ã±ã¼ã¹ã§ã¯ããã§ã¯ãªãããã®ã¨ãèµ·ãã£ãåºæ¥äºã§ããã¤ãã³ããã¯ã©ã¤ã¢ã³ãã«è¿ããã¨ã«ãªãã¾ãã
ä¾ãã°ãä¸è¨ã®AddCartItemUseCase#executeãå®è¡ãããã¨ãåå追å ã¤ãã³ããçºçããã¹ããªã¼ã ã«æ¥ç¶ããã¯ã©ã¤ã¢ã³ãã«ãã®ã¤ãã³ããéç¥ãããã¨ããå ·åã«ãªãã¾ãããµã¼ãããã¯ã©ã¤ã¢ã³ãã¸ãã¼ã¿ãPUSHãããå½¢ã«ãªããé常ã¯ã¹ããªã¼ã ã®æ¥ç¶ã¯æ°¸ç¶çã«ãªãã¾ãã
ãµã¼ãå´ã®ã¨ã³ããã¤ã³ãã®å®è£ ã§ã¯ã¤ãã³ãã®ãµãã¹ã¯ã©ã¤ããä½ãã¾ãããµãã¹ã¯ã©ã¤ãã§ã«ã¼ãã¤ãã³ããåä¿¡ããããã¹ããªã¼ã ã«æµãã ãã§ãã以ä¸ã¯akka-httpã§SSE(Server Sent Event)ãè¡ãå ´åã®ä¾ã§ããã¤ãã³ãã©ã®ãµã¼ãããã§ãåä¿¡ã§ããããã«ã¹ãã¬ã¼ã¸ããWrite/Readãããã¨ã«ãªãã¨æãã¾ãã
path("events" / LongNumber ) { cartId => get { complete { cartEventSubscriber.subscribe(cartId) .map(event => ServerSentEvent(event)) .keepAlive(1.second, () => ServerSentEvent.heartbeat) } } }
ãã¦ããã¡ã¤ã³ç¶æ ãå¤åããã¨ãã«ãã¤ãã³ãããããªãã·ã¥ããå®è£ ã¯ã©ããããããããã¦ã¼ã¹ã±ã¼ã¹ããä¸è¨ã§èª¬æããã¹ãã¬ã¼ã¸ã¸ã¤ãã³ããæ¸ãè¾¼ããã¨ã«ãªãã¨æãã¾ãã
// CRUDåæã®ã¦ã¼ã¹ã±ã¼ã¹å®è£ class AddCartItemUseCase(cartRepository: CartRepository) { def execute(cartId: CartId, itemId: ItemId, num: ItemNum): Unit = { val cart = cartRepository.findById(cartId) val newCart = cart.addItem(itemId, num) cartRepository.store(newCart) // (1) cartEventService.publish(CartItemAddedEvent(cartId, itemId, num)) // (2) } }
ããã¯ããããPub/Subã®ä»çµã¿ã§ããã§ãåéãããã¦ããæ¹ãå¤ãã®ã§ãããCQRS/Event Sourcingã¨ã¯ç´æ¥é¢ä¿ãªãã§ããæ··åããªãããã«ãã¾ããããä¸è¨ã¯ã¤ãã³ããéåä¿¡ãã¦ããã ãã§CQRS/Event Sourcingã§ã¯ããã¾ããã
ã¨ããã§ãä¸è¨ã³ã¼ãã®(1)ã®ç¶æ æ´æ°ã«å ã(2)ã®ã¤ãã³ãã®æ¸ãè¾¼ã¿ãå¢ããããã§ãããããã§éåæãè¦ãã¾ãã(1)ã¨(2)ã£ã¦ã¹ãã¬ã¼ã¸ãã¾ãéãã®ã§åä¸ãã©ã³ã¶ã¯ã·ã§ã³ã«ã§ãã¾ãããã¤ã¾ã2ç¸ã³ãããã¨ãããã«ã³ãããã¨ãããã¤ã§ããä¸æ´åãèµ·ããå ´åããªã«ããªãé¢åãªãã¤ã§ããä¾ãã°ã(1)ãã³ãããããããã¨ã«ã(2)ã失æãããã©ããªããã(1)ã®å®äºããæ¸ãè¾¼ã¿ãåé¤ãããã(2)ã®æ¸ãè¾¼ã¿ãæåããã¾ã§ãªãã©ã¤ãããã§ããè¤éãªãªã«ããªå¦çãèªåã§å®è£ ãããã¡ã«ãªãã¾ãã
ããã«ã³ãããã¯é¿ãã
ãªã®ã§ãããã«ã³ãããã¯ã§ããéãé¿ãã¾ãããã§ãã以ä¸ã¯å¤å ¸ã§ããèªããã¨ããå§ããã¾ãã
CQRS/Event Sourcingã®èæ¡è ã§ããGregãããããã«ã³ããããæ¨å¥¨ãã¦ããªãããã§ãï¼(èå³ããã°ãã¡ããåç §ãã ãã)
The two-phase commit can be expensive but for low latency systems there is a larger problem when dealing with this situation. Generally the queue itself is persistent so the event becomes written on disk twice in the two-phase commit, once to the Event Storage and once to the persistent queue. Given for most systems having dual writes is not that important but if you have low latency requirements it can become quite an expensive operation as it will also force seeks on the disk.
âäºç¸ã³ãããã¯ã³ã¹ãããããã¾ãããä½ã¬ã¤ãã³ã·ã®ã·ã¹ãã ã§ã¯ããã®ç¶æ³ãæ±ãéã«ã¯ãã大ããªåé¡ãããã¾ããä¸è¬çã«ããã¥ã¼èªä½ã¯æ°¸ç¶çãªã®ã§ãã¤ãã³ãã¯äºæ®µéã®ã³ãããã§äºåº¦ãã£ã¹ã¯ä¸ã«æ¸ãè¾¼ã¾ãã¾ããã»ã¨ãã©ã®ã·ã¹ãã ã§ã¯ãäºéæ¸ãè¾¼ã¿ãè¡ããã¨ã¯ããã»ã©éè¦ã§ã¯ããã¾ããããããä½ã¬ã¤ãã³ã·ã®è¦ä»¶ãããå ´åã«ã¯ã ãã£ã¹ã¯ä¸ã§ã®ã·ã¼ã¯ãå¼·å¶çã«è¡ããã¨ã«ãªããããé常ã«é«ä¾¡ãªæä½ã«ãªãå¯è½æ§ãããã¾ããâ
The database would insure that the values of sequence number would be unique and incrementing, this can be easily done using an auto-incrementing type. Because the values are unique and incrementing a secondary process can chase the Events table, publishing the events off to their queue. The chasing process would simply have to store the value of the sequence number of the last event it had processed, it could even update this value with a two-phase commit bringing the update and the publish to the queue into the same transaction.
âãã¼ã¿ãã¼ã¹ã¯ãã·ã¼ã±ã³ã¹çªå·ã®å¤ãä¸æã§ã¤ã³ã¯ãªã¡ã³ãããããã¨ãä¿è¨¼ãã¾ãããããã¯AUTO INCREMENTåã使ç¨ãã¦ç°¡åã«è¡ããã¨ãã§ãã¾ããå¤ãä¸æã§ã¤ã³ã¯ãªã¡ã³ãããã¦ããã®ã§ãã»ã«ã³ããªããã»ã¹ã¯ã¤ãã³ããã¼ãã«ã追ãããããã¨ãã§ããã¤ãã³ãããã¥ã¼ã«å ¬éãããã¨ãã§ãã¾ãã追ããããããã»ã¹ã¯ãåã«æå¾ã«å¦çããã¤ãã³ãã®ã·ã¼ã±ã³ã¹çªå·ã®å¤ãä¿åãã¦ããå¿ è¦ãããã¾ãã ãã®å¤ãæ´æ°ããã«ã¯ãäºæ®µéã®ã³ãããã§æ´æ°ã¨ãã¥ã¼ã¸ã®å ¬éãåããã©ã³ã¶ã¯ã·ã§ã³ã«ãããã¨ãã§ãã¾ããâ
ã§ã¯ã©ãããããããã(1)ã¨(2)ã¯åæã«äºã¤å¦çããã«ã(2)ã®ã¤ãã³ãã ããæ¸ãè¾¼ãããã«ãã¾ãã(1)ã®ç¶æ ã¯(2)ã®ã¤ãã³ããåºã«å¥ã®ããã»ã¹ã§ä½ãåºãã¾ãããããCQRS/Event Sourcingã§ããã¹ã¦ã®ç¶æ ã¯ã¤ãã³ãããå°åºã§ããããã«ãã¾ãããã£ã¨æ¬é¡ã
ãããã以ä¸ã®(0)ãã(1)ã®å¦çã¯ãææ°ç¶æ ã«åºã¥ãã¦ããã®ã§ãã®ã¾ã¾ã§ã¯ä½¿ãã¾ãããè¨è¨ãè¦ç´ãå¿ è¦ãããã¾ãã
def addCartItem(cartId: CartId, itemId: ItemId, num: ItemNum): Unit = { // val cart = cartRepository.findById(cartId) // (0) // val newCart = cart.addItem(itemId, num) // cartRepository.store(newCart) // (1) // ã¤ãã³ãã®æ¸ãè¾¼ã¿ã¯ããã§ããããâã®å¦çãã©ããããããã cartEventService.publish(ItemAdded(cartId, itemId, num)) // (2) }
Event Sourcingã§ããã°ã©ãã³ã°ã¢ãã«ãã©ãå¤ããã
åè¿°ãã¾ããããCQRS/Event Sourcingã¯ãç°¡åã«ããã¨ã¤ãã³ãããç¶æ ãå°åºããã¢ã¼ããã¯ãã£ã§ããçµµã§èª¬æããã»ããããããããã®ã§çµè«ã¨ãªãå³ã¯ä»¥ä¸ãã©ã¼ãããããè¤éã ãªããã¨æãã¾ããããåç´ãªCRUDããã¯ãããé£ãããªãã¾ããâ¦ããã®ä»£ããèé害æ§ãã¹ã±ã¼ã©ããªãã£ãåä¸ããã®ã§ãâ¦ãä¸ã¤ã®ã¢ããªã±ã¼ã·ã§ã³ã®ããã«è¦ãã¾ãããã³ãã³ãå´ã¨ã¯ã¨ãªå´ã¯åé¢ãã¦ãããã¤ãããã¨ãå¯è½ã§ãã
ã¤ãã³ããããã¡ã¤ã³ãªãã¸ã§ã¯ãããªãã¬ã¤ãã
ã¸ã£ã¼ãã«DBã¨å¼ã°ãããã¼ã¿ãã¼ã¹ã«ãã«ã¼ããªãã¸ã§ã¯ããçºè¡ããã«ã¼ãã®ã¤ãã³ã(以ä¸ãã«ã¼ãã¤ãã³ã)ãæ°¸ç¶åããã¾ãããã®ã¤ãã³ãã使ã£ã¦ã³ãã³ãå´ã«ãã¡ã¤ã³ãªãã¸ã§ã¯ãããã¯ã¨ãªå´ã«ãªã¼ãã¢ãã«ãæ§ç¯ãã¾ãã(ãªã¼ãã¢ãã«ã®å½¢å¼ã¯ä½ã§ãOKã§ãããªã¼ãDBã¯ã¸ã£ã¼ãã«DBã¨ç©ççã«åãããã©ããã¯ããã§ã¯åãã¾ãããã¾ãæ¦å¿µçã«ç解ããã»ããããã§ããã)ãã¤ã¾ãç¶æ ã¨ããããã®ã2種é¡ããããã§ãããªã¼ãã¢ãã«ã¯å¾è¿°ãã¾ãããã¾ãã¯ãã¡ã¤ã³ãªãã¸ã§ã¯ãããã
以ä¸ã®ããã«ãæ°¸ç¶ãããè¤æ°ã®ã¤ãã³ããããã°ãææ°ã®ã«ã¼ããªãã¸ã§ã¯ããä½ã(ãªãã¬ã¤)ãã¨ãå¯è½ã§ãã(1)ã®é¨åã§ã¤ãã³ããèªã¿è¾¼ã¿ã(2)ã§ææ°ã®ã«ã¼ããªãã¸ã§ã¯ããä½ãåºãã¾ãã
def addCartItem(cartId: CartId, itemId: ItemId, num: ItemNum): Unit = { val allEvents = cartEventService.getAllEventsById(cartId) // (1) val cart = replayFunction(allEvents) // (2) val itemAdded = cart.addItem(itemId, num) cartEventService.publish(itemAdded) }
ã¹ãããã·ã§ããã§ãªãã¬ã¤æéãç縮ãã
åè¿°ã®ã³ã¼ãã¿ã¦ããã¯ã²ã©ãè¨è¨ã ã¨æã£ãã®ã§ã¯ãªãã§ããããããããã¾ããåãæåããæãã¾ããâ¦ã容æã«æ³åãã¤ãã¾ãããæ°¸ç¶åãããã«ã¼ãã¤ãã³ãã®ä»¶æ°ã«æ¯ä¾ãã¦ãªãã¬ã¤ã®ããã©ã¼ãã³ã¹ãæªåãã¾ãããªã®ã§ã対çã¨ãã¦ãæ°¸ç¶åããã¤ãã³ãN件ãã¨ã«ã«ã¼ããªãã¸ã§ã¯ãã®ç¶æ ãã¹ãããã·ã§ããDBã«ä¿åãã¦ãããã«ã¼ããªãã¸ã§ã¯ãããªãã¬ã¤ããéã«ãææ°ã¹ãããã·ã§ããï¼ãã以éã«çºçããå·®åã¤ãã³ããèªã¿è¾¼ãã§ãé«éã«ãªãã¬ã¤ãã¾ããã¤ã¾ããN件以ä¸ã¤ãã³ãããã£ã¦ããææ°ã®ã¹ãããã·ã§ããã¨æ大N - 1件åã®èªã¿è¾¼ã¿ã§æ¸ã¾ãããã¨ãã§ãã¾ããã¨ã¯ãããNãå°ããããã¨èªã¿è¾¼ãå·®åã¤ãã³ããå°ãªããªãä¸æ¹ã§ã¤ãã³ãæ¸ãè¾¼ã¿æã®ã¹ãããã·ã§ãããæ´æ°ããé »åº¦ãé«ããªãã¾ãã諸åã®å£ã§ã¯ããã¾ããããã©ã³ã¹ããã¾ãã¨ãå¿ è¦ãããã¾ããAkka(akka-persistence)ã§ããµãã¼ãããã¦ããæ©è½ã§S3ãDynamoDBãã¹ãããã·ã§ããDBã¨ãã¦ä½¿ããã¨ãã§ãã¾ãã
以ä¸ã®ãã¨ãè¸ã¾ããã¨ä»¥ä¸ã®ãããªã¤ã¡ã¼ã¸ã«ãªãã¾ããåæã¨ãã¦ã¤ãã³ããã¹ãããã·ã§ããã«ã¯é£çª(seqNr)ãæ¯ã£ã¦ãããã®ã¨ãã¾ããã¾ããææ°ã®ã¹ãããã·ã§ããã¨ãã®ã¹ãããã·ã§ããã®seqNr以éã®å·®åã¤ãã³ããåå¾ãã¾ãããããã使ã£ã¦ææ°ã®ã«ã¼ããªãã¸ã§ã¯ãããªãã¬ã¤ãã¾ããããã¦ãªãã¸ã§ã¯ããªãã¸ã§ã¯ãã®æ¯ãèããå®è¡ãã¾ããæ°ããã«ã¼ããªãã¸ã§ã¯ããã¤ãã³ããå¾ã¾ããã¤ãã³ãã¯ã¨ã³ãã¥ã¼ããã¾ãããseqNrã1000件ã§å²ãåããã¨ãã«æ°ããã«ã¼ããªãã¸ã§ã¯ããææ°ã®ã¹ãããã·ã§ããã¨ãã¦ä¿åãã¾ãã
def addCartItem(cartId: CartId, itemId: ItemId, num: ItemNum): Unit = { val snapshot = loadSnapshot(cartId) val events = cartEventService.getEventsByIdWithSeqNr(cartId, snapshot.seqNr + 1) val cart = replayFunction(snapshot, events) val (newCart, itemAdded) = cart.addItem(itemId, num) seqNr += 1 if (seqNr % 1000 == 0 ) { saveSnapshot(newCart, seqNr) } cartEventService.publish(itemAdded, seqNr) }
ãããããã¨ã§ãªãã¬ã¤æéãç縮åã§ãã¾ãããCRUDã®ã¨ãã¨æ¯è¼ããã¨å¤ãã®ãã¼ã¿ãèªã¿è¾¼ãã§ãããã¨ããããã¾ããâ¦ãããå°ãå¹ççã«ãªããªããã«ã¤ãã¦ã¯å¾ã»ã©èª¬æãã¾ãã
追è¨:
ãã¡ã¤ã³ãªãã¸ã§ã¯ãã¯ãã¡ãã¡ã¤ãã³ããããªãã¬ã¤ããã«ãã¯ã¨ãªå´ã®ãªã¼ãã¢ãã«ãã¦ã¼ã¹ã±ã¼ã¹ã§ãã¾ã使ããªãã®ï¼ã¨æãããããã¾ãããã以ä¸ã®åé¡ãããé£ããã§ãã
- éæ£è¦åããããªã¼ãã¢ãã«ã¯æ£è¦åããããã¡ã¤ã³ã¢ãã«ã§ã¯ãªãã®ã§ã代æ¿ã¯ããããé£ããã代æ¿ããã¨ãããDDDã§ã¯ãªããªããã
- C/Qãã¢ã¸ã¥ã¼ã«ã¨ãã¦åé¢ã§ããªããªããCã¨Qãåé¢ããã¦ããªããªããã¯ãCQRSã§ã¯ãªãï¼éãå¼ã³æ¹ãããããã§ãã
- ãªã¼ãDBã¸ã®æ¸ãè¾¼ã¿æéåãææ°ç¶æ ãåå¾ããæéãé 延ãããã¤ã¾ã常ã«éå»ã®ãã¼ã¿ãã¿ã¦ãããã¨ã«ãªã£ã¦ãã¾ãã¾ããããã¯è¨±å®¹ã§ããå ´åããããããè¦ä»¶ã«ããã¾ãã
ãªã¼ãã¢ãã«ã®æ§ç¯
ã³ãã³ãå´ã®ãã¡ã¤ã³ã¢ãã«ã®æ¦è¦ã¯ã¤ãããã¨æãã¾ãããã¯ã¨ãªå´ã®ãªã¼ãã¢ãã«ãèãã¦ã¿ã¾ãããã
ãã®ã¢ã¼ããã¯ãã£ãã¿ã¼ã³ã«ãã£ã¦ãã¤ãã³ããå¯ä¸ä¿¡é ¼ã§ãããã¼ã¿ã½ã¼ã¹ã¨ãªãããã®ã¤ãã³ããåºã«ã¯ã¨ãªå´ã®ãªã¼ãã¢ãã«ãæ§ç¯ãã¾ããã¤ãã³ãã¯ä¸å¤ã¨ããç¹å¾´ãæã¡ã¾ãããªã®ã§ããã¤ã§ãæ£ãããªã¼ãã¢ãã«ãä½ãåºãã¾ãã極端ãªè©±ããªã¼ãã¢ãã«ã®è¨è¨ããã¹ã£ã¦ãã¤ãã³ãããä½ãç´ãã¾ãããã¡ããããã¼ã¿æ´æ°ã³ã¹ãã¯ãããã¾ããâ¦ã
ã³ãã³ãã¨ã¯ã¨ãªã§è¦ä»¶ãé対称
ãããããªãã³ãã³ãã¨ã¯ã¨ãªã§ã¢ãã«ãåé¢ããã®ããçç±ã¯ä»¥ä¸ã®è¡¨ãåç §ãã¦ãã ãããã¤ã¾ãã¨ããè¦ä»¶ãéãããã§ãã人éãé²è¦§ãããããªã·ã¹ãã (SoEã¯ç¹ã«é¡è)ã ã¨ã¾ãã¯ã¨ãªå´ã¯éæ£è¦åãã¼ã¿ãæ±ãã¾ãã
- | ã³ãã³ã | ã¯ã¨ãª |
---|---|---|
ä¸è²«æ§/å¯ç¨æ§ | ãã©ã³ã¶ã¯ã·ã§ã³æ´åæ§ã使ãä¸è²«æ§ãéè¦ãã | çµææ´åã使ãå¯ç¨æ§ãéè¦ãã |
ãã¼ã¿æ§é | ãã©ã³ã¶ã¯ã·ã§ã³å¦çãè¡ãæ£è¦åããããã¼ã¿ãä¿åãããã¨ã好ã¾ãã(éç´åä½ãªã©) | éæ£è¦åãããã¼ã¿å½¢å¼ãåå¾ãããã¨ã好ã¾ãã(ã¯ã©ã¤ã³ãé½åã®ã¬ã¹ãã³ã¹ãªã©) |
ã¹ã±ã¼ã©ããªã㣠| å ¨ä½ã®ãªã¯ã¨ã¹ãæ¯çã¨ããå°æ°ã®ãã©ã³ã¶ã¯ã·ã§ã³å¦çããããªããå¿ ãããã¹ã±ã¼ã©ããªãã£ã¯éè¦ã§ã¯ãªã | å ¨ä½ã®ããªãã®ãªã¯ã¨ã¹ãæ¯çãå ããå¦çãè¡ããããã¯ã¨ãªå´ã¯ã¹ã±ã¼ã©ããªãã£ãéè¦ |
ä¾ãã°ã以ä¸ã®ãããªéç´(ã°ãã¼ãã«ãªã¨ã³ãã£ãã£)ãããå ´åã§ã
- Employee { id, name, deptId }
- Department { id, name }
ç»é¢ã¯ä»¥ä¸ã®ããã«éç´ãäºã¤çµåãããããªãã¼ã¿ãæ±ãã¾ããdeptId
ã ãã§ã¯ã©ã®é¨ç½²ãåãããªãããååãå¿
è¦ãªã®ã§ãã*4
- Employee { id, name, deptId, deptName }
ãã¡ã¤ã³ãªãã¸ã§ã¯ãã®æ§é ã«ã¤ã³ãã¯ããä¸ããã®ã¯ããã®ãããªæ£è¦ã¨éæ£è¦ã®æ§é çãªé対称æ§ã§ããèãã¦ã¿ãã¨ãããã¾ããããã©ã³ã¶ã¯ã·ã§ã³å¦çã¨æ¤ç´¢ã»ã¬ãã¼ãã両ç«ããã¢ãã«ã®å®ç¾ãç解ãé£ããã®ã§ãã
ãã¦ãã³ãã³ãã¨ã¯ã¨ãªãåé¢ããªãã¨å®è£ ä¸ã§ã©ããªåé¡ãèµ·ãããæ°ã«ãªãã¨ããã§ããã以ä¸ã®ãããªãã®ãããã¾ãã
- ãªãã¸ããªã®ã¯ã¨ãªã¡ã½ãããè¤éã«ãªã
- N+1ã¯ã¨ãªãçºçãããã
- ãã¡ã¤ã³ãªãã¸ã§ã¯ãããDTOã¸ã®å¤æãéå¹ç
ç°¡åã«ä»¥ä¸ã«èª¬æãã¾ãã
ã¯ã¨ãªè¦ä»¶ããªãã¸ããªã§æºãããã¨ãã¦ã¡ã½ãããè¤éã«ãªã
éç´(ã°ãã¼ãã«ãªã¨ã³ãã£ãã£)ã¯èå¥ã®ããã«IDãæã¤ãããIDããéç´æ¬ä½ãå¼ãå½ã¦ããã¨ãã§ãã¾ããããã¯ããæå³æ£å¼ãã§ãã
val employee = employeeRepository.findById(employeeId)
ããããéç´ã®ä»ã®å±æ§(ååãä»ã®IDãªã©)ã使ã£ã¦éç´(åä½ãããã¯è¤æ°)ãå¼ãå½ã¦ããå ´åãããã¾ããã»ã«ã³ããªã¤ã³ããã¯ã¹ã使ããããªéå¼ãã§ããã
以ä¸ã®ãããªã³ã¼ããæ¸ãããã¨ãããã¾ãããï¼ åã¯æ£ã æ¸ãã¦ãã¾ããã
val employees = employeeRepository
.findByDeptIdsWithEmpNamePatterns(deptIds, empNamePatterns)
ãã¡ã¤ã³ã®æ¯ãèããèµ·ããããã¨ãããããã¯ã©ã¤ã¢ã³ãã«ã¯ã¨ãªã®ã¬ã¹ãã³ã¹ãè¿ãããã«ãã使ã£ã¦ãã¾ãããããã¯æ¬å½ã«ãã¡ã¤ã³ã®è²¬åã§ããããããã¡ã¤ã³ã¯æ¯ãèããèµ·ãããã¨ã責åã¨æããã¨ããã¡ã¤ã³ã®æ¯ãèããä¼´ããªããã®ãããªãªãã¸ããªã¡ã½ããã¯é¢å¿ãåé¢ã§ãã¦ããªãã®ããããã¾ãããåè¿°ãã GetCartItemsUseCase
ã¯ã©ã¹ã®å®è£
ãæ¬å½ã«ãªãã¸ããªã®è²¬åã§ãããèããå¿
è¦ãããããã§ããæ¬å½ã«éç´ããã®ã¾ã¾è¿ããªãã°åé¡ãªãããããã¾ããã次ã®N+1ã¯ã¨ãªãå«ããããªåé¡ã«çºå±ããå¯è½æ§ãããå ´åã¯è¦æ³¨æã§ãã
ãªãã¸ããªã§ã¬ã¹ãã³ã¹ãçµã¿ç«ã¦ãã¨N+1ã¯ã¨ãªãçºçãããã
APIã®ã¬ã¹ãã³ã¹ã§ã¯éæ£è¦åãã¼ã¿ãè¿ããã¨ãã»ã¨ãã©ã§ããä¾ãã°ãããã«ã®äºç´æ å ±ä¸è¦§ãä½ãããã«ãäºç´éç´ã«é¢é£ããããã«éç´ã顧客éç´ã解決ããªããã°ãªããªããã¨ãããã¾ããããã§ããã¡ã¤ã³ã®æ¯ãèãã¯èµ·ãã¾ãããã¯ã¨ãªã ããã§ããããã¯æ¬å½ã«ãªãã¸ããªãããã¹ãä»äºã§ãããããåã¯éå»ã«DBAã«ç³ã訳ãªãã¨æããªããããããN+1ã大éã«çºçããã³ã¼ããæ¸ãã¦ãã¾ããâ¦ãããããã¬ã¹ãã³ã¹ã¨ãã¦è¡¨å½¢å¼ãæå¾ ãããªãSQLã¨RDBã«ããããã¹ãã§ã¯ãªãã§ããããã
reservationRepository.findByIds(ids).map { reservation => val hotel = hotelRepository.findById(reservation.hotelId) val customer = customerRepository.findById(reservation.customerId) new ReservationDto(reservation, hotel.name, customer.name) }
ãã¡ã¤ã³ãªãã¸ã§ã¯ãããDTOã¸ã®å¤æãéå¹ç
APIã®ã¬ã¹ãã³ã¹ã§ã¯éç´ã®ä¸é¨ã ããæ±ãããã¨ãããã¾ãã以ä¸ã¯æ£æçãªä¾ã§ãããUIã«ä½µãã¦é¡§å®¢åä¸è¦§ãè¿ãå¦çã§ãããªãã¸ããªã§å¾ãéç´ã®å¤§é¨åãæ¨ã¦ã¦ååã ãã®ãªã¹ããä½ãã¾ããã¯ã¨ãªè¦ä»¶ãæºããããã«å¤§é¨åã®ãªã¼ãçµæãæ¨ã¦ããããã¨ã«ãªãã¾ãã
val customerNames = customerRepository.findByIds(ids).map { customer =>
customer.name
}
ãã¡ãããæ¦å¿µã¢ãã«ã®ç²åº¦ã大ããã¨I/Oã³ã¹ããæ¯ä¾ãã¦å¤§ãããªãã®ã§ãã¾ãå ã«æ¦å¿µã®å¤§ããã«ç®ãåããã¹ãã§ãããã³ãã³ãå´ã®ãã¡ã¤ã³ã§ã¯ã¨ãªãé å¼µããããã¨ãããããã¨ã«ãªãã¾ãã
ã¤ãã³ããããªã¼ãã¢ãã«ãä½ã
RDBã«ãªã¼ãã¢ãã«ãæ§ç¯ãããªãã以ä¸ã®ããã«æ°¸ç¶åãããã¤ãã³ããåä¿¡ãã¦ãSQLãå®è¡ããã ãã§ã(å®éã«ã¯ãã®ãããªã¹ããªã¼ã å¦çã¯åçºã§ã¯ãªãå¾ã«çºçããã¯ã¼ã¯ãã¼ãã«åãã¦å¸¸æèµ·åãããå¿ è¦ãããã¾ã)ãããã¯ããã§ç°¡åã§ãããcartIdãæå®ãã¦èªã¿è¾¼ãã®ã§ãã¹ã±ã¼ã«ãã«ããã§ããå ·ä½çãªIDãæå®ããªãã®ã§ãããç¨åº¦ã®ä¸¦å度ãç¶æãæ°¸ç¶åãããã¤ãã³ããå ¨é åºã«èªã¿è¾¼ãå¿ è¦ãããã¾ããã¾ãããããã£ãåä½ãããã³ã³ã·ã¥ã¼ãã¯é害çºçæã®ãªãã©ã³ã¹ãå¿ è¦ã«ãªãã¾ããåã®ãå§ãã¯Kafkaã使ããã¨ã§ããããã¯ããã§ä¸æ¬ããã°è¨äºãæ¸ãããããã®ç¥èéãªã®ã§ãå¥ã®æ©ä¼ã«è§¦ãã¾ãã
cartEventSubscriber.subscribe(cartId).runWith(Sink.foreach{ case e: CartCreated => insertCartTable(e) case e: CartItemAdded => insertCartItemTable(e) case e: CartItemNumUpdated => updateCartItemTable(e) case e: CartItemRemoved => deleteCartItemTable(e) case e: CartRemoved => deleteCartTable(e) })
ã¾ã¨ããããã¦èª²é¡ã¯ã¾ã ãã
ã¨ãããã¨ã§ã ãããã®é°å²æ°ã¯ã¤ãããã®ã§ã¯ãªããã¨æãã¾ããã¨ã¯ããå®è£ ããããã§ã¯ã¾ã 課é¡ãããã¾ãã
ãã¨ãã°ãaddCartItem
å
é¨ã®å¦çãããå°ãå¹çåã§ããªããã®èª²é¡ã«ã¤ãã¦ã¯ãã¾ãã«åæ£ã·ã¹ãã ã®åé¡ã«ç´é¢ãã¾ãã
èãããã対çã¨ãã¦ã¯ãã«ã¼ããªãã¸ã§ã¯ããã¦ã¼ã¹ã±ã¼ã¹ã®ã¹ã³ã¼ãããå¤ã«åºãã¦ãã¯ã¼ã¯ãã¼ããããéã ãèµ·åããã¦ãã£ãã·ã¥ããã¦ããæ¹æ³ãããã¾ãããªã¯ã¨ã¹ããã¨ã«éç´ããªãã¬ã¤ããªããªãã®ã§ãªã¼ãã¼ãããã軽æ¸ãã¾ããããããã¯èªåã§å®è£ ããã®ã¯å¤§å¤ã§ãããã¨ãã°ãã¦ã§ããµã¼ããã¹ã±ã¼ã«ã¢ã¦ããã¦ããã¼ããã©ã³ãµããåãã«ã¼ãIDåãã®ç°ãªããªã¯ã¨ã¹ãããè¤æ°ãµã¼ãã«é£ãã ãã¨æ³å®ãã¦ã¿ã¦ãã ãããåä¸ã«ã¼ããªãã¸ã§ã¯ããå¥ã ã®ãµã¼ãã§ãªãã¬ã¤ãããç°ãªãã³ãã³ããåçããããã¨ã§ãåä¸IDãªã®ã«å¥ã ã®ç¶æ ãä½ãåºããã¦ãã¾ãã¾ããããæå³ã¹ããªãããã¬ã¤ã³ãªç¶æ ã«ãªãã¾ãã
ä¸å³ã¯ã¤ã¡ã¼ã¸ãªã®ã§å ¨ç¶æ£ç¢ºãããªãã¨ããåæã§ãããåè¿°ã®ãããªã¹ããªãããã¬ã¤ã³ã«ãªããªãããã«ããã«ã¯ãéç´(ã°ãã¼ãã«ãªã¨ã³ãã£ãã£)ã以ä¸ã®ããã«ã·ã£ã¼ãã£ã³ã°ãã¦ãã³ãã³ããã«ã¼ãã£ã³ã°ããã¨ããããã§ããã¤ã¾ããåä¸IDã®éç´ã¯ã¯ã©ã¹ã¿å ¨ä½ã§1åãããªãããã«é ç½®ããã°ããã§ãããâ¦ãã¯ã©ã¹ã¿ä¸ã§è³ã¿ããä¸ã¤ãããªãã®ã§ã¹ããªãããããããªãã¨ãã話ã§ããã¨ãããã¾ã§èãã¦ãã¡ããã¡ã大å¤ã ã¨æ³åã§ããã¨æãã¾ããããã«ãµã¼ããæ éããå ´åã«å¥ãµã¼ãã«éç´ããã¤ã¯ãªã¼ãããããªã©èªåã§å®è£ ããããªãâ¦ããªã®ã§ãAkkaãErlangãªã©åæ£ã·ã¹ãã ã®ãã¬ã¼ã ã¯ã¼ã¯ãªãã§ãããããã¨ã¯è¾ãã¾ãããâ¦ãAkkaã§ã¯Actorã¨ãã軽éããã»ã¹ãã¯ã©ã¹ã¿ä¸ã«åæ£ããããã¨ãã§ãã¾ããããããåºç¤ãªãã«ç¡è¶ã¯è¾ãããâ¦ã*5
èå³ãããã°ä»¥ä¸åç §ã
Akkaå®è·µãã¤ãã« ã¢ã¯ã¿ã¼ã¢ãã«ã«ãã並è¡ã»åæ£ã·ã¹ãã ã®å®ç¾
- ä½è :ã¬ã¤ã¢ã³ãã»ãã¹ãã³ãã¼ã°,ããã»ããã«ã¼,ããã»ã¦ã£ãªã¢ã ãº,ååº ç¥å¾,æ ¹æ¥ åè¼,éå± äºé
- çºå£²æ¥: 2017/12/13
- ã¡ãã£ã¢: Kindleç
æ£ç´ãã®åéã¯æ²¼æãããã¾ãããåèã«ãªãã°å¹¸ãã§ãã
*1:ã¨ã¯ãã£ã¦ãæ¦å¿µã説æããããã®çä¼¼ã³ã¼ãã ã¨æã£ã¦ãã ãã
*2:CQRS/Event Sourcingãã®ãã®ã¨ãããããåæ£ã·ã¹ãã ã«èµ·å ããé£ããã§ããâ¦
*3:ãã¼ã¿ãã¼ã¹ã«æ°¸ç¶åãããåæã®ã«ã¼ãã¨æã£ã¦ãã ãã
*4:ã·ã¹ãã ãã¦ã¼ã¹ã±ã¼ã¹ã®ã¢ã¯ã¿ã¼ãªãããããèæ ®ã¯ãããªãã¨æãã¾ãããSoEã ã¨ã©ããã¦ãããããè¦æ±ã¯çºçãã¾ã
*5:åæ£ã·ã¹ãã ã«é¢ä¿ãã話ã¯é端ã«é£ãããªããã¡ãããããããæ¸ããã¤ããã§ãããååã«ä¼ãããªãããããã¾ããâ¦ã容赦ãâ¦