Azure Functions ã§ Domain-Driven Design (DDD) ã® Domain Event ãå®è£ ãã
ãã®è¨äºã¯ Sansan Advent Calendar 2017 ã® 25 æ¥ç®ãããªãã¡æçµæ¥ã®è¨äºã§ã *1ããã¿ããªãã®ã§åå ããæ°ã¯ãªãã£ãã®ã§ãããã¨ãã社å ãã¼çãªãã®ã§é ã£æã£ã¦ããã¨ããä¼ç¤¾ã®æ°ååã§ãã id:kanjirz50 ãç®ãè¼ããã¦ãæçµæ¥ã飾ã£ã¦ãã ããï¼ï¼ãã¨è¨ã£ã¦ããã®ã§ãã¾ãã®ãã¥ã¢ãã¹ãç©ããã¦äºã¤è¿äºã§å¿«è«¾ãã¦ãã¾ãã¾ãããã¨ããããã§ãã¿ã«å°ã£ã¦ãã¾ãã
ãªãã§ãããªã«ãã¿ã«å°ã£ã¦ãããã¨ããã¨ãå®ã¯ãã 2 å¹´å¼±ç¨ãéçºè ã§ã¯ãªãã¦éçºããã¼ã¸ã£ã¼ããã£ã¦ããé¢ä¿ã§ãæè¡ãã¿ããªãã®ã§ãããã® 11 æããæ¹ãã¦éçºè ã«ãªã£ãã®ã§ãä»ã¯ãªãããªä¸ã§ãã¦ãæè¡ãã¿ã¯å¾ã ã«æºã¾ã£ã¦ããã¨æãã¾ãã Advent Calendar ã¨è¨ã£ã¦ãå¿ ãããæè¡ãã¿ã§ããå¿ è¦ã¯ãªãã¦ã Sansan Advent Calendar 2017 ã§ã¯ 5 æ¥ç®ã¨ã 20 æ¥ç®ãã¸ã§ããã§ã³ã¸ã¨ã社å 転è·ã¨ãããã¿ã«ãã¦ããã®ã§ãéçºããã¼ã¸ã£ã¼ã®ç«å ´ããéçºè ã«å¾©å¸°ããçµç·¯ã¨ããéçºããã¼ã¸ã£ã¼ã¨ãã¦çµé¨ãããã¨ãå¦ãã ãã¨ã¨ããæ¸ãã¨ããæã¯ãããã§ããã復帰å¾ã®è©±ãæ¸ãã«ã¯ã¾ã å°ãææå°æ©ãªäºæ ãããã®ã§ããããããã¿ã¯ä»å¾ã®ããã«åã£ã¦ãããã¨æãã¾ãã
ãã¦ã
çµè«
ããæ¸ãã¾ããã Azure Functions ã§ Domain Event ãå®è£ ããææ³ã®ãã¡ææ¡ã¯ä»¥ä¸ã
- Service Bus Topic / Subscription ã使ãæããã
- ç¾æç¹ã§ã¯ Subscription ãã Queue ã¸ã®è»¢éã使ãã¨è¯ããã
ã¯ãããã©ãã©ã¨æ¸ãã¦ããã¾ãã
Domain Event ã£ã¦ä½ï¼
çºæ³èªä½ã¯ GoF ã®ãã¶ã¤ã³ãã¿ã¼ã³ã«ããã Observer ãã¿ã¼ã³ã¨ä¸ç·ã ã¨æã£ã¦ã¾ãã Observer ãã¿ã¼ã³ããèªä½ã¯ã¤ã³ããã»ã¹ã§ã¯ã©ã¹å士ã®é¢é£ã§å®è£ ããããã¨ãå¤ãã§ããã Domain Event ã§ã¯ãã®è¡¨ç¾å½¢å¼ãæã«ããã»ã¹ããããã¯ã¼ã¯å¢çãã¾ããã¾ããä»åãã®è¨äºã§ãã¼ãã«ãã¦ããã®ã¯ããããã¯ã¼ã¯è¶ãã® Domain Event ã§ãã
Domain Event ã¯ã Domain-Driven Design (DDD) çéã§ã¯å®çªã¨ãªã£ã¦ããè¨è¨ææ³ã§ããããã¾ãé å¼µã£ã¦èª¬æããªãã¦ãä¸ã®ä¸ã«è¯ãããã¥ã¡ã³ããæº¢ãã¦ããã®ã§ãã¡ããåç §ããããã§ããã¨ã ãè¨ãã®ãããã¾ãã«ãçªãæ¾ãéããªæ°ãããã®ã§é©å½ã«ã°ã°ã£ã¦åºã¦ãããã¼ã¸ã¸ã®ãªã³ã¯ã§ãå¼µããã¨æã£ããããã¤ã¯ãã½ããã®æ©æ¢°ç¿»è¨³ãç¬ããã®ã§ãã®ãã¼ã¸ã¸ã®ãªã³ã¯ãå¼µãã¾ãã
ãã¡ã¤ã³ã®ã¤ãã³ãã§ããè¨è¨ã¨å®è£
話ã¯è±ç·ãã¾ããæè¿ã®ãã¤ã¯ãã½ããããã¯ã¢ã¼ããã¯ãã£è¨è¨ã«é¢ããããã¥ã¡ã³ãããããµãªåºãã¦ãã¦ãããã§ãããèªã¿ãããªãã§ããä¸è¨ã¯ Microservices ã«é¢ããä¸é£ã®ééç´ã®ããã¥ã¡ã³ãããã¤ã¯ããµã¼ãã¹ã§ DDD 㨠CQRS ãã¿ã¼ã³ã使ã£ã¦ãã¸ãã¹ã®è¤éãã«åãçµããã® 1 ãã¼ã¸ã§ããæ¨ä»ã®æµè¡ããããããã§ããã¨ãããããçãè¾¼ãã§ãã¦ã¿ã¤ãã«ã ãã§ãè ¹ãã£ã±ããæã¯ Web ã§èªããã®ã¯ãã¤ã¯ãã½ãããã¯ããã¸ã¼ã®ä½¿ãæ¹çãªããã¥ã¡ã³ãã«çã¾ã£ã¦ããå°è±¡ãããã®ã§ãããè¯ãæä»£ã«ãªãã¾ããããã¨ã¯æ©æ¢°ç¿»è¨³ããæ´ç·´ãããã°è¨ããã¨ãªãã§ããã
ã㦠Domain Event ã§ãããå®ã¯ DDD ã®åèã§ããèå ¸ãã Eric Evans ã® DDD æ¬ã§ã¯ãããåºã¦ãã¦ããªãææ³ã§ã

