پیاده سازی عملی و پروژه محور Domain Driven Design و CQRS

پرووید

دسته های مقالات

Domain Driven Design چیست؟

طراحی دامنه محور یا همان Domain Driven Design در واقع درک نیازهای واقعی کسب و کار مشتری
است. ما باید درباره دامنه های مختلف مانند بانکداری، مخابرات، زنجیره تامین، مراقبت های بهداشتی و… دانش داشته باشیم.
بنابراین در اینجا دامنه به معنی دانش کسب و کار درباره صنعت خاص است. به طور مشابه طراحی دامنه محور تمرکز بیشتری نسبت
به نیازهای کسب و کار دارد نه به تکنولوژی. برای شروع نوشتن یک سیستم، باید بدانیم که مشتری چه چیزی را مد نظر دارد، در
حین فاز اولیه هیچگاه درباره برنامه نویسی و معماری آن فکر نمیکنید. هدف اصلی شما درک تمام شرایط کسب و کار مشتری و
چگونگی مدل دامنه مورد نیاز او است.

بنابراین چیزی که ما به دنبال آن هستیم بحث با مشتری درباره نیازهای کسب و کار او است.
Domain Driven Design صرفا بر اساس این فرضیات برای ترسیم کردن نیازهای کسب و کار در مدل دامنه است. طراحی دامنه محور
همه چیز درباره چگونگی طراحی مدل دامنه شماست. به این معنا که هر کلاس دامنه باید یک رابطه مستقیم با  آنچه که در دامنه
کسب و کار است داشته باشد.

آموزش عملی و پروژه محور Domain Driven Design و CQRS سری آموزشی از وبسایت پرووید است که
در رابطه با Domain Driven Design و CQRS تنظیم شده است. پس از این دوره ی آموزشی می توانید از آموزش پیاده سازی اگلوی CQRS در سی شارپ و بسته ی آموزش ویدئویی معماری CQRS در نرم افزار مباحث تئوری و کاربردی
استفاده کنید.

CQRS چیست؟

به بیان ساده CQRS یک سبک معماری ( Architecture Style) است که با استفاده از آن عملیات
خواندن (Read Operations) و عملیات نوشتن (Write Operations) از هم تفکیک می شوند.

در معماری های سنتی و ساده اغلب از یک Data Model یکسان برای Query گرفتن و Update کردن
دیتابیس استفاده می شود. این موضوع برای نرم افزارهایی که عملیات ساده ی CRUD را انجام می دهند مناسب است. اما در نرم
افزارهای پیچیده تر این روش می تواند مشکل ساز شود. برای مثال، ممکن است عملیات خواندن از دیتابیس شامل انجام Query های
پیچیده، کار کردن با DTO ها (Data Transfer Object) و همچنین Object Mapping های پیچیده باشد. از طرفی عملیات نوشتن بر
روی دیتابیس می تواند شامل Validation های پیجیده و Business Logic های خاص باشد. بنابراین، اگر در چنین نرم افزارهایی
از یک Data Model یکسان برای انجام عملیات خواندن و عملیات نوشتن استفاده شود، Data Model به شدت پیجیده می شود.

در این آموزش نحوه استفاده از Edument CQRS Starter Kit برای ساختن
Domain Logic به صورت کامل به همراه تست ها را فرا خواهید گرفت. علاوه بر آن در رابطه با Read Model ها صحبت خواهیم کرد
و نحوه استفاده از Domain Logic و Read Model ها در یک برنامه ی نمونه ی ASP.NET MVC بررسی خواهیم کرد. از شما دعوت
می‌کنیم که تا پایان این آموزش با وب سایت پرووید همراه شوید و علاوه بر این، از آموزش اصول طراحی دامنه محور Domain Driven Design، آموزش کاربردی طراحی دامنه محور Domain Driven Design وب سایت استفاده کنید.

