آموزش Event Sourcing بخش ششم
سلام دنیا به روش Event Sourcing- بخش دوم
لیست مطالب آموزشی Event Sourcing:
- بخش اول مقدمهای بر Event Sourcing
- بخش دوم آشنایی مقدماتی با ساختار داخلی Event Store
- مقایسه رویکردهای State-Oriented و State-Transition
- مزیتهای Event Sourcing
- سلام به دنیا به روش Event Sourcing
- سلام به دنیا به روش Event Sourcing-بخش دوم
- بخش هفتم Projection
- ویرایش event ها در EventSourcing
- Message، Command یا Event، کدوم رو انتخاب کنم؟
در پست قبلی به پیادهسازی مثال حساب بانکی به روش ایونت سورسینگ پرداختیم. در این بخش ادامه آن مثال را پیش خواهیم برد. داستان کاربری دوم مربوط به حساب بانکی در این بخش پیادهسازی خواهد شد.
مقدمه
حساب بانکی که در پست قبلی به آن اشاره کردیم به این صورت بود:
شما به بانک مراجعه میکنید و یک حساب بانکی را برای خودتان افتتاح میکنید. سپس تراکنشهای مالی خود را از طریق این حساب بانکی انجام میدهید.
در این پست بخش دوم سناریوی بالا را پیادهسازی خواهیم کرد.
اگر کارت را برگردانیم در قسمت condition پشت این کارت شرایط پذیرش زیر قید شده است.
پیاده سازی تست
اگر به داستان کاربری بالا نگاه کنیم، میتوانیم اینطور برداشت کنیم که:
- اگر: حساب بانکی را با موجودی اولیه ۱۰۰۰۰۰۰ ریال باز کرده باشیم
- و: ۲۰۰۰۰۰ بابت کارمزد بانکی و ۸۰۰۰۰۰ بابت کارمزد پیامک از حساب کسر شده باشد
- در صورتی که: ۱۰۰۰۰۰ ریال از طریق عابر بانک از حساب برداشت کنیم
- در نتیجه: موجودی حساب باید ۸۰۰۰۰۰ باشد
[Fact]
public void deposited_successfully()
{
// Context
const int InitialBalance = 10000000;
const decimal BankFees = 20000;
const decimal SmsFees = 80000;
var depositAmount = 100000;
var bankAccountId = BankAccountId.New(Guid.NewGuid().ToString());
var aNewBankAccountIsOpened = new ANewBankAccountIsOpened(bankAccountId.Id, InitialBalance);
var theBankAccountIsDepositedDueToBankFees = new TheBankAccountIsDepositedDueToBankFees(bankAccountId.Id, BankFees);
var theBankAccountIsDepositedDueToSmsFees = new TheBankAccountIsDepositedDueToSMSFees(bankAccountId.Id, SmsFees);
var depositFromBankAccountCommand = new DepositFromBankAccountCommand(depositAmount);
// Action and Outcome
InTermsOfAggregateRoot<BankAccount, BankAccountId>
.IfIApplied(aNewBankAccountIsOpened,
theBankAccountIsDepositedDueToBankFees,
theBankAccountIsDepositedDueToSmsFees)
.WhenICall(bankAccount=>bankAccount.Deposit(depositFromBankAccountCommand))
.ThenIWillExpect(bankAccount => bankAccount.Balance == (InitialBalance - (BankFees + SmsFees)) - depositAmount);
}
همانطور که بخش Action و Outcome در کد بالا میبینید، پس از اعمال اوینتهای قبلی، در قسمت Action از طریق متد WhenICall ما رفتاری از حساب بانکی را تریگر میکنیم، و در نهایت انتظار داریم که بالانس جدید حساب بانکی به درستی محاسبه بشود.
از کامند DepositFromBankAccountCommand جهت برداشت از حساب استفاده میکنیم.
public class DepositFromBankAccountCommand
{
public decimal Amount { get; }
public DepositFromBankAccountCommand(decimal amount)
{
Amount = amount;
}
}
متد Deposit در کلاس حساب بانکی نیز بصورت زیر پیادهسازی میشود.
public void Deposit(DepositFromBankAccountCommand command)
=> Queue(new TheBankAccountIsDepositedFromPOS(Identity.Id, command.Amount));
همانطور که میبینید در این متد شرایط پذیرش شده در داستان کاربری چک نمیشود. دلیل این کار این است که ما باید ابتدا برای چک کردن این داستانهای کاربری تست(هایی) بنویسیم. پس از fail شدن آن تستها، اقدام به پیادهسازی کد production در حساب بانکی خواهیم کرد. این روشی است که توسعه برنامه به صورت TDD پیش میگیریم.
برای هندل کردن این ایونت در کلاس حساب بانکی، متد On نیز به کلاس حساب بانکی افزوده خواهد شد.
private void On(TheBankAccountIsDepositedFromPOS @event)
=> Balance -= @event.Amount;
کد ایونت TheBankAccountIsDepositedFromPOS نیز بصورت زیر می باشد.
public class TheBankAccountIsDepositedFromPOS : IsADomainEvent
{
public decimal Amount { get; }
public TheBankAccountIsDepositedFromPOS(string aggregateId, decimal amount):base(aggregateId)
{
Amount = amount;
}
public override IEnumerable<object> GetEqualityComponents()
{
yield return AggregateId;
}
}
تست دریافت صحیح ایونتها پس از deposit کردن حساب بانکی
همانند داستان کاربری افتتاح حساب بانکی که در پست قبلی مشاهده کردیم، ما باید برای سناریوهای تریگر کردن یک رفتار از یک آبجکت event sourced شده، و دریافت لیستی از ایونتها نیز تست مناسب بنویسیم.
در داستان کاربری برداشت پول از حساب بانکی، انتظار داریم که پس از پردازش عملیات برداشت از حساب، ایونت TheBankAccountIsDepositedFromPOS به لیست ایونتهای حساب بانکی افزوده شود.
تست مربوطه را در زیر مشاهده میکنید.
[Fact]
public void queue_TheBankAccountIsDepositedFromPOS_event_after_deposited_successfully_()
{
// Context
const int InitialBalance = 10000000;
const decimal BankFees = 20000;
const decimal SmsFees = 80000;
var depositAmount = 100000;
var bankAccountId = BankAccountId.New(Guid.NewGuid().ToString());
var aNewBankAccountIsOpened = new ANewBankAccountIsOpened(bankAccountId.Id, InitialBalance);
var theBankAccountIsDepositedDueToBankFees = new TheBankAccountIsDepositedDueToBankFees(bankAccountId.Id, BankFees);
var theBankAccountIsDepositedDueToSmsFees = new TheBankAccountIsDepositedDueToSMSFees(bankAccountId.Id, SmsFees);
var depositFromBankAccountCommand = new DepositFromBankAccountCommand(depositAmount);
// Action and Outcome
InTermsOfAggregateRoot<BankAccount, BankAccountId>
.IfIApplied(aNewBankAccountIsOpened,
theBankAccountIsDepositedDueToBankFees,
theBankAccountIsDepositedDueToSmsFees)
.WhenICall(bankAccount => bankAccount.Deposit(depositFromBankAccountCommand))
.ThenIWillExpectTheseEvents(new TheBankAccountIsDepositedFromPOS(bankAccountId.Id, (InitialBalance - (BankFees + SmsFees)) - depositAmount));
}
پایان بخش ششم