Skip to content

Commit a443b49

Browse files
authored
Merge pull request #16 from THC-Software/projection_extension
Projection extension
2 parents 277cee0 + f3a7093 commit a443b49

20 files changed

+690
-117
lines changed

Application/Handlers/Events/ClubEventHandler.cs

+18-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using MediatR;
2-
using PlayOfferService.Application.Exceptions;
32
using PlayOfferService.Domain.Events;
43
using PlayOfferService.Domain.Events.PlayOffer;
54
using PlayOfferService.Domain.Models;
@@ -13,15 +12,15 @@ public class ClubEventHandler : IRequestHandler<TechnicalClubEvent>
1312
private readonly ReadEventRepository _readEventRepository;
1413
private readonly WriteEventRepository _writeEventRepository;
1514
private readonly PlayOfferRepository _playOfferRepository;
16-
15+
1716
public ClubEventHandler(ClubRepository clubRepository, ReadEventRepository readEventRepository, WriteEventRepository writeEventRepository, PlayOfferRepository playOfferRepository)
1817
{
1918
_clubRepository = clubRepository;
2019
_readEventRepository = readEventRepository;
2120
_writeEventRepository = writeEventRepository;
2221
_playOfferRepository = playOfferRepository;
2322
}
24-
23+
2524
public async Task Handle(TechnicalClubEvent clubEvent, CancellationToken cancellationToken)
2625
{
2726
Console.WriteLine("ClubEventHandler received event: " + clubEvent.EventType);
@@ -31,7 +30,7 @@ public async Task Handle(TechnicalClubEvent clubEvent, CancellationToken cancell
3130
Console.WriteLine("Event already applied, skipping");
3231
return;
3332
}
34-
33+
3534
switch (clubEvent.EventType)
3635
{
3736
case EventType.TENNIS_CLUB_REGISTERED:
@@ -46,8 +45,11 @@ public async Task Handle(TechnicalClubEvent clubEvent, CancellationToken cancell
4645
case EventType.TENNIS_CLUB_DELETED:
4746
await HandleTennisClubDeletedEvent(clubEvent);
4847
break;
48+
case EventType.TENNIS_CLUB_NAME_CHANGED:
49+
await HandleTennisClubNameChangedEvent(clubEvent);
50+
break;
4951
}
50-
52+
5153
await _clubRepository.Update();
5254
await _readEventRepository.AppendEvent(clubEvent);
5355
await _readEventRepository.Update();
@@ -56,7 +58,7 @@ public async Task Handle(TechnicalClubEvent clubEvent, CancellationToken cancell
5658
private async Task HandleTennisClubDeletedEvent(TechnicalClubEvent clubEvent)
5759
{
5860
await CreatePlayOfferCancelledEventsByClubId(clubEvent);
59-
61+
6062
var existingClub = await _clubRepository.GetClubById(clubEvent.EntityId);
6163
existingClub!.Apply([clubEvent]);
6264
}
@@ -70,7 +72,7 @@ private async Task HandleTennisClubUnlockedEvent(TechnicalClubEvent clubEvent)
7072
private async Task HandleTennisClubLockedEvent(TechnicalClubEvent clubEvent)
7173
{
7274
await CreatePlayOfferCancelledEventsByClubId(clubEvent);
73-
75+
7476
var existingClub = await _clubRepository.GetClubById(clubEvent.EntityId);
7577
existingClub!.Apply([clubEvent]);
7678
}
@@ -81,12 +83,12 @@ private async Task HandleTennisClubRegisteredEvent(TechnicalClubEvent clubEvent)
8183
newClub.Apply([clubEvent]);
8284
_clubRepository.CreateClub(newClub);
8385
}
84-
86+
8587
private async Task CreatePlayOfferCancelledEventsByClubId(TechnicalClubEvent clubEvent)
8688
{
8789
// Get all play offers by club id
8890
var existingPlayOffer = await _playOfferRepository.GetPlayOffersByIds(null, null, clubEvent.EntityId);
89-
91+
9092
// Create PlayOfferCancelled events for each play offer
9193
foreach (var playOffer in existingPlayOffer)
9294
{
@@ -100,9 +102,15 @@ private async Task CreatePlayOfferCancelledEventsByClubId(TechnicalClubEvent clu
100102
EventData = new PlayOfferCancelledEvent(),
101103
CorrelationId = clubEvent.EventId
102104
};
103-
105+
104106
await _writeEventRepository.AppendEvent(cancelledEvent);
105107
}
106108
await _writeEventRepository.Update();
107109
}
110+
111+
private async Task HandleTennisClubNameChangedEvent(TechnicalClubEvent clubEvent)
112+
{
113+
var existingClub = await _clubRepository.GetClubById(clubEvent.EntityId);
114+
existingClub!.Apply([clubEvent]);
115+
}
108116
}

Application/Handlers/Events/MemberEventHandler.cs

+26-8
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ public class MemberEventHandler : IRequestHandler<TechnicalMemberEvent>
1313
private readonly ReadEventRepository _eventRepository;
1414
private readonly WriteEventRepository _writeEventRepository;
1515
private readonly PlayOfferRepository _playOfferRepository;
16-
16+
1717
public MemberEventHandler(MemberRepository memberRepository, ReadEventRepository eventRepository, PlayOfferRepository playOfferRepository, WriteEventRepository writeEventRepository)
1818
{
1919
_memberRepository = memberRepository;
2020
_eventRepository = eventRepository;
2121
_playOfferRepository = playOfferRepository;
2222
_writeEventRepository = writeEventRepository;
2323
}
24-
24+
2525
public async Task Handle(TechnicalMemberEvent memberEvent, CancellationToken cancellationToken)
2626
{
2727
Console.WriteLine("MemberEventHandler received event: " + memberEvent.EventType);
@@ -31,7 +31,7 @@ public async Task Handle(TechnicalMemberEvent memberEvent, CancellationToken can
3131
Console.WriteLine("Event already applied, skipping");
3232
return;
3333
}
34-
34+
3535
switch (memberEvent.EventType)
3636
{
3737
case EventType.MEMBER_REGISTERED:
@@ -46,8 +46,14 @@ public async Task Handle(TechnicalMemberEvent memberEvent, CancellationToken can
4646
case EventType.MEMBER_DELETED:
4747
await HandleMemberDeletedEvent(memberEvent);
4848
break;
49+
case EventType.MEMBER_FULL_NAME_CHANGED:
50+
await HandleMemberFullNameChangedEvent(memberEvent);
51+
break;
52+
case EventType.MEMBER_EMAIL_CHANGED:
53+
await HandleMemberEmailChangedEvent(memberEvent);
54+
break;
4955
}
50-
56+
5157
await _memberRepository.Update();
5258
await _eventRepository.AppendEvent(memberEvent);
5359
await _eventRepository.Update();
@@ -56,7 +62,7 @@ public async Task Handle(TechnicalMemberEvent memberEvent, CancellationToken can
5662
private async Task HandleMemberDeletedEvent(TechnicalMemberEvent memberEvent)
5763
{
5864
await CreatePlayOfferCancelledEventsByCreatorId(memberEvent);
59-
65+
6066
var existingMember = await _memberRepository.GetMemberById(memberEvent.EntityId);
6167
existingMember!.Apply([memberEvent]);
6268
}
@@ -70,7 +76,7 @@ private async Task HandleMemberUnlockedEvent(TechnicalMemberEvent memberEvent)
7076
private async Task HandleMemberLockedEvent(TechnicalMemberEvent memberEvent)
7177
{
7278
await CreatePlayOfferCancelledEventsByCreatorId(memberEvent);
73-
79+
7480
var existingMember = await _memberRepository.GetMemberById(memberEvent.EntityId);
7581
existingMember!.Apply([memberEvent]);
7682
}
@@ -86,7 +92,7 @@ private async Task CreatePlayOfferCancelledEventsByCreatorId(TechnicalMemberEven
8692
{
8793
// Get all play offers by creator id
8894
var existingPlayOffer = await _playOfferRepository.GetPlayOffersByIds(null, memberEvent.EntityId);
89-
95+
9096
// Create PlayOfferCancelled events for each play offer
9197
foreach (var playOffer in existingPlayOffer)
9298
{
@@ -100,9 +106,21 @@ private async Task CreatePlayOfferCancelledEventsByCreatorId(TechnicalMemberEven
100106
EventData = new PlayOfferCancelledEvent(),
101107
CorrelationId = memberEvent.EventId
102108
};
103-
109+
104110
await _writeEventRepository.AppendEvent(cancelledEvent);
105111
}
106112
await _writeEventRepository.Update();
107113
}
114+
115+
private async Task HandleMemberFullNameChangedEvent(TechnicalMemberEvent memberEvent)
116+
{
117+
var existingMember = await _memberRepository.GetMemberById(memberEvent.EntityId);
118+
existingMember!.Apply([memberEvent]);
119+
}
120+
121+
private async Task HandleMemberEmailChangedEvent(TechnicalMemberEvent memberEvent)
122+
{
123+
var existingMember = await _memberRepository.GetMemberById(memberEvent.EntityId);
124+
existingMember!.Apply([memberEvent]);
125+
}
108126
}

Application/RedisClubStreamService.cs

+13-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
using System.Text.Json.Nodes;
21
using MediatR;
32
using PlayOfferService.Domain.Events;
4-
using PlayOfferService.Domain.Repositories;
53
using StackExchange.Redis;
4+
using System.Text.Json.Nodes;
65

76
namespace PlayOfferService.Application;
87

@@ -13,8 +12,8 @@ public class RedisClubStreamService : BackgroundService
1312
private readonly IDatabase _db;
1413
private const string StreamName = "club_service_events.public.DomainEvent";
1514
private const string GroupName = "pos.club.events.group";
16-
17-
15+
16+
1817
public RedisClubStreamService(IServiceScopeFactory serviceScopeFactory)
1918
{
2019
_serviceScopeFactory = serviceScopeFactory;
@@ -28,13 +27,13 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
2827
{
2928
using IServiceScope scope = _serviceScopeFactory.CreateScope();
3029
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
31-
30+
3231
if (!(await _db.KeyExistsAsync(StreamName)) ||
33-
(await _db.StreamGroupInfoAsync(StreamName)).All(x=>x.Name!=GroupName))
32+
(await _db.StreamGroupInfoAsync(StreamName)).All(x => x.Name != GroupName))
3433
{
3534
await _db.StreamCreateConsumerGroupAsync(StreamName, GroupName, "0-0");
3635
}
37-
36+
3837

3938
var id = string.Empty;
4039
while (!_cancellationToken.IsCancellationRequested)
@@ -58,24 +57,26 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
5857
}
5958

6059
}
61-
60+
6261
private TechnicalClubEvent? FilterandParseEvent(StreamEntry value)
6362
{
6463
var dict = value.Values.ToDictionary(x => x.Name.ToString(), x => x.Value.ToString());
6564
var jsonContent = JsonNode.Parse(dict.Values.First());
6665
var eventInfo = jsonContent["payload"]["after"];
67-
66+
6867
var eventType = eventInfo["eventType"].GetValue<string>();
6968
var entityType = eventInfo["entityType"].GetValue<string>();
70-
69+
7170
if ((eventType != "TENNIS_CLUB_REGISTERED"
7271
&& eventType != "TENNIS_CLUB_LOCKED"
7372
&& eventType != "TENNIS_CLUB_UNLOCKED"
74-
&& eventType != "TENNIS_CLUB_DELETED") || entityType != "TENNIS_CLUB")
73+
&& eventType != "TENNIS_CLUB_DELETED"
74+
&& eventType != "TENNIS_CLUB_NAME_CHANGED"
75+
) || entityType != "TENNIS_CLUB")
7576
{
7677
return null;
7778
}
78-
79+
7980
return EventParser.ParseEvent<TechnicalClubEvent>(eventInfo);
8081
}
8182
}

