Skip to content

Commit 351bca8

Browse files
committed
Added AddProductItemToShoppingCart and RemoveProductItemFromShoppingCart commands handling in Simple EventStoreDB examples
1 parent 53823d1 commit 351bca8

File tree

17 files changed

+414
-57
lines changed

17 files changed

+414
-57
lines changed

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

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
using System.Threading.Tasks;
55
using Microsoft.AspNetCore.Mvc;
66
using ECommerce.Api.Requests;
7+
using ECommerce.ShoppingCarts.AddingProductItem;
78
using ECommerce.ShoppingCarts.Confirming;
89
using ECommerce.ShoppingCarts.GettingCartById;
910
using ECommerce.ShoppingCarts.GettingCarts;
1011
using ECommerce.ShoppingCarts.Initializing;
12+
using ECommerce.ShoppingCarts.ProductItems;
13+
using ECommerce.ShoppingCarts.RemovingProductItem;
1114

1215
namespace ECommerce.Api.Controllers
1316
{
@@ -37,40 +40,57 @@ CancellationToken ct
3740
return Created("api/ShoppingCarts", cartId);
3841
}
3942

40-
// [HttpPost("{id}/products")]
41-
// public async Task<IActionResult> AddProduct(
42-
// [FromRoute] Guid id, [FromBody] AddProductRequest? request)
43-
// {
44-
// var command = Carts.AddingProduct.AddProduct.Create(
45-
// id,
46-
// ProductItem.Create(
47-
// request?.ProductItem?.ProductId,
48-
// request?.ProductItem?.Quantity
49-
// )
50-
// );
51-
//
52-
// await commandBus.Send(command);
53-
//
54-
// return Ok();
55-
// }
56-
//
57-
// [HttpDelete("{id}/products")]
58-
// public async Task<IActionResult> RemoveProduct(Guid id, [FromBody] RemoveProductRequest request)
59-
// {
60-
// var command = Carts.RemovingProduct.RemoveProduct.Create(
61-
// id,
62-
// PricedProductItem.Create(
63-
// request?.ProductItem?.ProductId,
64-
// request?.ProductItem?.Quantity,
65-
// request?.ProductItem?.UnitPrice
66-
// )
67-
// );
68-
//
69-
// await commandBus.Send(command);
70-
//
71-
// return Ok();
72-
// }
73-
//
43+
[HttpPost("{id}/products")]
44+
public async Task<IActionResult> AddProduct(
45+
[FromServices] Func<AddProductItemToShoppingCart, CancellationToken, ValueTask> handle,
46+
[FromRoute] Guid id,
47+
[FromBody] AddProductRequest? request,
48+
CancellationToken ct
49+
)
50+
{
51+
if (request == null)
52+
throw new ArgumentNullException(nameof(request));
53+
54+
var command = AddProductItemToShoppingCart.From(
55+
id,
56+
ProductItem.From(
57+
request.ProductItem?.ProductId,
58+
request.ProductItem?.Quantity
59+
)
60+
);
61+
62+
await handle(command, ct);
63+
64+
return Ok();
65+
}
66+
67+
[HttpDelete("{id}/products")]
68+
public async Task<IActionResult> RemoveProduct(
69+
[FromServices] Func<RemoveProductItemFromShoppingCart, CancellationToken, ValueTask> handle,
70+
Guid id,
71+
[FromBody] RemoveProductRequest request,
72+
CancellationToken ct
73+
)
74+
{
75+
if (request == null)
76+
throw new ArgumentNullException(nameof(request));
77+
78+
var command = RemoveProductItemFromShoppingCart.From(
79+
id,
80+
PricedProductItem.From(
81+
ProductItem.From(
82+
request.ProductItem?.ProductId,
83+
request.ProductItem?.Quantity
84+
),
85+
request.ProductItem?.UnitPrice
86+
)
87+
);
88+
89+
await handle(command, ct);
90+
91+
return Ok();
92+
}
93+
7494
[HttpPut("{id}/confirmation")]
7595
public async Task<IActionResult> ConfirmCart(
7696
[FromServices] Func<ConfirmShoppingCart, CancellationToken, ValueTask> handle,

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,27 @@ namespace ECommerce.Api.Requests
55
public record InitializeShoppingCartRequest(
66
Guid? ClientId
77
);
8+
9+
public class ProductItemRequest
10+
{
11+
public Guid ProductId { get; set; }
12+
13+
public int Quantity { get; set; }
14+
}
15+
16+
public record AddProductRequest(
17+
Guid? CartId,
18+
ProductItemRequest? ProductItem
19+
);
20+
21+
public record PricedProductItemRequest(
22+
Guid? ProductId,
23+
int? Quantity,
24+
decimal? UnitPrice
25+
);
26+
27+
public record RemoveProductRequest(
28+
Guid? CartId,
29+
PricedProductItemRequest? ProductItem
30+
);
831
}

