Skip to content

Commit c4bc1ea

Browse files
committed
- Checkpoint: Fixed event handling
1 parent d07f86a commit c4bc1ea

File tree

10 files changed

+75
-27
lines changed

10 files changed

+75
-27
lines changed

src/SourceFlow.ConsoleApp/Aggregates/AccountAggregate.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
using Microsoft.Extensions.Logging;
12
using SourceFlow.ConsoleApp.Commands;
3+
using SourceFlow.Events;
24

35
namespace SourceFlow.ConsoleApp.Aggregates
46
{
5-
public class AccountAggregate : BaseAggregate<BankAccount>
7+
public class AccountAggregate : BaseAggregate<BankAccount>,
8+
IEventHandler<EntityCreated<BankAccount>>
9+
610
{
711
public void CreateAccount(int accountId, string holder, decimal amount)
812
{
@@ -42,5 +46,17 @@ public void Close(int accountId, string reason)
4246
ClosureReason = reason
4347
}));
4448
}
49+
50+
public Task Handle(EntityCreated<BankAccount> @event)
51+
{
52+
if (@event.Name != "BankAccountCreated")
53+
return Task.CompletedTask;
54+
55+
return PublishAsync(Command.For<BankAccount>(@event.Payload.Id)
56+
.Create<ActivateAccount, ActivationPayload>(new ActivationPayload
57+
{
58+
ActiveOn = DateTime.UtcNow,
59+
}));
60+
}
4561
}
4662
}

src/SourceFlow.ConsoleApp/Aggregates/BankAccount.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ public class BankAccount : IEntity
88
public decimal Balance { get; set; }
99
public bool IsClosed { get; set; }
1010
public string ClosureReason { get; internal set; }
11+
public DateTime ActiveOn { get; internal set; }
1112
}
1213
}