Application/RedisMemberStreamService.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,16 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
6767
var eventType = eventInfo["eventType"].GetValue<string>();
6868
var entityType = eventInfo["entityType"].GetValue<string>();
6969

70-
if ((eventType != "MEMBER_REGISTERED"
70+
if (
71+
(eventType != "MEMBER_REGISTERED"
7172
&& eventType != "MEMBER_DELETED"
7273
&& eventType != "MEMBER_LOCKED"
73-
&& eventType != "MEMBER_UNLOCKED") || entityType != "MEMBER")
74+
&& eventType != "MEMBER_UNLOCKED"
75+
&& eventType != "MEMBER_EMAIL_CHANGED"
76+
&& eventType != "MEMBER_FULL_NAME_CHANGED"
77+
)
78+
|| entityType != "MEMBER"
79+
)
7480
{
7581
return null;
7682
}

Domain/DbReadContext.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,28 @@ public class DbReadContext : DbContext
1010
public DbReadContext(DbContextOptions<DbReadContext> options) : base(options)
1111
{
1212
}
13-
13+
1414
public DbSet<PlayOffer> PlayOffers { get; set; }
1515
public DbSet<Club> Clubs { get; set; }
1616
public DbSet<Member> Members { get; set; }
1717
public DbSet<Reservation> Reservations { get; set; }
1818
public DbSet<Court> Courts { get; set; }
1919
public DbSet<BaseEvent> AppliedEvents { get; set; }
20-
20+
2121
protected override void OnModelCreating(ModelBuilder modelBuilder)
2222
{
2323
base.OnModelCreating(modelBuilder);
2424
modelBuilder.ApplyConfiguration(new BaseEventConfiguration());
25-
25+
2626
// TODO: Remove before coop testing
27-
var testClub = new Club{Id = Guid.Parse("06b812a7-5131-4510-82ff-bffac33e0f3e"), Status = Status.ACTIVE};
28-
var testMemberIds = new List<Guid> {Guid.Parse("40c0981d-e2f8-4af3-ae6c-17f79f3ba8c2"), Guid.Parse("ccc1c8fc-89b5-4026-b190-9d9e7e7bc18d")};
29-
27+
var testClub = new Club { Id = Guid.Parse("06b812a7-5131-4510-82ff-bffac33e0f3e"), Name = "Test Club", Status = Status.ACTIVE };
28+
var testMemberIds = new List<Guid> { Guid.Parse("40c0981d-e2f8-4af3-ae6c-17f79f3ba8c2"), Guid.Parse("ccc1c8fc-89b5-4026-b190-9d9e7e7bc18d") };
29+
3030
var testMembers = new List<Object>{
3131
new {Id = testMemberIds[0], ClubId = testClub.Id, Status = Status.ACTIVE},
3232
new {Id = testMemberIds[1], ClubId = testClub.Id, Status = Status.ACTIVE}
3333
};
34-
34+
3535
// Need to directly specify foreign keys for seeding
3636
var testPlayOffer = new
3737
{
@@ -42,7 +42,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
4242
ProposedEndTime = DateTime.UtcNow.AddHours(1),
4343
IsCancelled = false
4444
};
45-
45+
4646
modelBuilder.Entity<Club>().HasData(testClub);
4747
foreach (var testMember in testMembers)
4848
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace PlayOfferService.Domain.Events.Club;
2+
3+
public class ClubNameChangedEvent : DomainEvent
4+
{
5+
public string Name { get; set; }
6+
}

Domain/Events/DomainEvent.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
using System.Text.Json.Serialization;
1+
using PlayOfferService.Domain.Events.Club;
22
using PlayOfferService.Domain.Events.Court;
33
using PlayOfferService.Domain.Events.Member;
44
using PlayOfferService.Domain.Events.PlayOffer;
55
using PlayOfferService.Domain.Events.Reservation;
6+
using System.Text.Json.Serialization;
67

78
namespace PlayOfferService.Domain.Events;
89

@@ -11,10 +12,13 @@ namespace PlayOfferService.Domain.Events;
1112
[JsonDerivedType(typeof(ClubLockedEvent), typeDiscriminator: "TENNIS_CLUB_LOCKED")]
1213
[JsonDerivedType(typeof(ClubUnlockedEvent), typeDiscriminator: "TENNIS_CLUB_UNLOCKED")]
1314
[JsonDerivedType(typeof(ClubDeletedEvent), typeDiscriminator: "TENNIS_CLUB_DELETED")]
15+
[JsonDerivedType(typeof(ClubNameChangedEvent), typeDiscriminator: "TENNIS_CLUB_NAME_CHANGED")]
1416
[JsonDerivedType(typeof(MemberCreatedEvent), typeDiscriminator: "MEMBER_REGISTERED")]
1517
[JsonDerivedType(typeof(MemberLockedEvent), typeDiscriminator: "MEMBER_LOCKED")]
1618
[JsonDerivedType(typeof(MemberUnlockedEvent), typeDiscriminator: "MEMBER_UNLOCKED")]
1719
[JsonDerivedType(typeof(MemberDeletedEvent), typeDiscriminator: "MEMBER_DELETED")]
20+
[JsonDerivedType(typeof(MemberEmailChangedEvent), typeDiscriminator: "MEMBER_EMAIL_CHANGED")]
21+
[JsonDerivedType(typeof(MemberFullNameChangedEvent), typeDiscriminator: "MEMBER_FULL_NAME_CHANGED")]
1822
[JsonDerivedType(typeof(PlayOfferCreatedEvent), typeDiscriminator: "PLAYOFFER_CREATED")]
1923
[JsonDerivedType(typeof(PlayOfferJoinedEvent), typeDiscriminator: "PLAYOFFER_JOINED")]
2024
[JsonDerivedType(typeof(PlayOfferCancelledEvent), typeDiscriminator: "PLAYOFFER_CANCELLED")]

Domain/Events/EventType.cs

+3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ public enum EventType
1111
TENNIS_CLUB_LOCKED,
1212
TENNIS_CLUB_UNLOCKED,
1313
TENNIS_CLUB_DELETED,
14+
TENNIS_CLUB_NAME_CHANGED,
1415
MEMBER_REGISTERED,
1516
MEMBER_LOCKED,
1617
MEMBER_UNLOCKED,
1718
MEMBER_DELETED,
19+
MEMBER_EMAIL_CHANGED,
20+
MEMBER_FULL_NAME_CHANGED,
1821
ReservationCreatedEvent,
1922
ReservationRejectedEvent,
2023
ReservationLimitExceeded,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace PlayOfferService.Domain.Events.Member;
2+
3+
public class MemberEmailChangedEvent : DomainEvent
4+
{
5+
public string Email { get; set; }
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using PlayOfferService.Domain.ValueObjects;
2+
3+
namespace PlayOfferService.Domain.Events.Member;
4+
5+
public class MemberFullNameChangedEvent : DomainEvent
6+
{
7+
public FullName FullName { get; set; }
8+
}

0 commit comments

Comments
 (0)