Skip to content

Commit d762c2a

Browse files
committed
Added proper optimistic concurrency check to Simple with EventStoreDB sample
1 parent 3704526 commit d762c2a

File tree

12 files changed

+88
-25
lines changed

12 files changed

+88
-25
lines changed

Sample/EventStoreDB/Simple/ECommerce.Api.Tests/ShoppingCarts/Confirming/ConfirmShoppingCartTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public override async Task InitializeAsync()
2828

2929
ShoppingCartId = await initializeResponse.GetResultFromJson<Guid>();
3030

31-
CommandResponse = await Put($"{ShoppingCartId}/confirmation");
31+
CommandResponse = await Put($"{ShoppingCartId}/confirmation", new ConfirmShoppingCartRequest(0));
3232
}
3333
}
3434

@@ -60,7 +60,7 @@ public async Task Put_Should_Confirm_ShoppingCart()
6060

6161
//send query
6262
var queryResponse = await fixture.Get(query, 30,
63-
check: async response => (await response.GetResultFromJson<ShoppingCartDetails>())?.Version != 1);
63+
check: async response => (await response.GetResultFromJson<ShoppingCartDetails>())?.Version == 1);
6464

6565
queryResponse.EnsureSuccessStatusCode();
6666

@@ -69,7 +69,7 @@ public async Task Put_Should_Confirm_ShoppingCart()
6969
cartDetails.Id.Should().Be(fixture.ShoppingCartId);
7070
cartDetails.Status.Should().Be(ShoppingCartStatus.Confirmed);
7171
cartDetails.ClientId.Should().Be(fixture.ClientId);
72-
cartDetails.Version.Should().Be(2);
72+
cartDetails.Version.Should().Be(1);
7373
}
7474
}
7575
}

Sample/EventStoreDB/Simple/ECommerce.Api.Tests/ShoppingCarts/Initializing/InitializeShoppingCartTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public async Task Post_Should_Create_ShoppingCart()
6565
cartDetails.Id.Should().Be(createdId);
6666
cartDetails.Status.Should().Be(ShoppingCartStatus.Pending);
6767
cartDetails.ClientId.Should().Be(fixture.ClientId);
68-
cartDetails.Version.Should().Be(1);
68+
cartDetails.Version.Should().Be(0);
6969
}
7070
}
7171
}

Sample/EventStoreDB/Simple/ECommerce.Api/Controllers/ShoppingCartsController.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ CancellationToken ct
5656
ProductItem.From(
5757
request.ProductItem?.ProductId,
5858
request.ProductItem?.Quantity
59-
)
59+
),
60+
request.Version
6061
);
6162

6263
await handle(command, ct);
@@ -83,7 +84,8 @@ CancellationToken ct
8384
request.ProductItem?.Quantity
8485
),
8586
request.ProductItem?.UnitPrice
86-
)
87+
),
88+
request.Version
8789
);
8890