در این آموزش نحوه استفاده از Edument CQRS Starter Kit برای ساختن
Domain Logic به صورت کامل به همراه تست ها را فرا خواهید گرفت. علاوه بر آن در رابطه با Read Model ها صحبت خواهیم کرد
و نحوه استفاده از Domain Logic و Read Model ها در یک برنامه ی نمونه ی ASP.NET MVC بررسی خواهیم کرد. از شما دعوت
می‌کنیم که تا پایان این آموزش با وب سایت پرووید همراه شوید و علاوه بر این از آموزش Domain Driven Design وب سایت
استفاده کنید.

تعریف Domain مسئله

در این آموزش Domain ما یک کافی شاپ خواهد بود و تمرکز ما بر
روی مفهومی به نام تب است. وظیفه تب در کافی شاپ ردگیری کردن ملاقات های گروهی از افراد یا افراد تکی به کافی شاپ
است. زمانی که افراد به کافی شاپ می آیند و یک میز را انتخاب می‌کنند یک تب باز خواهد شد. پس از آن افراد می‌توانند
سفارش نوشیدنی یا غذا بدهند. نوشیدنی ها سریع سرو خواهند شد و این کار توسط کارکنان انجام می‌شود. اما غذا ها باید
ابتدا توسط سرآشپز پخته شوند. زمانی که سرآشپز غذا را آماده کرد آن غذا می‌تواند سرو شود.

در زمان ملاقات مشتریان از رستوران ما آنها می توانند
نوشیدنیها یا غذای اضافی سفارش بدهند. اگر آنها متوجه شوند که غذایی که سفارش داده اند اشتباه است می توانند سفارش
خود را کنسل کنند. البته این موضوع نمی تواند پس از سرو شدن غذا یا نوشیدنی و پذیرفته شدن آن توسط مشتری انجام شود.

در پایان کار مشتری‌ ها هزینه ی اقامت خود در کافی شاپ ما را
پرداخت می‌کنند و ممکن است حتی انعامی برای کارکنان در نظر بگیرند. در زمان پرداخت کل هزینه باید پرداخت شود. یک تب
که در آن آیتم هایی در حالت غیر سرو شده قرار دارند نمی تواند بسته شود. در واقع تمامی ایتم های درون یک تب یا باید
در حالت سرو شده و یا کنسل شده باشند.

تعریف Event ها

در سناریوی که در قسمت بالا معرفی شد افعال و اسم های
متنوعی مشخص شدند. زمانی که با روش Database Centric کار میکنیم گوش کردن با دقت به این سناریو ها و مشخص کردن
اسم ها و سپس نگاشت کردن آنها با جداول بانک های اطلاعاتی و ایجاد رابطه بین آنها بسیار مرسوم است. در چنین
شرایطی افعال اهمیت کمتری مسبت به اسم ها دارند. اما اگر طراحی را بر اساس Command ها و Event ها انجام بدهیم
تمرکز بر روی افعال قرار گرفته و اسم ها به عنوان موضوعات کم اهمیت تر در نظر گرفته می شوند. در طراحی Domain
تمرکز بر روی افعال قرار میگیرد و نه اسم ها. به عبارت دیگر عملیاتی که یک نرم افزار برای مشتری های خود انجام
می دهد بسیار اهمیت دارد و این عملیات در قالب افعال معنا می شوند.

با توجه به سناریوی که در قسمت بالا معرفی شد و تمرکز کردن
بر روی زبانی که در آن استفاده می شود ما با کلماتی برخورد می‌کنیم که اشاره به اتفاقاتی دارند که رخ می دهند.
این موضوع ما را به سمت اطلاعات جدید در Domain رهنمود می‌شود. ما این اتفاقات را به Event ها نگاشت می کنیم و
از آنجایی که Event ها در رابطه با اتفاقاتی هستند که در گذشته رخ داده اند آنها را در با زمان گذشته نامگذاری
میکنیم.