Sample/EventStoreDB/Simple/ECommerce/Configuration.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
using System;
2-
using ECommerce.Core;
3-
using ECommerce.Core.Entities;
4-
using ECommerce.Core.Events;
2+
using ECommerce.Pricing;
53
using ECommerce.ShoppingCarts;
64
using ECommerce.Storage;
75
using Microsoft.EntityFrameworkCore;
8-
using Microsoft.Extensions.Configuration;
96
using Microsoft.Extensions.DependencyInjection;
107

118
namespace ECommerce
@@ -15,6 +12,7 @@ public static class Configuration
1512
public static IServiceCollection AddECommerceModule(this IServiceCollection services)
1613
=> services
1714
.AddShoppingCartsModule()
15+
.AddPricingModule()
1816
.AddDbContext<ECommerceDbContext>(
1917
options => options.UseNpgsql("name=ConnectionStrings:ECommerceDB"))
2018
.AddSingleton<Func<Guid>>(Guid.NewGuid);

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public interface ICommandHandler<in T>
1313

1414
public static class CommandHandlerExtensions
1515
{
16-
public static async Task HandleCreateCommand<TEntity, TCommand>(
16+
public static async Task HandleCreateCommand<TCommand, TEntity>(
1717
IEventStoreDBRepository<TEntity> repository,
1818
Func<TCommand, object> handle,
1919
Func<TCommand, string> getId,
@@ -27,9 +27,9 @@ CancellationToken ct
2727
await repository.Append(entityId, @event, ct);
2828
}
2929

30-
public static async Task HandleUpdateCommand<TEntity, TCommand>(
30+
public static async Task HandleUpdateCommand<TCommand, TEntity>(
3131
IEventStoreDBRepository<TEntity> repository,
32-
Func<TEntity, TCommand, object> handle,
32+
Func<TCommand, TEntity, object> handle,
3333
Func<TCommand, string> getId,
3434
Func<TEntity?, object, TEntity> when,
3535
TCommand command,
@@ -39,7 +39,7 @@ CancellationToken ct
3939
var id = getId(command);
4040
var entity = await repository.Find(when, id, ct);
4141

42-
var @event = handle(entity, command);
42+
var @event = handle(command, entity);
4343

4444
await repository.Append(id, @event, ct);
4545
}
@@ -55,7 +55,7 @@ public static IServiceCollection AddCommandHandler<TCommand, TCommandHandler>(th
5555
await commandHandler.Handle(command, ct);
5656
});
5757

58-
public static IServiceCollection AddCreateCommandHandler<TEntity, TCommand>(
58+
public static IServiceCollection AddCreateCommandHandler<TCommand, TEntity>(
5959
this IServiceCollection services,
6060
Func<TCommand, object> handle,
6161
Func<TCommand, string> getId
@@ -69,9 +69,18 @@ Func<TCommand, string> getId
6969
await HandleCreateCommand(repository, handle, getId, command, ct);
7070
});
7171

72-
public static IServiceCollection AddUpdateCommandHandler<TEntity, TCommand>(
72+
73+
public static IServiceCollection AddUpdateCommandHandler<TCommand, TEntity>(
74+
this IServiceCollection services,
75+
Func<TCommand, TEntity, object> handle,
76+
Func<TCommand, string> getId,
77+
Func<TEntity?, object, TEntity> when
78+
) where TEntity : notnull
79+
=> AddUpdateCommandHandler(services, _ => handle, getId, when);
80+
81+
public static IServiceCollection AddUpdateCommandHandler<TCommand, TEntity>(
7382
this IServiceCollection services,
74-
Func<TEntity, TCommand, object> handle,
83+
Func<IServiceProvider, Func<TCommand, TEntity, object>> handle,
7584
Func<TCommand, string> getId,
7685
Func<TEntity?, object, TEntity> when
7786
) where TEntity : notnull
@@ -80,7 +89,7 @@ public static IServiceCollection AddUpdateCommandHandler<TEntity, TCommand>(
8089
{
8190
var repository = sp.GetRequiredService<IEventStoreDBRepository<TEntity>>();
8291
return async (command, ct) =>
83-
await HandleUpdateCommand(repository, handle, getId, when, command, ct);
92+
await HandleUpdateCommand(repository, handle(sp), getId, when, command, ct);
8493
});
8594

8695

Sample/EventStoreDB/Simple/ECommerce/Core/Events/EventBus.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ public async Task Publish<TEvent>(TEvent @event, CancellationToken ct)
4242

4343
foreach (var eventHandler in eventHandlers)
4444
{
45-
// await retryPolicy.ExecuteAsync(async token =>
46-
// {
47-
await eventHandler.Handle(@event, ct);
48-
//}, ct);
45+
await retryPolicy.ExecuteAsync(async token =>
46+
{
47+
await eventHandler.Handle(@event, token);
48+
}, ct);
4949
}
5050
}
5151

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using ECommerce.Pricing.ProductPricing;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace ECommerce.Pricing
5+
{
6+
public static class Configuration
7+
{
8+
public static IServiceCollection AddPricingModule(this IServiceCollection services)
9+
=> services.AddSingleton<IProductPriceCalculator, RandomProductPriceCalculator>();
10+
}
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Collections.Generic;
2+
using ECommerce.ShoppingCarts.ProductItems;
3+
4+
namespace ECommerce.Pricing.ProductPricing
5+
{
6+
public interface IProductPriceCalculator
7+
{
8+
PricedProductItem Calculate(ProductItem productItem);
9+
}
10+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using ECommerce.ShoppingCarts.ProductItems;
6+
7+
namespace ECommerce.Pricing.ProductPricing
8+
{
9+
public class RandomProductPriceCalculator: IProductPriceCalculator
10+
{
11+
private readonly ConcurrentDictionary<Guid, decimal> productPrices = new();
12+
13+
public PricedProductItem Calculate(ProductItem productItem)
14+
{
15+
var random = new Random();
16+
17+
var price = productPrices.GetOrAdd(
18+
productItem.ProductId,
19+
(decimal)random.NextDouble() * 100
20+
);
21+
return PricedProductItem.From(productItem, price);
22+
}
23+
}
24+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using ECommerce.Core.Commands;
5+
using ECommerce.Pricing.ProductPricing;
6+
using ECommerce.ShoppingCarts.ProductItems;
7+
8+
namespace ECommerce.ShoppingCarts.AddingProductItem
9+
{
10+
public record AddProductItemToShoppingCart(
11+
Guid ShoppingCartId,
12+
ProductItem ProductItem
13+
)
14+
{
15+
public static AddProductItemToShoppingCart From(Guid? cartId, ProductItem? productItem)
16+
{
17+
if (cartId == null || cartId == Guid.Empty)
18+
throw new ArgumentOutOfRangeException(nameof(cartId));
19+
if (productItem == null)
20+
throw new ArgumentOutOfRangeException(nameof(productItem));
21+
22+
return new AddProductItemToShoppingCart(cartId.Value, productItem);
23+
}
24+
25+
public static ProductItemAddedToShoppingCart Handle(
26+
IProductPriceCalculator productPriceCalculator,
27+
AddProductItemToShoppingCart command,
28+
ShoppingCart shoppingCart
29+
)
30+
{
31+
var (cartId, productItem) = command;
32+
33+
if(shoppingCart.Status != ShoppingCartStatus.Pending)
34+
throw new InvalidOperationException($"Adding product item for cart in '{shoppingCart.Status}' status is not allowed.");
35+
36+
var pricedProductItem = productPriceCalculator.Calculate(productItem);
37+
38+
return new ProductItemAddedToShoppingCart(
39+
cartId,
40+
pricedProductItem
41+
);
42+
}
43+
}
44+
}

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
using ECommerce.Core.Events;
55
using ECommerce.Core.Projections;
66
using ECommerce.Core.Queries;
7+
using ECommerce.Pricing.ProductPricing;
8+
using ECommerce.ShoppingCarts.AddingProductItem;
79
using ECommerce.ShoppingCarts.Confirming;
810
using ECommerce.ShoppingCarts.GettingCartById;
911
using ECommerce.ShoppingCarts.GettingCarts;
1012
using ECommerce.ShoppingCarts.Initializing;
13+
using ECommerce.ShoppingCarts.RemovingProductItem;
1114
using ECommerce.Storage;
1215
using Microsoft.EntityFrameworkCore;
1316
using Microsoft.Extensions.DependencyInjection;
@@ -19,11 +22,25 @@ public static class Configuration
1922
public static IServiceCollection AddShoppingCartsModule(this IServiceCollection services)
2023
=> services
2124
.AddEventStoreDBRepository<ShoppingCart>()
22-
.AddCreateCommandHandler<ShoppingCart, InitializeShoppingCart>(
25+
.AddCreateCommandHandler<InitializeShoppingCart, ShoppingCart>(
2326
InitializeShoppingCart.Handle,
2427
command => ShoppingCart.MapToStreamId(command.ShoppingCartId)
2528
)
26-
.AddUpdateCommandHandler<ShoppingCart, ConfirmShoppingCart>(
29+
.AddUpdateCommandHandler<AddProductItemToShoppingCart, ShoppingCart>(
30+
sp=> (command, shoppingCart) =>
31+
AddProductItemToShoppingCart.Handle(
32+
sp.GetRequiredService<IProductPriceCalculator>(),
33+
command,
34+
shoppingCart),
35+
command => ShoppingCart.MapToStreamId(command.ShoppingCartId),
36+
ShoppingCart.When
37+
)
38+
.AddUpdateCommandHandler<RemoveProductItemFromShoppingCart, ShoppingCart>(
39+
RemoveProductItemFromShoppingCart.Handle,
40+
command => ShoppingCart.MapToStreamId(command.ShoppingCartId),
41+
ShoppingCart.When
42+
)
43+
.AddUpdateCommandHandler<ConfirmShoppingCart, ShoppingCart>(
2744
ConfirmShoppingCart.Handle,
2845
command => ShoppingCart.MapToStreamId(command.ShoppingCartId),
2946
ShoppingCart.When
@@ -32,7 +49,7 @@ public static IServiceCollection AddShoppingCartsModule(this IServiceCollection
3249
builder => builder
3350
.AddOn<ShoppingCartInitialized>(ShoppingCartDetailsProjection.Handle)
3451
.UpdateOn<ShoppingCartConfirmed>(
35-
e => e.CartId,
52+
e => e.ShoppingCartId,
3653
ShoppingCartDetailsProjection.Handle
3754
)
3855
.QueryWith<GetCartById>(GetCartById.Handle)
@@ -41,7 +58,15 @@ public static IServiceCollection AddShoppingCartsModule(this IServiceCollection
4158
builder => builder
4259
.AddOn<ShoppingCartInitialized>(ShoppingCartShortInfoProjection.Handle)
4360
.UpdateOn<ShoppingCartConfirmed>(
44-
e => e.CartId,
61+
e => e.ShoppingCartId,
62+
ShoppingCartShortInfoProjection.Handle
63+
)
64+
.UpdateOn<ProductItemAddedToShoppingCart>(
65+
e => e.ShoppingCartId,
66+
ShoppingCartShortInfoProjection.Handle
67+
)
68+
.UpdateOn<ProductItemRemovedFromShoppingCart>(
69+
e => e.ShoppingCartId,
4570
ShoppingCartShortInfoProjection.Handle
4671
)
4772
.QueryWith<GetCarts>(GetCarts.Handle)

0 commit comments

Comments
 (0)