Skip to content

Commit

Permalink
Upgrade Eventuous to Latest (#40)
Browse files Browse the repository at this point in the history
* bumped Eventuous version to b10, applied associated fixes

* removed "with ID" command/event

* added use cases for Cart; partial impl
  • Loading branch information
erikshafer authored Jun 19, 2024
1 parent 9977081 commit 04fac49
Show file tree
Hide file tree
Showing 14 changed files with 81 additions and 38 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings>
<EventuousVersion>0.15.0-beta.4</EventuousVersion>
<EventuousVersion>0.15.0-beta.10</EventuousVersion>
<RepositoryUrl>https://github.com/erikshafer/event-sourcing-ecommerce</RepositoryUrl>
<PackageProjectUrl>https://event-sourcing.dev/</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
1 change: 1 addition & 0 deletions src/Catalog/Catalog.Api/Catalog.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<PackageReference Include="Eventuous.Diagnostics.OpenTelemetry" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.Application" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.EventStore" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.Extensions.DependencyInjection" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.Projections.MongoDB" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.AspNetCore.Web" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.Spyglass" Version="$(EventuousVersion)" />
Expand Down
2 changes: 1 addition & 1 deletion src/Catalog/Catalog.Api/Catalog.Api.http
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

###

GET {{Inventory.Api_HostAddress}}/swagger/
GET {{Inventory.Api_HostAddress}}/api/
Accept: application/json

###
Expand Down
3 changes: 3 additions & 0 deletions src/Catalog/Catalog.Api/Registrations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public static void AddEventuous(this IServiceCollection services, IConfiguration
)
);

// register known event types (e.g. using [EventType] annotation)
TypeMap.RegisterKnownEventTypes();

// event store (core)
services.AddEventStoreClient(configuration["EventStore:ConnectionString"]!);
services.AddAggregateStore<EsdbEventStore>();
Expand Down
2 changes: 0 additions & 2 deletions src/Inventory/Inventory.Api/Inventory.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
<ItemGroup>
<PackageReference Include="Eventuous.Application" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.EventStore" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.AspNetCore" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.Postgresql" Version="$(EventuousVersion)" />

<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.6"/>
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.2.0" />
Expand Down
32 changes: 20 additions & 12 deletions src/Retail/ShoppingCart.Api/HttpApi/CommandApi.cs
Original file line number Diff line number Diff line change
@@ -1,49 +1,57 @@
using Eventuous;
using Eventuous.AspNetCore.Web;
using Microsoft.AspNetCore.Mvc;
using static ShoppingCart.CartCommands.V1;

namespace ShoppingCart.Api.HttpApi;

[Route("/cart")]
public class CommandApi(IFuncCommandService<CartState> service) : ControllerBase
public class CommandApi : CommandHttpApiBaseFunc<CartState>
{
[HttpPost]
[Route("open")]
public async Task<ActionResult<Result>> OpenCart([FromBody] OpenCart cmd, CancellationToken ct)
private readonly IFuncCommandService<CartState> _service;

public CommandApi(IFuncCommandService<CartState> service) : base(service)
{
var result = await service.Handle(cmd, ct);
return Ok(result);
_service = service;
}

[HttpPost]
[Route("open-with-id")]
public async Task<ActionResult<Result>> OpenCart([FromBody] OpenCartWithProvidedId cmd, CancellationToken ct)
[Route("open")]
public async Task<ActionResult<Result>> OpenCart([FromBody] OpenCart cmd, CancellationToken ct)
{
var result = await service.Handle(cmd, ct);
var result = await _service.Handle(cmd, ct);
return Ok(result);
}

[HttpPost]
[Route("add-product")]
public async Task<ActionResult<Result>> OpenCart([FromBody] AddProductToCart cmd, CancellationToken ct)
{
var result = await service.Handle(cmd, ct);
var result = await _service.Handle(cmd, ct);
return Ok(result);
}

[HttpPost]
[Route("remove-product")]
public async Task<ActionResult<Result>> OpenCart([FromBody] RemoveProductFromCart cmd, CancellationToken ct)
{
var result = await service.Handle(cmd, ct);
var result = await _service.Handle(cmd, ct);
return Ok(result);
}

[HttpPost]
[Route("prepare-checkout")]
public async Task<ActionResult<Result>> PrepareForCheckout([FromBody] PrepareCartForCheckout cmd, CancellationToken ct)
{
var result = await _service.Handle(cmd, ct);
return Ok(result);
}

[HttpPost]
[Route("confirm")]
public async Task<ActionResult<Result>> OpenCart([FromBody] ConfirmCart cmd, CancellationToken ct)
{
var result = await service.Handle(cmd, ct);
var result = await _service.Handle(cmd, ct);
return Ok(result);
}
}
1 change: 1 addition & 0 deletions src/Retail/ShoppingCart.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using NodaTime;
using NodaTime.Serialization.SystemTextJson;
using Serilog;
using ShoppingCart;
using ShoppingCart.Api;
using ShoppingCart.Api.Infrastructure;

Expand Down
3 changes: 3 additions & 0 deletions src/Retail/ShoppingCart.Api/Registrations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public static void AddEventuous(this IServiceCollection services, IConfiguration
)
);

// register known event types (e.g. using [EventType] annotation)
TypeMap.RegisterKnownEventTypes();