در ادامه لیستی از Event هایی که ممکن است در دومین ما مطرح شوند را می بینید:

  • TabOpened
  • DrinksOrdered
  • FoodOrdered
  • DrinksCancelled
  • FoodCancelled
  • DrinksServed
  • FoodPrepared
  • FoodServed
  • TabClosed

نکته بسیار مهمی که در رابطه با Event ها باید به آن اشاره
کنیم این است که Event ها با تمرکز کامل بر روی Domain تعریف می شوند.

برای مثال تفاوت بین سفارش دادن نوشیدنی و سفارش دادن غذا
در Domain بسیار مهم است بنابراین ما Event های متفاوتی برای آنها در نظر گرفته ایم.

تعریف Command ها

ماهیت Command ها نشانگر یک درخواست به Domain ما
میباشند. یکی از نقاط تمایز Event ها و Command ها در این است که یک Event در گذشته بدون شک اتفاق افتاده
است اما یک Command چنین نیست. به عبارتی یک Command ممکن است پذیرفته یا رد شود. یک Command پذیرفته شده
می‌تواند منجر به رخ دادن هیچ Event یا بیش از چندین Event بشود. این Event ها حقایق جدیدی را وارد سیستم می
کنند و اما Command ی که پذیرفته نشده است منجر به نوعی Exception می‌شود.

تعریف Command ها نیز با تمرکز بر روی افعال Domain
انجام می شوند. با این وجود آنها به چیزهایی که کاربر از آنها به عنوان یک عملیات و یا Operation یاد می کند
دلالت دارند.

برای مثال گر چه که نحوه انجام سفارش های مربوط به
نوشیدنی ها و غذاها در Domain ما متفاوت است اما کارمندان نمی‌خواهند غذاها و نوشیدنی های مربوط به یک سفارش
یکسان را به صورت جداگانه سرو کنند. سناریوی منطقی تر این است که مشتری غذا و نوشیدنی خود را در کنار هم
سفارش دهد و یا حتی پس از مدتی تصمیم به تغییر سفارش خود بگیرد. بنابراین یک Command برای ثبت سفارش وجود
خواهد داشت. در ادامه Command هایی که در Domain تعریف می شوند را می بینید.

  • OpenTab
  • PlaceOrder
  • AmendOrder
  • MarkDrinksServed
  • MarkFoodPrepared
  • MarkFoodServed
  • CloseTab

دقت کنید که نام مربوط به این Command ها حاوی افعالی در حالت امری می باشند. این
نیز از نقاط تمایز Command ها و Event ها می باشد.

در نامگذاری Command ها از افعالی که در حالت امری هستند استفاده می شود در حالی که
در تعریف Event ها از افعالی در حالت گذشته قرار دارند استفاده می شود.

تعریف Exception ها

یکی از قسمت های مهم فرآیند مدل کردن فکر کردن در
رابطه با چیزهایی هست که منجر به پذیرفته نشدن یک Command می شود. چنین مواردی را که با نام مسیرهای
ناراحت (Sad Paths) نیز معرفی می‌شوند را بصورت Exception ها مدل می کنیم. پیشنهاد می کنیم مفهوم Happy
Path و Sad Path را بررسی کنید. دقت کنید که Command ها و Event ها در قالب DTO ها مدل می شوند. این یکی
از نقاط تمایز این دو مورد با Exception ها می باشد. Exception ها می توانند حاوی جزئیاتی در رابطه با
اینکه چرا Command مورد نظر پذیرفته نشد باشند.

یکی از مزیت های استفاده از Exception ها برای مدل
کردن چنین مواردی این است که Front End برنامه باید توسط Domain Logic مطلع شود که چه اتفاق ناخوشایندی
رخ داده است. اینکه Front End برنامه با بررسی بعضی از State ها تلاش به کشف کردن مشکل به وجود آمده
باشد کند یا حتی کاربر سعی به حدس زدن مشکل به وجود آمده کند روش کاملا اشتباهی است.