src/SourceFlow.ConsoleApp/Commands/AccountPayload.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
namespace SourceFlow.ConsoleApp.Commands
44
{
5+
public class ActivationPayload : IPayload
6+
{
7+
public DateTime ActiveOn { get; set; }
8+
}
9+
510
public class AccountPayload : IPayload
611
{
712
public decimal InitialAmount { get; set; }
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace SourceFlow.ConsoleApp.Commands
2+
{
3+
public class ActivateAccount : AccountCommand<ActivationPayload>
4+
{
5+
}
6+
}

src/SourceFlow.ConsoleApp/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
Console.WriteLine($"- Account Id: {account?.Id}");
5555
Console.WriteLine($"- Holder: {account?.AccountName}");
5656
Console.WriteLine($"- Created On: {account?.CreatedDate}");
57+
Console.WriteLine($"- Activated On: {account?.ActiveOn}");
5758
Console.WriteLine($"- Current Balance: ${account?.CurrentBalance}");
5859
Console.WriteLine($"- Transaction Count: {account?.TransactionCount}");
5960
Console.WriteLine($"- Is A/C Closed: {account?.IsClosed}");
@@ -70,6 +71,7 @@
7071
Console.WriteLine($"- Account Id: {account?.Id}");
7172
Console.WriteLine($"- Holder: {account?.AccountName}");
7273
Console.WriteLine($"- Created On: {account?.CreatedDate}");
74+
Console.WriteLine($"- Activated On: {account?.ActiveOn}");
7375
Console.WriteLine($"- Current Balance: ${account?.CurrentBalance}");
7476
Console.WriteLine($"- Transaction Count: {account?.TransactionCount}");
7577
Console.WriteLine($"- Is A/C Closed: {account?.IsClosed}");
@@ -86,6 +88,7 @@
8688
Console.WriteLine($"- Account Id: {account?.Id}");
8789
Console.WriteLine($"- Holder: {account?.AccountName}");
8890
Console.WriteLine($"- Created On: {account?.CreatedDate}");
91+
Console.WriteLine($"- Activated On: {account?.ActiveOn}");
8992
Console.WriteLine($"- Current Balance: ${account?.CurrentBalance}");
9093
Console.WriteLine($"- Transaction Count: {account?.TransactionCount}");
9194
Console.WriteLine($"- Is A/C Closed: {account?.IsClosed}");

src/SourceFlow.ConsoleApp/Sagas/AccountSaga.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ namespace SourceFlow.ConsoleApp.Sagas
66
{
77
public class AccountSaga : BaseSaga<BankAccount>,
88
ICommandHandler<CreateAccount>,
9+
ICommandHandler<ActivateAccount>,
910
ICommandHandler<DepositMoney>,
1011
ICommandHandler<WithdrawMoney>,
1112
ICommandHandler<CloseAccount>
@@ -31,6 +32,23 @@ public async Task Handle(CreateAccount command)
3132
await CreateAggregate(account);
3233
}
3334

35+
public async Task Handle(ActivateAccount command)
36+
{
37+
logger.LogInformation("Action=Account_Activate, ActivatedOn={ActiveOn}, Account={AccountId}", command.Payload.ActiveOn, command.Entity.Id);
38+
39+
var account = await GetAggregate(command.Entity.Id);
40+
41+
if (account.IsClosed)
42+
throw new InvalidOperationException("Cannot deposit to a closed account");
43+
44+
if (command.Payload.ActiveOn == DateTime.MinValue)
45+
throw new ArgumentException("Deposit amount must be positive", nameof(command.Payload.ActiveOn));
46+
47+
account.ActiveOn = command.Payload.ActiveOn;
48+
49+
await UpdateAggregate(account);
50+
}
51+
3452
public async Task Handle(DepositMoney command)
3553
{
3654
logger.LogInformation("Action=Money_Deposited, Amount={Amount}, Account={AccountId}", command.Payload.Amount, command.Entity.Id);

src/SourceFlow.ConsoleApp/ViewModels/AccountViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ public class AccountViewModel : IViewModel
1111
public bool IsClosed { get; set; }
1212
public string ClosureReason { get; set; }
1313
public int Version { get; set; }
14+
public DateTime ActiveOn { get; set; }
1415
}
1516
}

src/SourceFlow.ConsoleApp/ViewModels/AccountViewTransforms.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ public AccountViewTransforms(IViewRepository repository)
1515

1616
public async Task Transform(EntityCreated<BankAccount> @event)
1717
{
18+
if (@event.Name != "BankAccountCreated")
19+
throw new InvalidOperationException($"Unexpected event type: {@event.Name}");
20+
1821
var view = new AccountViewModel
1922
{
2023
Id = @event.Payload.Id,
@@ -33,6 +36,9 @@ public async Task Transform(EntityCreated<BankAccount> @event)
3336

3437
public async Task Transform(EntityUpdated<BankAccount> @event)
3538
{
39+
if (@event.Name != "BankAccountUpdated")
40+
throw new InvalidOperationException($"Unexpected event type: {@event.Name}");
41+
3642
var view = await repository.Get<AccountViewModel>(@event.Payload.Id);
3743

3844
if (view == null)
@@ -43,6 +49,7 @@ public async Task Transform(EntityUpdated<BankAccount> @event)
4349
view.AccountName = @event.Payload.AccountName;
4450
view.IsClosed = @event.Payload.IsClosed;
4551
view.ClosureReason = @event.Payload.ClosureReason;
52+
view.ActiveOn = @event.Payload.ActiveOn;
4653
view.Version++;
4754
view.TransactionCount++;
4855

src/SourceFlow/BaseSaga.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Threading.Tasks;
33
using Microsoft.Extensions.Logging;
44
using SourceFlow.Events;
5+
using SourceFlow.Impl;
56

67
namespace SourceFlow
78
{
@@ -45,11 +46,12 @@ protected BaseSaga()
4546
}
4647

4748
/// <summary>
48-
/// Checks if the given event handler is a generic event handler for the specified event type.
49+
/// Determines whether the specified saga instance can handle the given event type.
4950
/// </summary>
50-
/// <param name="instance"></param>
51-
/// <param name="eventType"></param>
52-
/// <returns></returns>
51+
/// <param name="instance">The saga instance to evaluate. Must not be <see langword="null"/>.</param>
52+
/// <param name="eventType">The type of the event to check. Must not be <see langword="null"/>.</param>
53+
/// <returns><see langword="true"/> if the saga instance can handle the specified event type; otherwise, <see
54+
/// langword="false"/>.</returns>
5355
internal static bool CanHandle(ISaga instance, Type eventType)
5456
{
5557
if (instance == null || eventType == null)
@@ -60,10 +62,13 @@ internal static bool CanHandle(ISaga instance, Type eventType)
6062
}
6163

6264
/// <summary>
63-
/// Handles the specified event asynchronously in the saga.
65+
/// Handles the specified command as part of the saga's workflow.
6466
/// </summary>
65-
/// <typeparam name="TCommand"></typeparam>
66-
/// <param name="event"></param>
67+
/// <remarks>This method dynamically resolves the appropriate command handler for the given
68+
/// command type and invokes its <c>Handle</c> method. If the saga cannot handle the specified command, the
69+
/// method returns without performing any action.</remarks>
70+
/// <typeparam name="TCommand">The type of the command to handle.</typeparam>
71+
/// <param name="command">The command to be processed by the saga. Must not be <see langword="null"/>.</param>
6772
/// <returns></returns>
6873
async Task ISaga.Handle<TCommand>(TCommand command)
6974
{

src/SourceFlow/Impl/EventQueue.cs

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,6 @@ public EventQueue(IEnumerable<IAggregateRoot> aggregates, IViewPublisher viewPub
3838
this.viewPublisher = viewPublisher ?? throw new ArgumentNullException(nameof(viewPublisher));
3939
}
4040

41-
/// <summary>
42-
/// Determines whether the specified aggregate can handle events of the given type.
43-
/// </summary>
44-
/// <param name="aggregate">The aggregate root to check. Must implement <see cref="IAggregateRoot"/>.</param>
45-
/// <param name="eventType">The type of the event to check for handling capability. Cannot be <see langword="null"/>.</param>
46-
/// <returns><see langword="true"/> if the aggregate can handle events of the specified type; otherwise, <see
47-
/// langword="false"/>.</returns>
48-
internal static bool CanHandle(IAggregateRoot aggregate, Type eventType)
49-
{
50-
if (aggregate == null || eventType == null)
51-
return false;
52-
53-
var handlerType = typeof(IEventHandler<>).MakeGenericType(eventType);
54-
return handlerType.IsAssignableFrom(aggregate.GetType());
55-
}
56-
5741
/// <summary>
5842
/// Enqueues an event in order to publish to subcribers.
5943
/// </summary>
@@ -66,18 +50,21 @@ public async Task Enqueue<TEvent>(TEvent @event)
6650
if (@event == null)
6751
throw new ArgumentNullException(nameof(@event));
6852

53+
await viewPublisher.Publish(@event);
54+
6955
var tasks = new List<Task>();
7056

7157
foreach (var aggregate in aggregates)
7258
{
73-
if (!CanHandle(aggregate, @event.GetType()))
59+
var handlerType = typeof(IEventHandler<>).MakeGenericType(@event.GetType());
60+
if (!handlerType.IsAssignableFrom(aggregate.GetType()))
7461
continue;
7562

7663
var method = typeof(IEventHandler<>)
7764
.MakeGenericType(@event.GetType())
7865
.GetMethod(nameof(IEventHandler<TEvent>.Handle));
7966

80-
var task = (Task)method.Invoke(this, new object[] { @event });
67+
var task = (Task)method.Invoke(aggregate, new object[] { @event });
8168

8269
logger?.LogInformation("Action=Event_Enqueue, Event={Event}, Aggregate={Aggregate}, Handler:{Handler}",
8370
@event.GetType().Name, aggregate.GetType().Name, method.Name);
@@ -86,7 +73,6 @@ public async Task Enqueue<TEvent>(TEvent @event)
8673
}
8774

8875
await Task.WhenAll(tasks);
89-
await viewPublisher.Publish(@event);
9076
}
9177
}
9278
}

0 commit comments

Comments
 (0)