Skip to content

Commit fad0b7c

Browse files
committed
- Checkpoint: Add ViewModel ETL
1 parent c9f9346 commit fad0b7c

File tree

76 files changed

+1554
-1856
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+1554
-1856
lines changed

SourceFlow.Net.sln

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
44
VisualStudioVersion = 17.13.35828.75
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceFlow.Core", "src\SourceFlow.Core\SourceFlow.Core.csproj", "{28E854D8-02A3-47CB-B17E-238F786C4C14}"
7-
EndProject
86
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
97
EndProject
108
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{653DCB25-EC82-421B-86F7-1DD8879B3926}"
@@ -36,10 +34,6 @@ Global
3634
Release|Any CPU = Release|Any CPU
3735
EndGlobalSection
3836
GlobalSection(ProjectConfigurationPlatforms) = postSolution
39-
{28E854D8-02A3-47CB-B17E-238F786C4C14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40-
{28E854D8-02A3-47CB-B17E-238F786C4C14}.Debug|Any CPU.Build.0 = Debug|Any CPU
41-
{28E854D8-02A3-47CB-B17E-238F786C4C14}.Release|Any CPU.ActiveCfg = Release|Any CPU
42-
{28E854D8-02A3-47CB-B17E-238F786C4C14}.Release|Any CPU.Build.0 = Release|Any CPU
4337
{60461B85-D00F-4A09-9AA6-A9D566FA6EA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
4438
{60461B85-D00F-4A09-9AA6-A9D566FA6EA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
4539
{60461B85-D00F-4A09-9AA6-A9D566FA6EA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -57,7 +51,6 @@ Global
5751
HideSolutionNode = FALSE
5852
EndGlobalSection
5953
GlobalSection(NestedProjects) = preSolution
60-
{28E854D8-02A3-47CB-B17E-238F786C4C14} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
6154
{60461B85-D00F-4A09-9AA6-A9D566FA6EA4} = {653DCB25-EC82-421B-86F7-1DD8879B3926}
6255
{43C0A7B4-6682-4A49-B932-010F0383942A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
6356
{C0724CCD-8965-4BE3-B66C-458973D5EFA1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}

src/SourceFlow.ConsoleApp/Dockerfile

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.Collections.Concurrent;
2+
3+
namespace SourceFlow.ConsoleApp.Impl
4+
{
5+
public class InMemoryDomianRepository : IDomainRepository
6+
{
7+
private readonly ConcurrentDictionary<int, IEntity> _cache = new();
8+
9+
public Task DeleteAsync<TEntity>(TEntity entity) where TEntity : IEntity
10+
{
11+
if (entity?.Id == null)
12+
throw new ArgumentNullException(nameof(entity));
13+
14+
_cache.TryRemove(entity.Id, out _);
15+
16+
return Task.CompletedTask;
17+
}
18+
19+
public Task<TEntity> GetByIdAsync<TEntity>(int id) where TEntity : class, IEntity
20+
{
21+
if (id == 0)
22+
throw new ArgumentNullException(nameof(id));
23+
24+
var success = _cache.TryGetValue(id, out var entity);
25+
26+
return Task.FromResult<TEntity>(success ? (TEntity)entity : null);
27+
}
28+
29+
public Task PersistAsync<TEntity>(TEntity entity) where TEntity : IEntity
30+
{
31+
if (entity?.Id == null)
32+
throw new ArgumentNullException(nameof(entity));
33+
34+
if (entity.Id == 0)
35+
entity.Id = new Random().Next();
36+
37+
_cache[entity.Id] = entity;
38+
39+
return Task.CompletedTask;
40+
}
41+
}
42+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.Collections.Concurrent;
2+
3+
namespace SourceFlow.ConsoleApp.Impl
4+
{
5+
public class InMemoryViewModelRepository : IViewModelRepository
6+
{
7+
private readonly ConcurrentDictionary<int, IViewModel> _cache = new();
8+
9+
public Task DeleteAsync<TEntity>(TEntity entity) where TEntity : IViewModel
10+
{
11+
if (entity?.Id == null)
12+
throw new ArgumentNullException(nameof(entity));
13+
14+
_cache.TryRemove(entity.Id, out _);
15+
16+
return Task.CompletedTask;
17+
}
18+
19+
public Task<TEntity> GetByIdAsync<TEntity>(int id) where TEntity : class, IViewModel
20+
{
21+
if (id == 0)
22+
throw new ArgumentNullException(nameof(id));
23+
24+
var success = _cache.TryGetValue(id, out var entity);
25+
26+
return Task.FromResult<TEntity>(success ? (TEntity)entity : null);
27+
}
28+
29+
public Task PersistAsync<TEntity>(TEntity entity) where TEntity : IViewModel
30+
{
31+
if (entity?.Id == null)
32+
throw new ArgumentNullException(nameof(entity));
33+
34+
if (entity.Id == 0)
35+
entity.Id = new Random().Next();
36+
37+
_cache[entity.Id] = entity;
38+
39+
return Task.CompletedTask;
40+
}
41+
}
42+
}

src/SourceFlow.ConsoleApp/Impl/InMemoryViewRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace SourceFlow.ConsoleApp.Impl
44
{
5-
public class InMemoryViewRepository : IViewRepository
5+
public class InMemoryViewRepository : IViewModelRepository
66
{
77
private readonly ConcurrentDictionary<int, IViewModel> _cache = new();
88

src/SourceFlow.ConsoleApp/Program.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
var accountService = serviceProvider.GetRequiredService<IAccountService>();
3030
var saga = serviceProvider.GetRequiredService<ISaga>();
3131
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
32-
var dataView = serviceProvider.GetRequiredService<IDataView>();
32+
var accountFinder = serviceProvider.GetRequiredService<IViewModelFinder>();
3333

3434
// Create account
3535
var accountId = await accountService.CreateAccountAsync("John Doe", 1000m);
@@ -49,9 +49,7 @@
4949
await accountService.DepositAsync(accountId, amount);
5050

5151
// Get current state
52-
var viewRepository = serviceProvider.GetRequiredService<IViewRepository>();
53-
54-
var account = await viewRepository.GetByIdAsync<AccountViewModel>(accountId);
52+
var account = await accountFinder.Find<AccountViewModel>(accountId);
5553
Console.WriteLine($"\nCurrent Account State:");
5654
Console.WriteLine($"- Account Id: {account?.Id}");
5755
Console.WriteLine($"- Holder: {account?.AccountName}");
@@ -67,7 +65,7 @@
6765
await accountService.ReplayHistoryAsync(accountId);
6866

6967
// Show account summary by replaying history.
70-
account = await viewRepository.GetByIdAsync<AccountViewModel>(accountId);
68+
account = await accountFinder.Find<AccountViewModel>(accountId);
7169
Console.WriteLine($"\nCurrent Account State:");
7270
Console.WriteLine($"- Account Id: {account?.Id}");
7371
Console.WriteLine($"- Holder: {account?.AccountName}");
@@ -83,7 +81,7 @@
8381
Console.WriteLine($"\nClose Account");
8482

8583
//// Final state
86-
account = await viewRepository.GetByIdAsync<AccountViewModel>(accountId);
84+
account = await accountFinder.Find<AccountViewModel>(accountId);
8785
Console.WriteLine($"\nCurrent Account State:");
8886
Console.WriteLine($"- Account Id: {account?.Id}");
8987
Console.WriteLine($"- Holder: {account?.AccountName}");
Lines changed: 60 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,74 @@
1-
using SourceFlow.ConsoleApp.Events;
1+
//using SourceFlow.ConsoleApp.Events;
22

3-
namespace SourceFlow.ConsoleApp.Projections
4-
{
5-
public class AccountView : BaseDataView<AccountViewModel>,
6-
IProjection<AccountCreated>,
7-
IProjection<MoneyDeposited>,
8-
IProjection<MoneyWithdrawn>,
9-
IProjection<AccountClosed>
10-
{
11-
public async Task ProjectAsync(AccountCreated @event)
12-
{
13-
var view = new AccountViewModel
14-
{
15-
Id = @event.Entity.Id,
16-
AccountName = @event.Payload.AccountName,
17-
CurrentBalance = @event.Payload.InitialAmount,
18-
IsClosed = false,
19-
CreatedDate = @event.OccurredOn,
20-
LastUpdated = @event.OccurredOn,
21-
TransactionCount = 1,
22-
ClosureReason = null,
23-
Version = @event.SequenceNo
24-
};
3+
//namespace SourceFlow.ConsoleApp.Projections
4+
//{
5+
// public class AccountView : BaseDataView<AccountViewModel>,
6+
// IProjection<AccountCreated>,
7+
// IProjection<MoneyDeposited>,
8+
// IProjection<MoneyWithdrawn>,
9+
// IProjection<AccountClosed>
10+
// {
11+
// public async Task ProjectAsync(AccountCreated @event)
12+
// {
13+
// var view = new AccountViewModel
14+
// {
15+
// Id = @event.Entity.Id,
16+
// AccountName = @event.Payload.AccountName,
17+
// CurrentBalance = @event.Payload.InitialAmount,
18+
// IsClosed = false,
19+
// CreatedDate = @event.OccurredOn,
20+
// LastUpdated = @event.OccurredOn,
21+
// TransactionCount = 1,
22+
// ClosureReason = null,
23+
// Version = @event.SequenceNo
24+
// };
2525

26-
await repository.PersistAsync(view);
27-
}
26+
// await repository.PersistAsync(view);
27+
// }
2828

29-
public async Task ProjectAsync(MoneyDeposited @event)
30-
{
31-
var view = await repository.GetByIdAsync<AccountViewModel>(@event.Entity.Id);
29+
// public async Task ProjectAsync(MoneyDeposited @event)
30+
// {
31+
// var view = await repository.GetByIdAsync<AccountViewModel>(@event.Entity.Id);
3232

33-
if (view == null)
34-
throw new InvalidOperationException($"Account view not found for ID: {@event.Entity.Id}");
33+
// if (view == null)
34+
// throw new InvalidOperationException($"Account view not found for ID: {@event.Entity.Id}");
3535

36-
view.CurrentBalance = @event.Payload.CurrentBalance;
37-
view.LastUpdated = @event.OccurredOn;
38-
view.Version = @event.SequenceNo;
39-
view.TransactionCount++;
36+
// view.CurrentBalance = @event.Payload.CurrentBalance;
37+
// view.LastUpdated = @event.OccurredOn;
38+
// view.Version = @event.SequenceNo;
39+
// view.TransactionCount++;
4040

41-
await repository.PersistAsync(view);
42-
}
41+
// await repository.PersistAsync(view);
42+
// }
4343

44-
public async Task ProjectAsync(MoneyWithdrawn @event)
45-
{
46-
var view = await repository.GetByIdAsync<AccountViewModel>(@event.Entity.Id);
44+
// public async Task ProjectAsync(MoneyWithdrawn @event)
45+
// {
46+
// var view = await repository.GetByIdAsync<AccountViewModel>(@event.Entity.Id);
4747

48-
if (view == null)
49-
throw new InvalidOperationException($"Account view not found for ID: {@event.Entity.Id}");
48+
// if (view == null)
49+
// throw new InvalidOperationException($"Account view not found for ID: {@event.Entity.Id}");
5050

51-
view.CurrentBalance = @event.Payload.CurrentBalance;
52-
view.LastUpdated = @event.OccurredOn;
53-
view.Version = @event.SequenceNo;
54-
view.TransactionCount++;
51+
// view.CurrentBalance = @event.Payload.CurrentBalance;
52+
// view.LastUpdated = @event.OccurredOn;
53+
// view.Version = @event.SequenceNo;
54+
// view.TransactionCount++;
5555

56-
await repository.PersistAsync(view);
57-
}
56+
// await repository.PersistAsync(view);
57+
// }
5858

59-
public async Task ProjectAsync(AccountClosed @event)
60-
{
61-
var view = await repository.GetByIdAsync<AccountViewModel>(@event.Entity.Id);
59+
// public async Task ProjectAsync(AccountClosed @event)
60+
// {
61+
// var view = await repository.GetByIdAsync<AccountViewModel>(@event.Entity.Id);
6262

63-
if (view == null)
64-
throw new InvalidOperationException($"Account view not found for ID: {@event.Entity.Id}");
63+
// if (view == null)
64+
// throw new InvalidOperationException($"Account view not found for ID: {@event.Entity.Id}");
6565

66-
view.ClosureReason = @event.Payload.ClosureReason;
67-
view.LastUpdated = @event.OccurredOn;
68-
view.Version = @event.SequenceNo;
69-
view.IsClosed = @event.Payload.IsClosed;
66+
// view.ClosureReason = @event.Payload.ClosureReason;
67+
// view.LastUpdated = @event.OccurredOn;
68+
// view.Version = @event.SequenceNo;
69+
// view.IsClosed = @event.Payload.IsClosed;
7070

71-
await repository.PersistAsync(view);
72-
}
73-
}
74-
}
71+
// await repository.PersistAsync(view);
72+
// }
73+
// }
74+
//}

src/SourceFlow.ConsoleApp/Services/AccountViewFinder.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
using SourceFlow.ConsoleApp.Projections;
1+
using SourceFlow.ConsoleApp.Projections;
22

33
namespace SourceFlow.ConsoleApp.Services
44
{
55
public class AccountViewFinder : BaseViewModelFinder, IAccountFinder
66
{
7+
public AccountViewFinder(IViewModelRepository repository) : base(repository)
8+
{
9+
}
10+
711
public async Task<AccountViewModel> GetAccountSummaryAsync(int accountId)
812
{
913
if (accountId <= 0)
1014
throw new ArgumentException("Account summary requires valid account id", nameof(accountId));
11-
var summary = await FindProjection<AccountViewModel>(accountId);
15+
var summary = await Find<AccountViewModel>(accountId);
1216
if (summary == null)
1317
throw new InvalidOperationException($"No account found with ID {accountId}");
1418
return summary;

src/SourceFlow/AggregateFactory.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.Extensions.DependencyInjection;
4+
5+
namespace SourceFlow
6+
{
7+
/// <summary>
8+
/// Factory for creating aggregate roots in the event-driven architecture.
9+
/// </summary>
10+
public class AggregateFactory : IAggregateFactory
11+
{
12+
/// <summary>
13+
/// Service provider for resolving dependencies.
14+
/// </summary>
15+
private readonly IServiceProvider serviceProvider;
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="AggregateFactory"/> class.
19+
/// </summary>
20+
/// <param name="serviceProvider"></param>
21+
public AggregateFactory(IServiceProvider serviceProvider)
22+
{
23+
this.serviceProvider = serviceProvider;
24+
}
25+
26+
/// <summary>
27+
/// Creates a singleton instance of an aggregate root with the specified state.
28+
/// </summary>
29+
/// <typeparam name="TAggregateRoot"></typeparam>
30+
/// <returns></returns>
31+
public async Task<TAggregateRoot> CreateAsync<TAggregateRoot>()
32+
where TAggregateRoot : IAggregateRoot
33+
{
34+
// Resolve the aggregate root from the container
35+
var aggregate = serviceProvider.GetService<IAggregateRoot>();
36+
return await Task.FromResult((TAggregateRoot)aggregate);
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)