با بررسی دقیق سناریو مطرح شده در قسمت های قبلی
در رابطه با Domain می توانیم متوجه بشیم که Exception های زیر ممکن است رخ دهند:

  • CannotCancelServedItem
  • TabHasUnservedItems
  • MustPayEnough

دقت کنید که در نامگذاری Exception ها چرایی اینکه
Command پذیرفته‌ نشد در نظر گرفته می شود. برای مثال نمیتوان ایتم سرو شده را کنسل کرد و یا تب حاوی
ایتم های غیر سرو شده می باشد.

تعریف Aggregate ها

دقت کنید که در Domain افعال به تنهایی
نمی‌توانند کار زیادی را انجام بدهند. بدون شک به استفاده از اسم ها هم نیاز داریم. اگر بخواهیم به
طور دقیق‌تر بگوییم استفاده از اسم ها برای تعریف کردن State جاری سیستم ضروری هستند. یکی از موارد
استفاده از State ها تصمیم گرفتن در رابطه با پذیرفته شدن یا عدم پذیرفته شدن Command ها می باشد.
برای مثال در Domain ما برای اینکه Command کنسل کردن یک سفارش پذیرفته شود باید بررسی کنیم و
ببینیم آیا State آن سفارش در حالت سرو شده قرار دارد یا نه چرا که در سناریوی که در قسمت های قبلی
تعریف کردیم ذکر کردیم که سفارشی که سرو شده باشد و مشتری آن را پذیرفته باشد امکان کنسل شدن را
ندارد.

دقت کنید که تمامی اطلاعاتی که ما به آن نیاز
داریم در مجموعه‌ Event هایی که رخ داده ‌اند می باشند چرا که آنها تمامی حقایق وارد شده به سیستم
را ثبت و ضبط کرده اند. موضوع مهمتر اینکه برای تصمیم گیری در رابطه با پذیرفته شدن یا عدم پذیرفته
شدن یک Command نیازی به تمامی Event های رخ داده شده نداریم. در واقع ما فقط به Event هایی که برای
تصمیم ‌گیری در رابطه با آن موضوع خاص به ما کمک می کنند اهمیت می دهیم. برای مثال تمامی Event هایی
که در رابطه با یک تب (مفهموم تب در Domain کافی شاپ را در قسمت اول این آموزش معرفی کنیم) خاص رخ
دادند که با استفاده از آنها می‌توانیم پذیرفته شدن یا عدم پذیرفته شدن Command کنسل کردن سفارش را
مشخص کنیم.

با این حساب به مفهوم Aggregate ها میرسیم. هر
Aggregate مجموعه Event های خاص خودش را دارد که با در نظر گرفتن تمامی آنها می‌توانیم State فعلی
آن Aggregate را مشخص کنیم. Aggregate ها از همدیگر کاملاً تفکیک (Isolated) شده هستند. تصمیم در
رابطه با اینکه یک Command باید پذیرفته شود یا نه صرفاً بر اساس خود آن Command و اطلاعات موجود در
Event های رخ داده برای یک Aggregate انجام می شود.

به طور کلی Aggregate ها یکی از دو مورد زیر
هستند:

  • یک Object که به هیچ Object دیگری رفرنس نمی زند.
  • یک گراف تفکیک شده از Object ها که که یکی از آنها را به عنوان ریشه میشناسیم و دنیای
    بیرون از آن Aggregate فقط آن ریشه را می شناسند.

طراحی Aggregate ها کار ساده ای نیست چرا که
ما را الزام می‌کنند که مفاهیم تجاری (Business Concepts) را کشف و از هم تفکیک کنیم. علاوه بر این
موضوع به این معنی است که ما باید به طور کامل بر روی مرزهای سازگاری (Consistency Boundaries)
تمرکز کنیم.