8991
await handle(command, ct);
@@ -95,11 +97,15 @@ CancellationToken ct
9597
public async Task<IActionResult> ConfirmCart(
9698
[FromServices] Func<ConfirmShoppingCart, CancellationToken, ValueTask> handle,
9799
Guid id,
100+
[FromBody] ConfirmShoppingCartRequest request,
98101
CancellationToken ct
99102
)
100103
{
104+
if (request == null)
105+
throw new ArgumentNullException(nameof(request));
106+
101107
var command =
102-
ConfirmShoppingCart.From(id);
108+
ConfirmShoppingCart.From(id, request.Version);
103109

104110
await handle(command, ct);
105111

Sample/EventStoreDB/Simple/ECommerce.Api/Requests/ShoppingCartsRequests.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public class ProductItemRequest
1515

1616
public record AddProductRequest(
1717
Guid? ShoppingCartId,
18-
ProductItemRequest? ProductItem
18+
ProductItemRequest? ProductItem,
19+
uint? Version
1920
);
2021

2122
public record PricedProductItemRequest(
@@ -26,6 +27,11 @@ public record PricedProductItemRequest(
2627

2728
public record RemoveProductRequest(
2829
Guid? ShoppingCartId,
29-
PricedProductItemRequest? ProductItem
30+
PricedProductItemRequest? ProductItem,
31+
uint? Version
32+
);
33+
34+
public record ConfirmShoppingCartRequest(
35+
uint? Version
3036
);
3137
}

Sample/EventStoreDB/Simple/ECommerce.Core/Commands/CommandHandler.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static async Task HandleUpdateCommand<TCommand, TEntity>(
2626
IEventStoreDBRepository<TEntity> repository,
2727
Func<TCommand, TEntity, object> handle,
2828
Func<TCommand, string> getId,
29+
Func<TCommand, uint> getVersion,
2930
Func<TEntity?, object, TEntity> when,
3031
TCommand command,
3132
CancellationToken ct
@@ -36,7 +37,7 @@ CancellationToken ct
3637

3738
var @event = handle(command, entity);
3839

39-
await repository.Append(id, @event, ct);
40+
await repository.Append(id, @event, getVersion(command), ct);
4041
}
4142

4243
public static IServiceCollection AddCreateCommandHandler<TCommand, TEntity>(
@@ -65,22 +66,24 @@ public static IServiceCollection AddUpdateCommandHandler<TCommand, TEntity>(
6566
this IServiceCollection services,
6667
Func<TCommand, TEntity, object> handle,
6768
Func<TCommand, string> getId,
69+
Func<TCommand, uint> getVersion,
6870
Func<TEntity?, object, TEntity> when
6971
) where TEntity : notnull
70-
=> AddUpdateCommandHandler(services, _ => handle, getId, when);
72+
=> AddUpdateCommandHandler(services, _ => handle, getId, getVersion, when);
7173

7274
public static IServiceCollection AddUpdateCommandHandler<TCommand, TEntity>(
7375
this IServiceCollection services,
7476
Func<IServiceProvider, Func<TCommand, TEntity, object>> handle,
7577
Func<TCommand, string> getId,
78+
Func<TCommand, uint> getVersion,
7679
Func<TEntity?, object, TEntity> when
7780
) where TEntity : notnull
7881
=> services
7982
.AddTransient<Func<TCommand, CancellationToken, ValueTask>>(sp =>
8083
{
8184
var repository = sp.GetRequiredService<IEventStoreDBRepository<TEntity>>();
8285
return async (command, ct) =>
83-
await HandleUpdateCommand(repository, handle(sp), getId, when, command, ct);
86+
await HandleUpdateCommand(repository, handle(sp), getId, getVersion, when, command, ct);
8487
});
8588

8689

Sample/EventStoreDB/Simple/ECommerce.Core/Entities/EventStoreDBRepository.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public interface IEventStoreDBRepository<TEntity> where TEntity: notnull
1313
Task<TEntity> Find(Func<TEntity?, object, TEntity> when, string id, CancellationToken cancellationToken);
1414

1515
Task Append(string id, object @event, CancellationToken cancellationToken);
16+
17+
Task Append(string id, object @event, uint version, CancellationToken cancellationToken);
1618
}
1719

1820
public class EventStoreDBRepository<TEntity>: IEventStoreDBRepository<TEntity> where TEntity: class
@@ -53,6 +55,39 @@ public async Task Append(
5355
object @event,
5456
CancellationToken cancellationToken
5557
)
58+
59+
{
60+
await eventStore.AppendToStreamAsync(
61+
id,
62+
// TODO: Add proper optimistic concurrency handling
63+
StreamState.NoStream,
64+
new[] { @event.ToJsonEventData() },
65+
cancellationToken: cancellationToken
66+
);
67+
}
68+
69+
70+
public async Task Append(
71+
string id,
72+
object @event,
73+
uint version,
74+
CancellationToken cancellationToken
75+
)
76+
{
77+
await eventStore.AppendToStreamAsync(
78+
id,
79+
StreamRevision.FromInt64(version),
80+
new[] { @event.ToJsonEventData() },
81+
cancellationToken: cancellationToken
82+
);
83+
}
84+
85+
private async Task Append(
86+
string id,
87+
object @event,
88+
uint? version,
89+
CancellationToken cancellationToken
90+
)
5691
{
5792
await eventStore.AppendToStreamAsync(
5893
id,

Sample/EventStoreDB/Simple/ECommerce/ShoppingCarts/AddingProductItem/AddProductItemToShoppingCart.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@ namespace ECommerce.ShoppingCarts.AddingProductItem
66
{
77
public record AddProductItemToShoppingCart(
88
Guid ShoppingCartId,
9-
ProductItem ProductItem
9+
ProductItem ProductItem,
10+
uint Version
1011
)
1112
{
12-
public static AddProductItemToShoppingCart From(Guid? cartId, ProductItem? productItem)
13+
public static AddProductItemToShoppingCart From(Guid? cartId, ProductItem? productItem, uint? version)
1314
{
1415
if (cartId == null || cartId == Guid.Empty)
1516
throw new ArgumentOutOfRangeException(nameof(cartId));
1617
if (productItem == null)
1718
throw new ArgumentOutOfRangeException(nameof(productItem));
19+
if (version == null)
20+
throw new ArgumentOutOfRangeException(nameof(version));
1821

19-
return new AddProductItemToShoppingCart(cartId.Value, productItem);
22+
return new AddProductItemToShoppingCart(cartId.Value, productItem, version.Value);
2023
}
2124

2225
public static ProductItemAddedToShoppingCart Handle(
@@ -25,7 +28,7 @@ public static ProductItemAddedToShoppingCart Handle(
2528
ShoppingCart shoppingCart
2629
)
2730
{
28-
var (cartId, productItem) = command;
31+
var (cartId, productItem, _) = command;
2932

3033
if(shoppingCart.Status != ShoppingCartStatus.Pending)
3134
throw new InvalidOperationException($"Adding product item for cart in '{shoppingCart.Status}' status is not allowed.");

Sample/EventStoreDB/Simple/ECommerce/ShoppingCarts/Configuration.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,19 @@ public static IServiceCollection AddShoppingCartsModule(this IServiceCollection
3030
command,
3131
shoppingCart),
3232
command => ShoppingCart.MapToStreamId(command.ShoppingCartId),
33+
command => command.Version,
3334
ShoppingCart.When
3435
)
3536
.AddUpdateCommandHandler<RemoveProductItemFromShoppingCart, ShoppingCart>(
3637
RemoveProductItemFromShoppingCart.Handle,
3738
command => ShoppingCart.MapToStreamId(command.ShoppingCartId),
39+
command => command.Version,
3840
ShoppingCart.When
3941
)
4042
.AddUpdateCommandHandler<ConfirmShoppingCart, ShoppingCart>(
4143
ConfirmShoppingCart.Handle,
4244
command => ShoppingCart.MapToStreamId(command.ShoppingCartId),
45+
command => command.Version,
4346
ShoppingCart.When
4447
)
4548
.For<ShoppingCartDetails, ECommerceDbContext>(

Sample/EventStoreDB/Simple/ECommerce/ShoppingCarts/Confirming/ConfirmShoppingCart.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@
33
namespace ECommerce.ShoppingCarts.Confirming
44
{
55
public record ConfirmShoppingCart(
6-
Guid ShoppingCartId
6+
Guid ShoppingCartId,
7+
uint Version
78
)
89
{
9-
public static ConfirmShoppingCart From(Guid? cartId)
10+
public static ConfirmShoppingCart From(Guid? cartId, uint? version)
1011
{
1112
if (cartId == null || cartId == Guid.Empty)
1213
throw new ArgumentOutOfRangeException(nameof(cartId));
1314

14-
return new ConfirmShoppingCart(cartId.Value);
15+
if (version == null)
16+
throw new ArgumentOutOfRangeException(nameof(version));
17+
18+
return new ConfirmShoppingCart(cartId.Value, version.Value);
1519
}
1620

1721
public static ShoppingCartConfirmed Handle(ConfirmShoppingCart command, ShoppingCart shoppingCart)

Sample/EventStoreDB/Simple/ECommerce/ShoppingCarts/GettingCartById/ShoppingCartDetails.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static ShoppingCartDetails Handle(ShoppingCartInitialized @event)
3131
Id = shoppingCartId,
3232
ClientId = clientId,
3333
Status = shoppingCartStatus,
34-
Version = 1
34+
Version = 0
3535
};
3636
}
3737

0 commit comments

Comments
 (0)