// event store (core)
services.AddEventStoreClient(configuration["EventStore:ConnectionString"]!);
services.AddAggregateStore<EsdbEventStore>();
Expand Down
1 change: 1 addition & 0 deletions src/Retail/ShoppingCart.Api/ShoppingCart.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<PackageReference Include="Eventuous.Diagnostics.OpenTelemetry" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.Application" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.EventStore" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.Extensions.DependencyInjection" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.AspNetCore.Web" Version="$(EventuousVersion)" />
<PackageReference Include="Eventuous.Spyglass" Version="$(EventuousVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.6" />
Expand Down
9 changes: 4 additions & 5 deletions src/Retail/ShoppingCart/CartCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ public record OpenCart(
string CustomerId
);

public record OpenCartWithProvidedId(
string CartId,
string CustomerId
);

public record AddProductToCart(
string CartId,
string ProductId,
Expand All @@ -25,6 +20,10 @@ public record RemoveProductFromCart(
int Quantity
);

public record PrepareCartForCheckout(
string CartId
);

public record ConfirmCart(
string CartId
);
Expand Down
5 changes: 5 additions & 0 deletions src/Retail/ShoppingCart/CartEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ int Quantity
public record CartConfirmed(
string CartId
);

[EventType("V1.EmptyCartDetected")]
public record EmptyCartDetected(
string CartId
);
}
}
44 changes: 28 additions & 16 deletions src/Retail/ShoppingCart/CartFuncService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,53 @@ public CartFuncService(
TypeMapper? typeMap = null)
: base(store, typeMap)
{
var generatedId = idGenerator.New(); // TODO: leverage
var generatedId = idGenerator.New();

// Register command handlers
OnNew<Commands.OpenCart>(cmd => GetStream(generatedId), OpenCart);
OnNew<Commands.OpenCartWithProvidedId>(cmd => GetStream(cmd.CartId), OpenCartCommandHasId);
OnExisting<Commands.AddProductToCart>(cmd => GetStream(cmd.CartId), AddItemToCart);
OnExisting<Commands.AddProductToCart>(cmd => GetStream(cmd.CartId), AddProductToCart);
OnExisting<Commands.RemoveProductFromCart>(cmd => GetStream(cmd.CartId), RemoveProductFromCart);
OnExisting<Commands.PrepareCartForCheckout>(cmd => GetStream(cmd.CartId), PrepareCartForCheckout);

// Helper function to get the stream name from the command
static StreamName GetStream(string id) => new($"Cart-{id}");

// When there's no stream to load, the function only receives the command. (CLOSURES)
IEnumerable<object> OpenCart(Commands.OpenCart cmd)
{
yield return new Events.CartOpened(generatedId, cmd.CustomerId);
}

// When there's no stream to load, the function only receives the command
static IEnumerable<object> OpenCartCommandHasId(Commands.OpenCartWithProvidedId cmd)
{
yield return new Events.CartOpened(cmd.CartId, cmd.CustomerId);
}

// For an existing stream, the function receives the state and the events
static IEnumerable<object> AddItemToCart(
static IEnumerable<object> AddProductToCart(
CartState state,
object[] originalEvents,
Commands.AddProductToCart cmd)
{
var added = new Events.ProductAddedToCart(cmd.CartId, cmd.ProductId, cmd.Quantity);

yield return added;

var newState = state.When(added);
// could have additional logic based on the cart's current state, emmit other events, etc
}

// could have logic based on the cart's current state
static IEnumerable<object> RemoveProductFromCart(
CartState state,
object[] originalEvents,
Commands.RemoveProductFromCart cmd)
{
var removed = new Events.ProductRemovedFromCart(cmd.CartId, cmd.ProductId, cmd.Quantity);
yield return removed;

var newState = state.When(removed);

if (newState.HasProductItems is false)
yield return new Events.EmptyCartDetected(cmd.CartId);
}

static IEnumerable<object> PrepareCartForCheckout(
CartState state,
object[] originalEvents,
Commands.PrepareCartForCheckout cmd)
{
if (state.CanProceedToCheckout())
yield return new Events.CartConfirmed(cmd.CartId);
}
}
}
10 changes: 9 additions & 1 deletion src/Retail/ShoppingCart/CartState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public record CartState : State<CartState>
public CartStatus Status { get; init; } = CartStatus.Unset;
public ProductItems ProductItems { get; init; } = null!;

public bool HasProductItems => ProductItems.IsEmpty;

public CartState()
{
On<V1.CartOpened>(Handle);
Expand Down Expand Up @@ -42,6 +44,12 @@ private static CartState Handle(CartState state, V1.CartOpened @event) => state
private static CartState Handle(CartState state, V1.CartConfirmed @event) => state.Status switch
{
CartStatus.Confirmed => throw InvalidStateChangeException.For<CartState, V1.ProductAddedToCart>(state.Id, CartStatus.Confirmed),
_ => state with { Status = CartStatus.Confirmed } // TODO: make confirm a behavior? but no aggregate? HOW DO!?
_ => state with { Status = CartStatus.Confirmed }
};

public bool CanProceedToCheckout() => Status switch
{
CartStatus.Unset or CartStatus.Opened => false,
_ => HasProductItems
};
}
4 changes: 4 additions & 0 deletions src/Retail/ShoppingCart/ProductItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class ProductItems

private ProductItems(ProductItem[] values) => Values = values;

public bool IsEmpty => Values.Length == 0;
public int Length => Values.Length;

public ProductItems Add(ProductItem productItem) => new(
Values
.Concat(new[] { productItem })
Expand All @@ -62,4 +65,5 @@ public class ProductItems
: pi)
.Where(pi => pi.Quantity > 0)
.ToArray());

}

0 comments on commit 04fac49

Please sign in to comment.