در Domain کافی شاپ ما فقط یک Aggregate به
نام تب وجود دارد. با این وجود ممکن است در بسیاری از نرم افزارهای دیگر نیاز به کشف Aggregate های
بیشتری باشد. به طور کلی آغاز کار از مدل کردن Event ها و Command ها و سپس گروه بندی کردن آن ها بر
اساس Invariant ها (قوانین تجاری که باید برقرار باشند) راهکاری مناسب است.

در ادامه کار نوشتن موارد تست (Test Case) را
آغاز میکنیم و Domain Logic را میسازیم. همینطور که مشغول به این کار می‌شویم جنبه‌های مختلف طراحی
خود را مرور کرده و جزئیات مربوط به Command ها و Event ها را بررسی می کنیم.

پیاده سازی Domain Logic و اولین Command و Event

تمامی سناریوها با باز شدن یک تب آغاز می‌شوند. بنابراین ما در قسمت پایین
TabOpened را به عنوان یک Event تعریف می کنیم.

 
public class TabOpened
{
    public Guid Id;
    public int TableNumber;
    public string Waiter;
}

در این Event پروپرتی هایی برای یک شناسه یکتا برای تبی که باز شده است
شماره میز مشتری و همچنین گارسون یا گارسون هایی که مسئول سرو کردن سفارش برای آن تب هستند وجود
دارد. کلاس های Event می‌توانند از کلاس Base ی ارث بری کنند. یکی از مهمترین اتفاقاتی که توصیه
می‌کنیم برای Event ها بیفتد تعریف یک Id از نوع Guid در آنها است.

تعریف Command مربوط به باز کردن بک تب با نام
OpenTab نیز بسیار شبیه به Event بالا می‌باشد. تعریف آن را در قسمت پایین میبینید:

 
public class OpenTab
{
    public Guid Id;
    public int TableNumber;
    public string Waiter;
}

قرار دادن کلمه ی Event در پایان نام Event ها
و یا کلمه ی Command در پایان نام Command ها توصیه نمی‌شود چرا که باعث ایجاد نویز می گردد. نام
گذاری مناسب برای Event ها و Command ها می تواند به تنهایی گویای ماهیت آنها باشد.

نوشتن اولین تست

قبل از اینکه بتوانیم تست ها را بنویسیم
باید کلاسی را برای Aggregate خود تعریف کنیم. در ادامه تعریف کلاس TabAggregate را مشاهده می
کنید.

 
public class TabAggregate : Aggregate
{
}

همانطور که میبینید این کلاس از کلاس
Aggregate ارثدبری کرده است. کلاس Aggregate در بردارنده یک Id و مقداری کد در رابطه با Event
های مربوط به Aggregate می باشد.

در این لحظه می توانیم اولین Test Class
خود را ساخته و اولین تست خود را در درون آن قرار بدهیم.

 
[TestFixture]
public class TabTests  : BDDTest
{
    private Guid testId;
    private int testTable;
    private string testWaiter;

    [SetUp]
    public void Setup()
    {
        testId = Guid.NewGuid();
        testTable = 42;
        testWaiter = "Derek";
    }

    [Test]
    public void CanOpenANewTab()
    {
        Test(
            Given(),
            When(new OpenTab
            {
                Id = testId,
                TableNumber = testTable,
                Waiter = testWaiter
            }),
            Then(new TabOpened
            {
                Id = testId,
                TableNumber = testTable,
                Waiter = testWaiter
            }));
    }
}

تستی که در قسمت بالا قرار داده شده است
می گوید که به شرطی که تاریخچه ‌ی Event ی برای یک تب وجود نداشته باشد زمانی که کامند OpenTab
صادر شد ما انتظار داریم که TabOpened تولید شود.

در حال حاضر Solution ما حاوی مواردی است
که در تصویر زیر مشاهده می کنید:

solution-structure

اجرای تست و Fail شدن آن

جرای تست نوشته شده در قسمت بالا بدون
شک باعث Fail شدن آن می شود اما این شکست جزئیات جالبی را در رابطه با این که در قسمت بعدی
باید چه چیزی را پیاده سازی کنیم به ما میدهد.

