Skip to content

Commit 4e871bc

Browse files
authored
Merge pull request #20 from THC-Software/optimistic-locking
Optimistic locking
2 parents ff42138 + 27f65cd commit 4e871bc

File tree

4 files changed

+73
-25
lines changed

4 files changed

+73
-25
lines changed

Application/Handlers/CancelPlayOfferHandler.cs

+21-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using MediatR;
22
using PlayOfferService.Application.Commands;
33
using PlayOfferService.Application.Exceptions;
4-
using PlayOfferService.Domain;
54
using PlayOfferService.Domain.Events;
65
using PlayOfferService.Domain.Events.PlayOffer;
76
using PlayOfferService.Domain.Models;
@@ -14,7 +13,7 @@ public class CancelPlayOfferHandler : IRequestHandler<CancelPlayOfferCommand, Ta
1413
private readonly WriteEventRepository _writeEventRepository;
1514
private readonly PlayOfferRepository _playOfferRepository;
1615
private readonly ClubRepository _clubRepository;
17-
16+
1817
public CancelPlayOfferHandler(WriteEventRepository writeEventRepository, PlayOfferRepository playOfferRepository, ClubRepository clubRepository)
1918
{
2019
_writeEventRepository = writeEventRepository;
@@ -24,30 +23,33 @@ public CancelPlayOfferHandler(WriteEventRepository writeEventRepository, PlayOff
2423

2524
public async Task<Task> Handle(CancelPlayOfferCommand request, CancellationToken cancellationToken)
2625
{
26+
var transaction = _writeEventRepository.StartTransaction();
27+
var excpectedEventCount = _writeEventRepository.GetEventCount(request.PlayOfferId) + 1;
28+
2729
var existingPlayOffer = (await _playOfferRepository.GetPlayOffersByIds(request.PlayOfferId)).FirstOrDefault();
2830
if (existingPlayOffer == null)
2931
throw new NotFoundException($"PlayOffer {request.PlayOfferId} not found!");
3032
if (existingPlayOffer.CreatorId != request.MemberId)
3133
throw new AuthorizationException($"PlayOffer {request.PlayOfferId} can only be cancelled by creator!");
32-
34+
3335
if (existingPlayOffer.OpponentId != null)
3436
throw new InvalidOperationException($"PlayOffer {request.PlayOfferId} is already accepted and cannot be cancelled!");
3537
if (existingPlayOffer.IsCancelled)
3638
throw new InvalidOperationException($"PlayOffer {request.PlayOfferId} is already cancelled!");
37-
39+
3840
var existingClub = await _clubRepository.GetClubById(existingPlayOffer.ClubId);
3941
if (existingClub == null)
4042
throw new NotFoundException($"Club {existingPlayOffer.ClubId} not found!");
41-
42-
43+
44+
4345
switch (existingClub.Status)
4446
{
4547
case Status.LOCKED:
4648
throw new InvalidOperationException("Can't cancel PlayOffer while club is locked!");
4749
case Status.DELETED:
4850
throw new InvalidOperationException("Can't cancel PlayOffer in deleted club!");
4951
}
50-
52+
5153
var domainEvent = new BaseEvent
5254
{
5355
EntityId = request.PlayOfferId,
@@ -57,9 +59,20 @@ public async Task<Task> Handle(CancelPlayOfferCommand request, CancellationToken
5759
EventData = new PlayOfferCancelledEvent(),
5860
Timestamp = DateTime.UtcNow
5961
};
60-
62+
6163
await _writeEventRepository.AppendEvent(domainEvent);
6264
await _writeEventRepository.Update();
65+
66+
67+
var eventCount = _writeEventRepository.GetEventCount(request.PlayOfferId);
68+
69+
if (eventCount != excpectedEventCount)
70+
{
71+
transaction.Rollback();
72+
throw new InvalidOperationException("Concurrent modification detected!");
73+
}
74+
75+
transaction.Commit();
6376

6477
return Task.CompletedTask;
6578
}

Application/Handlers/CreatePlayOfferHandler.cs

+23-10
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
using MediatR;
22
using PlayOfferService.Application.Commands;
33
using PlayOfferService.Application.Exceptions;
4-
using PlayOfferService.Domain;
54
using PlayOfferService.Domain.Events;
65
using PlayOfferService.Domain.Models;
76
using PlayOfferService.Domain.Repositories;
87

98
namespace PlayOfferService.Application.Handlers;
9+
1010
public class CreatePlayOfferHandler : IRequestHandler<CreatePlayOfferCommand, Guid>
1111
{
12-
13-
private readonly WriteEventRepository _writeEventRepository;
1412
private readonly ClubRepository _clubRepository;
1513
private readonly MemberRepository _memberRepository;
1614

17-
public CreatePlayOfferHandler(WriteEventRepository writeEventRepository, ClubRepository clubRepository, MemberRepository memberRepository)
15+
private readonly WriteEventRepository _writeEventRepository;
16+
17+
public CreatePlayOfferHandler(WriteEventRepository writeEventRepository, ClubRepository clubRepository,
18+
MemberRepository memberRepository)
1819
{
1920
_writeEventRepository = writeEventRepository;
2021
_clubRepository = clubRepository;
@@ -24,9 +25,9 @@ public CreatePlayOfferHandler(WriteEventRepository writeEventRepository, ClubRep
2425
public async Task<Guid> Handle(CreatePlayOfferCommand request, CancellationToken cancellationToken)
2526
{
2627
var playOfferDto = request.CreatePlayOfferDto;
27-
28+
2829
var club = await _clubRepository.GetClubById(request.ClubId);
29-
if(club == null)
30+
if (club == null)
3031
throw new NotFoundException($"Club {request.ClubId} not found");
3132
switch (club.Status)
3233
{
@@ -35,9 +36,9 @@ public async Task<Guid> Handle(CreatePlayOfferCommand request, CancellationToken
3536
case Status.DELETED:
3637
throw new InvalidOperationException("Can't create PlayOffer in deleted club!");
3738
}
38-
39+
3940
var creator = await _memberRepository.GetMemberById(request.CreatorId);
40-
if(creator == null)
41+
if (creator == null)
4142
throw new NotFoundException($"Member {request.CreatorId} not found!");
4243
switch (creator.Status)
4344
{
@@ -65,10 +66,22 @@ public async Task<Guid> Handle(CreatePlayOfferCommand request, CancellationToken
6566
Timestamp = DateTime.Now.ToUniversalTime()
6667
};
6768

69+
var transaction = _writeEventRepository.StartTransaction();
70+
var excpectedEventCount = _writeEventRepository.GetEventCount(playOfferId) + 1;
71+
6872
await _writeEventRepository.AppendEvent(domainEvent);
6973
await _writeEventRepository.Update();
7074

75+
var eventCount = _writeEventRepository.GetEventCount(playOfferId);
76+
77+
if (eventCount != excpectedEventCount)
78+
{
79+
transaction.Rollback();
80+
throw new InvalidOperationException("Concurrent modification detected!");
81+
}
82+
83+
transaction.Commit();
84+
7185
return playOfferId;
7286
}
73-
74-
}
87+
}

Application/Handlers/JoinPlayOfferHandler.cs

+11
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ public JoinPlayOfferHandler(WriteEventRepository writeEventRepository, PlayOffer
2626

2727
public async Task<Task> Handle(JoinPlayOfferCommand request, CancellationToken cancellationToken)
2828
{
29+
var transaction = _writeEventRepository.StartTransaction();
30+
var excpectedEventCount = _writeEventRepository.GetEventCount(request.JoinPlayOfferDto.PlayOfferId) + 1;
31+
2932
var existingPlayOffer = (await _playOfferRepository.GetPlayOffersByIds(request.JoinPlayOfferDto.PlayOfferId)).FirstOrDefault();
3033
if (existingPlayOffer == null)
3134
throw new NotFoundException($"PlayOffer {request.JoinPlayOfferDto.PlayOfferId} not found!");
@@ -81,6 +84,14 @@ public async Task<Task> Handle(JoinPlayOfferCommand request, CancellationToken c
8184

8285
await _writeEventRepository.AppendEvent(domainEvent);
8386
await _writeEventRepository.Update();
87+
88+
var eventCount = _writeEventRepository.GetEventCount(request.JoinPlayOfferDto.PlayOfferId);
89+
90+
if (eventCount != excpectedEventCount)
91+
{
92+
transaction.Rollback();
93+
throw new InvalidOperationException("Concurrent modification detected!");
94+
}
8495

8596
return Task.CompletedTask;
8697
}
+18-7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.EntityFrameworkCore.Storage;
23
using PlayOfferService.Domain.Events;
34

45
namespace PlayOfferService.Domain.Repositories;
56

67
public class WriteEventRepository
78
{
89
private readonly DbWriteContext _context;
9-
10-
public WriteEventRepository(){}
11-
10+
11+
public WriteEventRepository() { }
12+
1213
public WriteEventRepository(DbWriteContext context)
1314
{
1415
_context = context;
1516
}
16-
17+
1718
public virtual async Task<BaseEvent?> GetEventById(Guid eventId)
1819
{
1920
var events = await _context.Events
@@ -25,7 +26,7 @@ public WriteEventRepository(DbWriteContext context)
2526

2627
return events.First();
2728
}
28-
29+
2930
public virtual async Task<List<BaseEvent>> GetEventByEntityId(Guid entityId)
3031
{
3132
var events = await _context.Events
@@ -34,14 +35,24 @@ public virtual async Task<List<BaseEvent>> GetEventByEntityId(Guid entityId)
3435

3536
return events;
3637
}
37-
38+
3839
public virtual async Task AppendEvent(BaseEvent baseEvent)
3940
{
4041
_context.Events.Add(baseEvent);
4142
}
42-
43+
4344
public virtual async Task Update()
4445
{
4546
await _context.SaveChangesAsync();
4647
}
48+
49+
internal int GetEventCount(Guid playOfferId)
50+
{
51+
return _context.Events.Count(e => e.EntityId == playOfferId);
52+
}
53+
54+
internal IDbContextTransaction StartTransaction()
55+
{
56+
return _context.Database.BeginTransaction();
57+
}
4758
}

0 commit comments

Comments
 (0)