Domain-Driven Design: Tackling Complexity in the Heart of Software
- ä½è : Eric Evans
- åºç社/ã¡ã¼ã«ã¼: Addison-Wesley Professional
- çºå£²æ¥: 2003/08/22
- ã¡ãã£ã¢: ãã¼ãã«ãã¼
- è³¼å ¥: 4人 ã¯ãªãã¯: 113å
- ãã®ååãå«ãããã° (89ä»¶) ãè¦ã
ãããã 10 å¹´ãå¾ã«åºã Vaughn Vernon ã«ããåè IDDD ã§ç¤ºããããã®ã ã£ãæ°ããã¾ãã

Implementing Domain-Driven Design (English Edition)
- ä½è : Vaughn Vernon
- åºç社/ã¡ã¼ã«ã¼: Addison-Wesley Professional
- çºå£²æ¥: 2013/02/06
- ã¡ãã£ã¢: Kindleç
- ãã®ååãå«ãããã°ãè¦ã
ãã®è¾ºãã®çµç·¯ã¯è©³ããç¥ãã¾ãããããã£ã¨ Vaughn Vernon èªèº«ãä¸ããèãã ãããã®ã¨ããããã§ããªãã¦ã DDD Community ã® 10 å¹´éã®ç©ã¿éãã®ãªãã§ãã©ããã§çã¾ãããã®ãªã®ã§ã¯ãªããã¨æ³åãã¦ãã¾ãã
ãªã Domain Eventï¼
ç§ã®çè§£ã§ã¯ãä¾åé¢ä¿ãé転ããããããã§ããåºãæå³ã§ DIP (Dependency Inversion Principle: ä¾åæ§é転ã®åå)ãã¨è¨ã£ãã DIP ãæ¡å¤§è§£éãããããããã¾ããããç§ã¯æ¬è³ªçã«ä¼¼éã£ã¦ããããã«æãã¦ãã¾ãã以åã« AWS Dev Day Tokyo 2017 ã¨ããã¨ããã§ç»å£ããæã®èªåã®è³æã«ã¡ããã©ããå³ããã£ãã®ã§åãè¾¼ã¿ã¾ãã
å·¦å´ã®å³ (NOT Domain Event) ã§è¨ãã¨ããã® Operation A ã¯æ¬¡ã« Operation B ãå®è¡ãããã¹ãã ã¨ç¥ã£ã¦ãã¾ãããã㯠A ã B ã«ä¾åãã¦ããç¶æ ã§ãã Domain Event ã«ãã£ã¦ A ã¯èªèº«ã®å®äºå¾ã« B ã C ãå®è¡ãããã¹ãã ã¨ããç¥èãæããªãã¦è¯ããªãã¾ãã代ããã« A ã®è²¬åã¯ãèªèº«ã®å®äºã Event Aggregator ã«ç¥ããããã¨ã ãã«ãªãã¾ããããã§ A ãã B ã¸ã®ä¾åã¯ãªããªãã¾ããã代ããã« B ãã Domain Event ã¸ã®ä¾åãçºçãã¾ããã Domain Event ã®æ½è±¡åº¦ãååã«é«ããã°åé¡ãªããã¨ãå¤ãã§ãã
ããããä¾ã ã¨ã伿¥åã SaaS ã«ãããè¨è¨ã ã¨ãã¦ã A ãä½ããã®éè¦ãªãã¼ã¿ã®æ´æ°å¦çã B ã管çè ã¦ã¼ã¶ã¼ã¸ã®ã¡ã¼ã«éä¿¡å¦çãã¨ãã§ããããã㯠DDD çã«ã¯ A ã Domain Layer ã§ã B ã Application Layer ã§èµ·ãã£ã¦ãããã¨ã§ããã Layer ãã¾ããä¾ã§ãã Domain Layer 㯠Application Layer ã«ä¾åãã¹ãã§ã¯ãªãã®ã§ã Domain Event ã«ããä¾åé¢ä¿ãé転ããã¾ãã
ä»åç§ã Domain Event ã使ã£ãã®ã¯ Layer ã¾ããã§ã¯ãªãã¦ã DDD çç¨èªã§è¨ãã° Bounded Context ãã¾ããã±ã¼ã¹ã§ããã Bounded Context ã¨ã¯ä½ãããã¨ãã話ããå§ããã¨ããªããªãã®ã§ã¾ãå¥ã®æ©ä¼ã«ãã¡ãã£ã¨è«¸äºæ ã«ããä»åã®è¨äºã®ãã¿ã«ãªã£ãå®ä¾ãæãã¥ããã®ã§ç¡çããå¥ã®ã¨ããããä¾ãæã£ã¦ãã¾ãã 伿¥åã SaaS ã§ãã Sansan ã§ã¯ã伿¥ã®ååºãã¼ã¿ã䏿¬ç®¡çã§ãã¾ããã¦ã¼ã¶ã¼ãååºãã¹ãã£ã³ãããç¬èªã®ãã¯ããã¸ã¼ãªãããé§ä½¿ãã¦ããã¹ããã¼ã¿åãããããã³ã伿¥åãã®ååº DB ã«èç©ããã¦ãããããªãã§ããããã¡ããååº DB æ§ç¯ä»¥å¤ã«ãæ©è½ãããã¾ããä¾ãã°ååºãããã¹ããã¼ã¿åãããã¿ã¤ãã³ã°ã§ãå½è©²ååºãã¼ã¿ããã¨ã« Salesforce ä¸ã«ãªã¼ããåå¼å 責任è ãã¤ããæ©è½ããã£ãããã¾ããåè¿°ã®ä¾ã® A 㨠B ã§è¨ãã°ã A ãååºã®ããã¹ããã¼ã¿åã B ã Salesforce ã¸ã®æ¸ãè¾¼ã¿ã§ãã ååºã®ããã¹ããã¼ã¿åå¦çã®æå¾ã« Salesforce ã¸ã®æ¸ãè¾¼ã¿å¦çã®èµ·åå¦çãããã®ã¯ãååºã®ããã¹ããã¼ã¿åæèããè¦ãã¨ä¸ç´ç©ã§ãããä¸é©åãªä¾åã§ããã¨ããããã§ãã Bounded Context ãã©ã®ç¨åº¦ã®ç²åº¦ã¨å®ç¾©ãããã«ãããã¾ããã A 㨠B ãç°ãªã Bounded Context ã«å±ããã±ã¼ã¹ã®å ¸åä¾ã§ããã Domain Event ã¯ãããã£ãä¸é©åãªä¾åé¢ä¿ã®è§£æ¶ã«å½¹ç«ã¡ã¾ãã
ã©ããã£ã¦ Domain Eventï¼
æ¯åããã¦ããã®ã§é§ãè¶³ã§ã Azure ã® PaaS ãçµã¿åããã¦å®è£ ããã«ã¯ã Pub/Sub ã¢ãã«ç¨ã«è¨è¨ããã¦ãã Service Bus ã® Topic/Subscription ã使ãã¾ãã Domain Event ã¨ã¯ Observer ãã¿ã¼ã³ã§ããã Observer ãã¿ã¼ã³ã¨ã¯ Pub/Subã¢ãã«ã§ããããã¾ãã«ããã¤ããåãã¨ããããã§ãã
Topic/Subscription 㨠Queue ãæºåãã
ãããªæãã§ Topic ãä½ã£ã¦ãã
Subscription ãã¶ãä¸ãã¾ãã Topic : Subscription = 1 : N ã«ã§ããã®ã§ãä¸ã®æ¹ã®ã¹ã©ã¤ãã®å³å´ã®å³ã®ããã«ã A ã®å¦çå¾ã« B 㨠C ãããªã¬ã¼ããããã¨ããã®ã容æã«å®ç¾ã§ãã¾ãã
ããã§ãã£ã¦ Queue ãä½ã£ã¦ããã¾ããæåã®çµè«ã§æ¸ããã Subscription ãã Queue ã¸ã®è»¢éã«ããã転éå ç¨ã§ãã
ããã¦ãããããã½ãªãã§ããã Subscription ãã Queue ã¸ã®è»¢éãè¨å®ãã¾ããããã 2017/12/26 ç¾å¨ Azure Portal ããã¯ã§ããªãããã§ã Service Bus Explorer çããè¨å®ãããã¨ã«ãªãã¾ãã
æ®å¿µãªãã ARM Template ã§ãè¨å®ã§ããªãããã§ãã ããã㯠ARM Template ã§ subscription ã® forwardTo
ããããã£ã¨ãã¦è¨å®ãã¾ã (é·ããå¤ãæ
å ±ã®ã¾ã¾æ¾ç½®ãã¦ãã¾ãã¾ããã 2018-11-19 ã«ä¿®æ£)ãä»ã«ã¯ Azure CLI ããã¨ããªãã§ããæãã§ããããã調ã¹ã¦ã¾ãããã
ã³ã¼ããæ¸ã
ããã¾ã§ã§ãããå¾ã¯ç°¡åã§ã Topic ã«ã¡ãã»ã¼ã¸ãæ¾ãè¾¼ãã¨ãã㨠Queue ããã¡ãã»ã¼ã¸ãèªã¿åºãã¨ãããå®è£ ããã®ã¿ã§ããæç´ã« C# ã§æ¸ãã¦ã¿ã¾ãã
ã¾ã㯠Topic ã«ã¡ãã»ã¼ã¸ãæ¾ãè¾¼ãã¨ããã Visual Studio ã§ HTTP Trigger ã® Function ãæ°è¦ä½æããã ãã®ã³ã¼ãã«ãæ¾ãè¾¼ãã³ã¼ããè¶³ããã ãã®ãã®ã§ããè¶³ããã®ã¯æ¥æ¬èªã§ã³ã¡ã³ãããç®æã§ããæ¾ãè¾¼ãåã¯ä½ã§ãè¯ãã®ã§ãããããã§ã¯ string ã«ãã¦ãã¾ããä»»æã®åã JSON ã«ã·ãªã¢ã©ã¤ãºãã¦æ¾ãè¾¼ãã ãããã°è¯ãã®ã§ãªãã§ããããã
using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Azure.WebJobs.Host; using Microsoft.Azure.WebJobs.ServiceBus; namespace FunctionApp2 { public static class Function1 { [FunctionName("Function1")] public static async Task<HttpResponseMessage> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequestMessage req, // Topic ã«ã¡ãã»ã¼ã¸ãæ¾ãè¾¼ãããã®å¼æ° [ServiceBus("function1-completed", EntityType = EntityType.Topic, Connection = "ServiceBusConnectionString")] IAsyncCollector<string> asyncCollector, TraceWriter log) { log.Info("C# HTTP trigger function processed a request."); // parse query parameter string name = req.GetQueryNameValuePairs() .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0) .Value; // Get request body dynamic data = await req.Content.ReadAsAsync<object>(); // Set name to query string or body data name = name ?? data?.name; // ããã§æ¾ãè¾¼ã await asyncCollector.AddAsync(name); return name == null ? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body") : req.CreateResponse(HttpStatusCode.OK, "Hello " + name); } } }
ããã¦åãåãå´ã®ã³ã¼ãã¯ãããªæãã ServieBusTrigger
ã®ã¨ããã Subscription ã§ãªã Queue ããã¤ã³ããã¦ããã¨ããããã¤ã³ãã§ããä¸ã®æ¹ã§ Subscription ãã Queue ã¸ã®è»¢éãè¨å®ãã¦ããã®ã§ãã¡ããã¨ã¡ãã»ã¼ã¸ããã£ã¦ãã¾ãã
using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Host; using Microsoft.ServiceBus.Messaging; namespace AnotherBoundedContext { public static class Subscriber1 { [FunctionName("Subscriber1")] public static void Run( [ServiceBusTrigger("subscriber1", AccessRights.Listen, Connection = "ServiceBusConnectionString")]string name, TraceWriter log) { log.Info($"C# ServiceBus queue trigger function processed message: {name}"); } } }
ãªã Subscription ãã Queue ã«è»¢éããã
æå¾ã«ããªã Subscription ãç´æ¥ãã¤ã³ãããã䏿éå ã㦠Queue ã¸ã®è»¢éãè¨å®ãããã§ããä»åãã®å¤æãããç´æ¥ã®çç±ã¯ã Functions 㨠Service Bus ã®é£æºã«ããã¦ã 2017/12/26 ç¾å¨ã§ã¯ã¾ã Exponential Backoff çãªãã®ãå®è£ ããã¦ããªããã¨ã§ãã
ä¾ãã°ä¸ã®ä¾ã§ Subscriber1 ãå¤é¨ãµã¼ãã¹ã® Web API å¼ã³åºããå«ãã¨ãã¾ããããã¦å¤é¨ãµã¼ãã¹ã (REST ã ã¨ãã¦) 500 ç³»ã®ã¬ã¹ãã³ã¹ã³ã¼ããè¿ãã¦ããã¨ãã¾ãã 500 ç³»ã®ã¬ã¹ãã³ã¹ã³ã¼ãã¯ä¸æç㪠(transient) ã¨ã©ã¼ã表ãã¾ããããå°ãéãç½®ãã¦åããªã¯ã¨ã¹ããå度éãã¨ããã®æã«ã¯æåããå¯è½æ§ãããã¾ãããã®ãªãã©ã¤ã¯ãã¤ã³ã¡ã¢ãªã§ã«ã¼ãçã«å®è¡ãããã¨ãã§ãã¾ããããã£ãã Service Bus ãå©ç¨ãã¦ãããªãã Service Bus èªèº«ãåãã¦ããã¡ãã»ã¼ã¸ã®åå®è¡æ©æ§ã«ä¹ã£ãããã¨ãã§ãã¾ããä¸è¬çã«ã¡ãã»ã¼ã¸ã³ã°ã使ãè¨è¨ã«ããã¦ã¯ã 1 ã¤ã®ã¡ãã»ã¼ã¸ã®å¦çã¯é·ããªããããªãããã«ããæ¹ãè¯ãã¨ããã¦ãã¾ãããã«ã¼ãçã«ãªãã©ã¤å¦çãããã¨ããã«åãã¦ãã¾ãã®ã§ãä»åã¯ã¡ãã»ã¼ã¸ã®åå®è¡æ©æ§ã«ä¹ã£ãã£ã¦ãªãã©ã¤ãããã¨ã«ãã¾ãã
ãã ãä½ã®å¶å¾¡ãããªãã¨å ´åã«ãã£ã¦ã¯ 1 åç®ã®ã¨ã©ã¼ã®ç´å¾ã« 2 åç®ã®ã¡ãã»ã¼ã¸å¦çãèµ°ãå¯è½æ§ãããã¾ã (Queue ã«ä»ã®ã¡ãã»ã¼ã¸ãæºã¾ã£ã¦ããªãå ´åãããªãã¾ã)ãå¼ã³åºãå ã®å¤é¨ãµã¼ãã¹ã¯ã¨ã©ã¼ãè¿ããã®ã ããããããã«å°ãéãç½®ãã¦ãããæ¹ãè¯ãã¨æããã¾ããã¾ãã 1 åç®ã®å¤±æãã 2 åç®ã®å®è¡ã®ééãã 2 åç®ã®å¤±æãã 3 åç®ã®å®è¡ã®ééã¯ãé·ãããæ¹ãè¯ãã§ãããããã®ããã«ãªãã©ã¤åæ°ãå¢ãããã¨ã«å®è¡ééãé·ãããè¨è¨ææ³ã Exponential Backoff ã¨è¨ãã¾ãã
Functions 㨠Service Bus ã飿ºããå ´åãçµã¿è¾¼ã¿ã® Exponential Backoff 㯠2017/12/26 æç¹ã§ãµãã¼ãããã¦ããããå®è£
ãããã¨ãããããããã Functions ã®å®è¡èªä½ã¯ã¨ã©ã¼çµäºã§ã¯ãªãæåã¨ããèªåã§ ScheduledEnqueueTimeUtc
ãè¨å®ãã BrokeredMessage
ãå
¥ãå£ã¨åã Queue ã«æ¾ãè¾¼ããããªã³ã¼ããæ¸ããã¨ã«ãªãã¾ãããããããã¨ããããã¨ããæã«ã Subscription ãç´æ¥ä½¿ã£ã¦ããã¨ä¸é½åãçãã¾ãã Subscription ãç´æ¥ä½¿ã£ã¦ããã¨ããªãã©ã¤ã®ããã« BrokeredMessage
ãæ¾ãè¾¼ãå
ã¯å½è©² Subscription ãçµã³ä»ãããã Topic ã«ãªãã¾ãããããã«æ¾ãè¾¼ãã ã¡ãã»ã¼ã¸ã¯å½è©² Topic ã«çµã³ä»ãå
¨ã¦ã® Subscription ã«é
ä¿¡ããã¾ããã¨ã©ã¼ã«ãªã£ãã®ã¯ãã®ãã¡ 1 ã¤ã® Subscription ã®å¦çã§ããããããã¯ä¸é©åã§ãã
ã¨ããããã§ããã£ãã Queue ã«è»¢éãããã¨ã§åãåããæ¥½ã«ãªãããã§ãã Dead Letter ãåºãå ´åãªããã«ããåããã¨ãè¨ãããã§ãã
ã¾ã¨ã
æ¸ãã¦ããã¡ã«ã¨ã¦ãé·ããªãã¾ãããããã®è¨äºã¯ãããªã¨ããã§ã使ã£ã¦ã¿ãã¨åããã¾ãã Functions ã¯ã¾ã ã¾ã çºå±é䏿ãããã¾ããä»å¾ã®é²åã«æå¾ ã§ããã
*1:æ¸ãã¦ãããã¡ã« 26 æ¥ã«ãªã£ã¦ãã¾ãã¾ããâ¦