test

در جزئیات مربوط به این شکست گفته شده
است که TabAggregate که در حال حاضر OpenTab را Handle نمی‌کند.

برای اضافه کردن یک Command Handler
جدید باید اینترفیس جنریک IHandleCommand را پیاده سازی کنید و Type Parameter این اینترفیس
را با Command ی که قرار است Handle کند تنظیم کنید.

 
public class TabAggregate : Aggregate,
    IHandleCommand
{
    public IEnumerable Handle(OpenTab c)
    {
        yield return new TabOpened
        {
            Id = c.Id,
            TableNumber = c.TableNumber,
            Waiter = c.Waiter
        };
    }
}

دقت کنید که پیاده‌سازی Handler برای
OpenTab بسیار ساده است. فقط باید TabOpened را ایجاد کنید. علاوه بر این دقت کنید که از
کلید واژه yield استفاده شده است تا بتوان صفر یا چندین Event را ساخت. در حال حاضر اجرا
کردن تست ما با موفقیت انجام می شود.

test

ثبت سفارش

برای پیاده سازی فرآیند ثبت سفارش
در ابتدا دو Event با نام های DrinksOrdered و FoodOrdered را به صورت زیر تعریف می
کنیم.

 
public class OrderedItem
{
    public int MenuNumber;
    public string Description;
    public bool IsDrink;
    public decimal Price;
}

public class DrinksOrdered
{
    public Guid Id;
    public List Items;
}

public class FoodOrdered
{
    public Guid Id;
    public List Items;
}

هر دوی این Event ها از کلاس
OrderItem به طور اشتراکی استفاده می کنند. دقت کنید که استقلال Event ها از همدیگر
بسیار مهم است اما استفاده مجدد از کلاسی به این شکل مشکل ساز نخواهد بود. Command
مربوط به ثبت سفارش با نام PlaceOrder شبیه زیر تعریف می شود.

 
public class PlaceOrder
{
    public Guid Id;
    public List Items;
}

دقت کنید که برای نوشتن تست مربوط
به این Event ها و Command ها باید در نظر داشت که برای ثبت یک سفارش ابتدا یک تب باید
باز باشد. در غیر اینصورت Command پذیرفته نخواهد شد. به همین منظور در ابتدا کلاس
TabNotOpen که یک Exception هست را به صورت زیر تعریف می کنیم.

 
public class TabNotOpen : Exception
{
}

در تعریف Exception های مربوط به
یک Aggregate می‌توان هر Exception را در درون یک فایل جداگانه قرار داد. علاوه بر این
می‌توان یک فایل تک با نام Exceptions.cs به پروژه اضافه کرد و تمامی Exception های
مربوط به یک Aggregate را در آن قرار داد.

تستی که در ادامه می بینید در
مورد عدم امکان ثبت سفارش در زمانی است که یک تب باز نباشد.

 
[Test]
public void CanNotOrderWithUnopenedTab()
{
    Test(
        Given(),
        When(new PlaceOrder
        {
            Id = testId,
            Items = new List { testDrink1 }
        }),
        ThenFailWith());
}

بدون شک این تست نیز در ابتدای
کار شکست خواهد خورد. در توضیحات مربوط به دلیل شکست این تست گفته شده است که
TabAggregate در حال حاضر PlaceOrder در Handle نمی‌کند.

به منظور پاس شدن این تست باید یک
Handler برای این Command در نظر بگیریم. برای تعریف یک Handler باید اینترفیس جنریک
IHandleCommand را پیاده سازی کنیم. در قسمت های قبلی نحوه پیاده سازی یک Command با
استفاده از این اینترفیس را دیدیم.

در قسمت بعدی سه تست بسیار جالب خواهیم نوشت. یک تست برای
ثبت سفارشی که فقط حاوی نوشیدنی است یک تست برای سفارشی که فقط حاوی غذا است و یک تست
برای سفارشی که حاوی مقداری غذا و همچنین نوشیدنی است.

 
[Test]
public void CanPlaceDrinksOrder()
{
    Test(
        Given(new TabOpened
        {
            Id = testId,
            TableNumber = testTable,
            Waiter = testWaiter
        }),
        When(new PlaceOrder
        {
            Id = testId,
            Items = new List
        }),
        Then(new DrinksOrdered
        {
            Id = testId,
            Items = new List { testDrink1, testDrink2 }
        }));
}

[Test]
public void CanPlaceFoodOrder()
{
    Test(
        Given(new TabOpened
        {
            Id = testId,
            TableNumber = testTable,
            Waiter = testWaiter
        }),
        When(new PlaceOrder
        {
            Id = testId,
            Items = new List { testFood1, testFood1 }
        }),
        Then(new FoodOrdered
        {
            Id = testId,
            Items = new List { testFood1, testFood1 }
        }));
}

[Test]
public void CanPlaceFoodAndDrinkOrder()
{
    Test(
        Given(new TabOpened
        {
            Id = testId,
            TableNumber = testTable,
            Waiter = testWaiter
        }),
        When(new PlaceOrder
        {
            Id = testId,
            Items = new List { testFood1, testDrink2 }
        }),
        Then(new DrinksOrdered
        {
            Id = testId,
            Items = new List { testDrink2 }
        },
        new FoodOrdered
        {
            Id = testId,
            Items = new List { testFood1 }
        }));
}

هدف ما این است که اطمینان حاصل
کنیم که آیتم ها در Event های به درستی دسته بندی شوند و سفارش هایی که حاوی غذا و یا
نوشیدنی خالی هستند Event ی را ایجاد نکنند.

همه این سه تست در ابتدا با شکست
روبرو خواهند شد. در توضیحات علت شکست گفته می شود که TabAggregate نمی ‌داند چطور
TabOpened را اعمال کند.

هدف ما این است که اطمینان حاصل
کنیم که آیتم ها در Event های به درستی دسته بندی شوند و سفارش هایی که حاوی غذا و یا
نوشیدنی خالی هستند Event ی را ایجاد نکنند. همه این سه تست در ابتدا با شکست روبرو
خواهند شد. در توضیحات علت شکست گفته می شود که TabAggregate نمی ‌داند چطور TabOpened
را اعمال کند.

حال که ما تست هایی داریم که
Event هایی در قسمت Given آنها قرار گرفتند یک تاریخچه از Event ها را در دست داریم.
Aggregate مورد نظر ما نقش بسیار مهمی در تبدیل کردن این تاریخچه از Event ها به State
فعلی سیستم ایفا می‌کند. این نقش بسیار مهم توسط پیاده سازی اینترفیس جنریک IApplyEvent
برای هر کدام از Event های مربوطه انجام می شود. متد Apply در درون این اینترفیس باید
State یک Aggregate را بر اساس Event و داده های آن به روز رسانی کند. در این مورد خاص
محتوای Event برای ما هیچ اهمیتی ندارد.

فقط این موضوع که این Event رخ
داده است اهمیت دارد. در قسمت بعد پیاده سازی اینترفیس IApplyEvent را برای
TabAggregate را مشاهده می کنید.

 
public class TabAggregate : Aggregate,
IHandleCommand,
IHandleCommand,
IApplyEvent
{
    private bool open = false;

    // Command handlers, omitted for brevity

    public void Apply(TabOpened e)
    {
        open = true;
    }
}

با ایجاد این تغییر هنوز هم تست ها با شکست روبرو می شوند
اما یک پیام متفاوت نسبت به قبل دریافت می کنیم. پیام مربوط به شکست تست‌ ها می‌ گوید
که Event ها مورد انتظار بودند اما TabNotOpen که یک Exception است رخ داده است.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *