diff --git a/EventSourcing.NetCore.sln b/EventSourcing.NetCore.sln
index 806af24d2..e03f70888 100644
--- a/EventSourcing.NetCore.sln
+++ b/EventSourcing.NetCore.sln
@@ -293,8 +293,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "07-BusinessLogic", "Worksho
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "07-BusinessLogic", "Workshops\IntroductionToEventSourcing\Solved\07-BusinessLogic\07-BusinessLogic.csproj", "{2B630D31-F68C-447E-82C4-48E2B17322B5}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "09-BusinessLogic.EventStoreDB", "Workshops\IntroductionToEventSourcing\Solved\09-BusinessLogic.EventStoreDB\09-BusinessLogic.EventStoreDB.csproj", "{26E884E8-4E81-4C87-A643-FE7327F218CE}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "10-OptimisticConcurrency.Marten", "Workshops\IntroductionToEventSourcing\10-OptimisticConcurrency.Marten\10-OptimisticConcurrency.Marten.csproj", "{DFC5BD15-8718-469A-B0FE-D21E473F1133}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "10-OptimisticConcurrency.Marten", "Workshops\IntroductionToEventSourcing\Solved\10-OptimisticConcurrency.Marten\10-OptimisticConcurrency.Marten.csproj", "{C33A067F-5335-4011-B955-66901D877B24}"
@@ -815,10 +813,6 @@ Global
{2B630D31-F68C-447E-82C4-48E2B17322B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B630D31-F68C-447E-82C4-48E2B17322B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B630D31-F68C-447E-82C4-48E2B17322B5}.Release|Any CPU.Build.0 = Release|Any CPU
- {26E884E8-4E81-4C87-A643-FE7327F218CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {26E884E8-4E81-4C87-A643-FE7327F218CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {26E884E8-4E81-4C87-A643-FE7327F218CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {26E884E8-4E81-4C87-A643-FE7327F218CE}.Release|Any CPU.Build.0 = Release|Any CPU
{DFC5BD15-8718-469A-B0FE-D21E473F1133}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFC5BD15-8718-469A-B0FE-D21E473F1133}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFC5BD15-8718-469A-B0FE-D21E473F1133}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -1164,7 +1158,6 @@ Global
{0FE43045-AC67-4DB9-9820-82C9D55EA09A} = {14C7B928-9D6C-441A-8A1F-0C49173E73EB}
{2FFCF9EC-9C75-497B-BC94-B0051367780F} = {14C7B928-9D6C-441A-8A1F-0C49173E73EB}
{2B630D31-F68C-447E-82C4-48E2B17322B5} = {65F6E2BE-B2D4-4E56-B0CB-3062C4882B9E}
- {26E884E8-4E81-4C87-A643-FE7327F218CE} = {65F6E2BE-B2D4-4E56-B0CB-3062C4882B9E}
{DFC5BD15-8718-469A-B0FE-D21E473F1133} = {14C7B928-9D6C-441A-8A1F-0C49173E73EB}
{C33A067F-5335-4011-B955-66901D877B24} = {65F6E2BE-B2D4-4E56-B0CB-3062C4882B9E}
{F21F4280-8B2A-43E1-8044-3E6E2905F60B} = {14C7B928-9D6C-441A-8A1F-0C49173E73EB}
diff --git a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/09-BusinessLogic.EventStoreDB.csproj b/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/09-BusinessLogic.EventStoreDB.csproj
deleted file mode 100644
index b03d9075a..000000000
--- a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/09-BusinessLogic.EventStoreDB.csproj
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
- net8.0
- IntroductionToEventSourcing.BusinessLogic
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
diff --git a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Immutable/BusinessLogic.cs b/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Immutable/BusinessLogic.cs
deleted file mode 100644
index 7dae9f2e9..000000000
--- a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Immutable/BusinessLogic.cs
+++ /dev/null
@@ -1,263 +0,0 @@
-namespace IntroductionToEventSourcing.BusinessLogic.Immutable;
-using static ShoppingCartEvent;
-
-// ENTITY
-public record ShoppingCart(
- Guid Id,
- Guid ClientId,
- ShoppingCartStatus Status,
- PricedProductItem[] ProductItems,
- DateTime? ConfirmedAt = null,
- DateTime? CanceledAt = null
-)
-{
- public bool IsClosed => ShoppingCartStatus.Closed.HasFlag(Status);
-
- public static ShoppingCart Default() =>
- new (default, default, default, []);
-
- public static string StreamName(Guid id) => $"shopping_cart-{id}";
-
- public static ShoppingCart Evolve(ShoppingCart shoppingCart, object @event)
- {
- return @event switch
- {
- ShoppingCartOpened(var shoppingCartId, var clientId) =>
- shoppingCart with
- {
- Id = shoppingCartId,
- ClientId = clientId,
- Status = ShoppingCartStatus.Pending
- },
- ProductItemAddedToShoppingCart(_, var pricedProductItem) =>
- shoppingCart with
- {
- ProductItems = shoppingCart.ProductItems
- .Concat(new [] { pricedProductItem })
- .GroupBy(pi => pi.ProductId)
- .Select(group => group.Count() == 1?
- group.First()
- : new PricedProductItem(
- group.Key,
- group.Sum(pi => pi.Quantity),
- group.First().UnitPrice
- )
- )
- .ToArray()
- },
- ProductItemRemovedFromShoppingCart(_, var pricedProductItem) =>
- shoppingCart with
- {
- ProductItems = shoppingCart.ProductItems
- .Select(pi => pi.ProductId == pricedProductItem.ProductId?
- pi with { Quantity = pi.Quantity - pricedProductItem.Quantity }
- :pi
- )
- .Where(pi => pi.Quantity > 0)
- .ToArray()
- },
- ShoppingCartConfirmed(_, var confirmedAt) =>
- shoppingCart with
- {
- Status = ShoppingCartStatus.Confirmed,
- ConfirmedAt = confirmedAt
- },
- ShoppingCartCanceled(_, var canceledAt) =>
- shoppingCart with
- {
- Status = ShoppingCartStatus.Canceled,
- CanceledAt = canceledAt
- },
- _ => shoppingCart
- };
- }
-
- public bool HasEnough(PricedProductItem productItem)
- {
- var (productId, quantity, _) = productItem;
- var currentQuantity = ProductItems.Where(pi => pi.ProductId == productId)
- .Select(pi => pi.Quantity)
- .FirstOrDefault();
-
- return currentQuantity >= quantity;
- }
-}
-
-public enum ShoppingCartStatus
-{
- Pending = 1,
- Confirmed = 2,
- Canceled = 4,
-
- Closed = Confirmed | Canceled
-}
-
-public record OpenShoppingCart(
- Guid ShoppingCartId,
- Guid ClientId
-)
-{
- public static OpenShoppingCart From(Guid? cartId, Guid? clientId)
- {
- if (cartId == null || cartId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(cartId));
- if (clientId == null || clientId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(clientId));
-
- return new OpenShoppingCart(cartId.Value, clientId.Value);
- }
-
- public static ShoppingCartOpened Handle(OpenShoppingCart command)
- {
- var (shoppingCartId, clientId) = command;
-
- return new ShoppingCartOpened(
- shoppingCartId,
- clientId
- );
- }
-}
-
-public record AddProductItemToShoppingCart(
- Guid ShoppingCartId,
- ProductItem ProductItem
-)
-{
- public static AddProductItemToShoppingCart From(Guid? cartId, ProductItem? productItem)
- {
- if (cartId == null || cartId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(cartId));
- if (productItem == null)
- throw new ArgumentOutOfRangeException(nameof(productItem));
-
- return new AddProductItemToShoppingCart(cartId.Value, productItem);
- }
-
- public static ProductItemAddedToShoppingCart Handle(
- IProductPriceCalculator productPriceCalculator,
- AddProductItemToShoppingCart command,
- ShoppingCart shoppingCart
- )
- {
- var (cartId, productItem) = command;
-
- if (shoppingCart.IsClosed)
- throw new InvalidOperationException(
- $"Adding product item for cart in '{shoppingCart.Status}' status is not allowed.");
-
- var pricedProductItem = productPriceCalculator.Calculate(productItem);
-
- return new ProductItemAddedToShoppingCart(
- cartId,
- pricedProductItem
- );
- }
-}
-
-public record RemoveProductItemFromShoppingCart(
- Guid ShoppingCartId,
- PricedProductItem ProductItem
-)
-{
- public static RemoveProductItemFromShoppingCart From(Guid? cartId, PricedProductItem? productItem)
- {
- if (cartId == null || cartId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(cartId));
- if (productItem == null)
- throw new ArgumentOutOfRangeException(nameof(productItem));
-
- return new RemoveProductItemFromShoppingCart(cartId.Value, productItem);
- }
-
- public static ProductItemRemovedFromShoppingCart Handle(
- RemoveProductItemFromShoppingCart command,
- ShoppingCart shoppingCart
- )
- {
- var (cartId, productItem) = command;
-
- if (shoppingCart.IsClosed)
- throw new InvalidOperationException(
- $"Adding product item for cart in '{shoppingCart.Status}' status is not allowed.");
-
- if (!shoppingCart.HasEnough(productItem))
- throw new InvalidOperationException("Not enough product items to remove");
-
- return new ProductItemRemovedFromShoppingCart(
- cartId,
- productItem
- );
- }
-}
-
-public record ConfirmShoppingCart(
- Guid ShoppingCartId
-)
-{
- public static ConfirmShoppingCart From(Guid? cartId)
- {
- if (cartId == null || cartId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(cartId));
-
- return new ConfirmShoppingCart(cartId.Value);
- }
-
- public static ShoppingCartConfirmed Handle(ConfirmShoppingCart command, ShoppingCart shoppingCart)
- {
- if (shoppingCart.IsClosed)
- throw new InvalidOperationException($"Confirming cart in '{shoppingCart.Status}' status is not allowed.");
-
- return new ShoppingCartConfirmed(
- shoppingCart.Id,
- DateTime.UtcNow
- );
- }
-}
-
-public record CancelShoppingCart(
- Guid ShoppingCartId
-)
-{
- public static CancelShoppingCart From(Guid? cartId)
- {
- if (cartId == null || cartId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(cartId));
-
- return new CancelShoppingCart(cartId.Value);
- }
-
- public static ShoppingCartCanceled Handle(CancelShoppingCart command, ShoppingCart shoppingCart)
- {
- if (shoppingCart.IsClosed)
- throw new InvalidOperationException($"Canceling cart in '{shoppingCart.Status}' status is not allowed.");
-
- return new ShoppingCartCanceled(
- shoppingCart.Id,
- DateTime.UtcNow
- );
- }
-}
-
-public interface IProductPriceCalculator
-{
- PricedProductItem Calculate(ProductItem productItems);
-}
-
-public class FakeProductPriceCalculator: IProductPriceCalculator
-{
- private readonly int value;
-
- private FakeProductPriceCalculator(int value)
- {
- this.value = value;
- }
-
- public static FakeProductPriceCalculator Returning(int value) => new(value);
-
- public PricedProductItem Calculate(ProductItem productItem)
- {
- var (productId, quantity) = productItem;
-
- return new PricedProductItem(productId, quantity, value);
- }
-}
diff --git a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Immutable/BusinessLogicTests.cs b/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Immutable/BusinessLogicTests.cs
deleted file mode 100644
index 5b5b08e52..000000000
--- a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Immutable/BusinessLogicTests.cs
+++ /dev/null
@@ -1,152 +0,0 @@
-using FluentAssertions;
-using IntroductionToEventSourcing.BusinessLogic.Tools;
-using Xunit;
-
-namespace IntroductionToEventSourcing.BusinessLogic.Immutable;
-
-// EVENTS
-public abstract record ShoppingCartEvent
-{
- public record ShoppingCartOpened(
- Guid ShoppingCartId,
- Guid ClientId
- ): ShoppingCartEvent;
-
- public record ProductItemAddedToShoppingCart(
- Guid ShoppingCartId,
- PricedProductItem ProductItem
- ): ShoppingCartEvent;
-
- public record ProductItemRemovedFromShoppingCart(
- Guid ShoppingCartId,
- PricedProductItem ProductItem
- ): ShoppingCartEvent;
-
- public record ShoppingCartConfirmed(
- Guid ShoppingCartId,
- DateTime ConfirmedAt
- ): ShoppingCartEvent;
-
- public record ShoppingCartCanceled(
- Guid ShoppingCartId,
- DateTime CanceledAt
- ): ShoppingCartEvent;
-
- // This won't allow external inheritance
- private ShoppingCartEvent(){}
-}
-
-// VALUE OBJECTS
-
-public record ProductItem(
- Guid ProductId,
- int Quantity
-);
-
-public record PricedProductItem(
- Guid ProductId,
- int Quantity,
- decimal UnitPrice
-);
-
-// Business logic
-
-public class BusinessLogicTests: EventStoreDBTest
-{
- [Fact]
- public async Task GettingState_ForSequenceOfEvents_ShouldSucceed()
- {
- var shoppingCartId = Guid.NewGuid();
- var clientId = Guid.NewGuid();
- var shoesId = Guid.NewGuid();
- var tShirtId = Guid.NewGuid();
- var twoPairsOfShoes = new ProductItem(shoesId, 2);
- var pairOfShoes = new ProductItem(shoesId, 1);
- var tShirt = new ProductItem(tShirtId, 1);
-
- var shoesPrice = 100;
- var tShirtPrice = 50;
-
- var pricedPairOfShoes = new PricedProductItem(shoesId, 1, shoesPrice);
- var pricedTShirt = new PricedProductItem(tShirtId, 1, tShirtPrice);
-
- await EventStore.Add(
- command => ShoppingCart.StreamName(command.ShoppingCartId),
- OpenShoppingCart.Handle,
- OpenShoppingCart.From(shoppingCartId, clientId),
- CancellationToken.None
- );
-
- // Add two pairs of shoes
- await EventStore.GetAndUpdate(
- ShoppingCart.Evolve,
- ShoppingCart.Default(),
- command => ShoppingCart.StreamName(command.ShoppingCartId),
- (command, shoppingCart) =>
- AddProductItemToShoppingCart.Handle(FakeProductPriceCalculator.Returning(shoesPrice), command, shoppingCart),
- AddProductItemToShoppingCart.From(shoppingCartId, twoPairsOfShoes),
- CancellationToken.None
- );
-
- // Add T-Shirt
- await EventStore.GetAndUpdate(
- ShoppingCart.Evolve,
- ShoppingCart.Default(),
- command => ShoppingCart.StreamName(command.ShoppingCartId),
- (command, shoppingCart) =>
- AddProductItemToShoppingCart.Handle(FakeProductPriceCalculator.Returning(tShirtPrice), command, shoppingCart),
- AddProductItemToShoppingCart.From(shoppingCartId, tShirt),
- CancellationToken.None
- );
-
- // Remove pair of shoes
- await EventStore.GetAndUpdate(
- ShoppingCart.Evolve,
- ShoppingCart.Default(),
- command => ShoppingCart.StreamName(command.ShoppingCartId),
- RemoveProductItemFromShoppingCart.Handle,
- RemoveProductItemFromShoppingCart.From(shoppingCartId, pricedPairOfShoes),
- CancellationToken.None
- );
-
- // Confirm
- await EventStore.GetAndUpdate(
- ShoppingCart.Evolve,
- ShoppingCart.Default(),
- command => ShoppingCart.StreamName(command.ShoppingCartId),
- ConfirmShoppingCart.Handle,
- ConfirmShoppingCart.From(shoppingCartId),
- CancellationToken.None
- );
-
- // Cancel
- var exception = await Record.ExceptionAsync(async () =>
- {
- await EventStore.GetAndUpdate(
- ShoppingCart.Evolve,
- ShoppingCart.Default(),
- command => ShoppingCart.StreamName(command.ShoppingCartId),
- CancelShoppingCart.Handle,
- CancelShoppingCart.From(shoppingCartId),
- CancellationToken.None
- );
- }
- );
- exception.Should().BeOfType();
-
- var shoppingCart = await EventStore.Get(
- ShoppingCart.Evolve,
- ShoppingCart.Default(),
- ShoppingCart.StreamName(shoppingCartId),
- CancellationToken.None
- );
-
- shoppingCart.Id.Should().Be(shoppingCartId);
- shoppingCart.ClientId.Should().Be(clientId);
- shoppingCart.ProductItems.Should().HaveCount(2);
- shoppingCart.Status.Should().Be(ShoppingCartStatus.Confirmed);
-
- shoppingCart.ProductItems[0].Should().Be(pricedPairOfShoes);
- shoppingCart.ProductItems[1].Should().Be(pricedTShirt);
- }
-}
diff --git a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Immutable/EventStoreClientExtensions.cs b/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Immutable/EventStoreClientExtensions.cs
deleted file mode 100644
index 751cb0a35..000000000
--- a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Immutable/EventStoreClientExtensions.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System.Text.Json;
-using EventStore.Client;
-
-namespace IntroductionToEventSourcing.BusinessLogic.Immutable;
-
-public static class EventStoreClientExtensions
-{
- public static async Task Get(
- this EventStoreClient eventStore,
- Func when,
- TEntity empty,
- string streamName,
- CancellationToken cancellationToken = default
- )
- {
- var result = eventStore.ReadStreamAsync(
- Direction.Forwards,
- streamName,
- StreamPosition.Start,
- cancellationToken: cancellationToken
- );
-
- if (await result.ReadState == ReadState.StreamNotFound)
- throw new InvalidOperationException("Shopping Cart was not found!");
-
- return await result
- .Select(@event =>
- JsonSerializer.Deserialize(
- @event.Event.Data.Span,
- Type.GetType(@event.Event.EventType)!
- )!
- )
- .AggregateAsync(
- empty,
- when,
- cancellationToken
- );
- }
-
- public static Task Add(
- this EventStoreClient eventStore,
- Func getStreamName,
- Func action,
- TCommand command,
- CancellationToken cancellationToken = default
- )
- {
- var @event = action(command);
-
- return eventStore.AppendToStreamAsync(
- getStreamName(command),
- StreamState.Any,
- new[]
- {
- new EventData(
- Uuid.NewUuid(),
- @event.GetType().FullName!,
- JsonSerializer.SerializeToUtf8Bytes(@event)
- )
- },
- cancellationToken: cancellationToken
- );
- }
-
- public static async Task GetAndUpdate(
- this EventStoreClient eventStore,
- Func when,
- TEntity empty,
- Func getStreamName,
- Func action,
- TCommand command,
- CancellationToken cancellationToken = default
- )
- {
- var streamName = getStreamName(command);
- var current = await eventStore.Get(when, empty, streamName, cancellationToken);
-
- var @event = action(command, current);
-
- await eventStore.AppendToStreamAsync(
- getStreamName(command),
- StreamState.Any,
- new[]
- {
- new EventData(
- Uuid.NewUuid(),
- @event.GetType().FullName!,
- JsonSerializer.SerializeToUtf8Bytes(@event)
- )
- },
- cancellationToken: cancellationToken
- );
- }
-}
diff --git a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Mixed/BusinessLogic.cs b/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Mixed/BusinessLogic.cs
deleted file mode 100644
index 38816433c..000000000
--- a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Mixed/BusinessLogic.cs
+++ /dev/null
@@ -1,294 +0,0 @@
-namespace IntroductionToEventSourcing.BusinessLogic.Mixed;
-using static ShoppingCartEvent;
-
-public interface IAggregate
-{
- public Guid Id { get; }
-
- public void Evolve(object @event) { }
-}
-
-
-// COMMANDS
-public record OpenShoppingCart(
- Guid ShoppingCartId,
- Guid ClientId
-)
-{
- public static OpenShoppingCart From(Guid? cartId, Guid? clientId)
- {
- if (cartId == null || cartId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(cartId));
- if (clientId == null || clientId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(clientId));
-
- return new OpenShoppingCart(cartId.Value, clientId.Value);
- }
-}
-
-public record AddProductItemToShoppingCart(
- Guid ShoppingCartId,
- ProductItem ProductItem
-)
-{
- public static AddProductItemToShoppingCart From(Guid? cartId, ProductItem? productItem)
- {
- if (cartId == null || cartId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(cartId));
- if (productItem == null)
- throw new ArgumentOutOfRangeException(nameof(productItem));
-
- return new AddProductItemToShoppingCart(cartId.Value, productItem);
- }
-}
-
-public record RemoveProductItemFromShoppingCart(
- Guid ShoppingCartId,
- PricedProductItem ProductItem
-)
-{
- public static RemoveProductItemFromShoppingCart From(Guid? cartId, PricedProductItem? productItem)
- {
- if (cartId == null || cartId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(cartId));
- if (productItem == null)
- throw new ArgumentOutOfRangeException(nameof(productItem));
-
- return new RemoveProductItemFromShoppingCart(cartId.Value, productItem);
- }
-}
-
-public record ConfirmShoppingCart(
- Guid ShoppingCartId
-)
-{
- public static ConfirmShoppingCart From(Guid? cartId)
- {
- if (cartId == null || cartId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(cartId));
-
- return new ConfirmShoppingCart(cartId.Value);
- }
-}
-
-public record CancelShoppingCart(
- Guid ShoppingCartId
-)
-{
- public static CancelShoppingCart From(Guid? cartId)
- {
- if (cartId == null || cartId == Guid.Empty)
- throw new ArgumentOutOfRangeException(nameof(cartId));
-
- return new CancelShoppingCart(cartId.Value);
- }
-}
-
-public class ShoppingCart: IAggregate
-{
- public Guid Id { get; private set; }
- public Guid ClientId { get; private set; }
- public ShoppingCartStatus Status { get; private set; }
- public IList ProductItems { get; } = new List();
- public DateTime? ConfirmedAt { get; private set; }
- public DateTime? CanceledAt { get; private set; }
-
- public bool IsClosed => ShoppingCartStatus.Closed.HasFlag(Status);
-
- public static string StreamName(Guid id) => $"shopping_cart-{id}";
-
- public void Evolve(object @event)
- {
- switch (@event)
- {
- case ShoppingCartOpened opened:
- Apply(opened);
- break;
- case ProductItemAddedToShoppingCart productItemAdded:
- Apply(productItemAdded);
- break;
- case ProductItemRemovedFromShoppingCart productItemRemoved:
- Apply(productItemRemoved);
- break;
- case ShoppingCartConfirmed confirmed:
- Apply(confirmed);
- break;
- case ShoppingCartCanceled canceled:
- Apply(canceled);
- break;
- }
- }
-
- public static (ShoppingCartOpened Event, ShoppingCart Aggregate) Open(
- Guid cartId,
- Guid clientId)
- {
- var @event = new ShoppingCartOpened(
- cartId,
- clientId
- );
-
-
-
- return (@event, new ShoppingCart(@event));
- }
-
- private ShoppingCart(ShoppingCartOpened @event) =>
- Apply(@event);
-
- //just for default creation of empty object
- private ShoppingCart() { }
-
- private void Apply(ShoppingCartOpened opened)
- {
- Id = opened.ShoppingCartId;
- ClientId = opened.ClientId;
- Status = ShoppingCartStatus.Pending;
- }
-
- public ProductItemAddedToShoppingCart AddProduct(
- IProductPriceCalculator productPriceCalculator,
- ProductItem productItem)
- {
- if (IsClosed)
- throw new InvalidOperationException(
- $"Adding product item for cart in '{Status}' status is not allowed.");
-
- var pricedProductItem = productPriceCalculator.Calculate(productItem);
-
- var @event = new ProductItemAddedToShoppingCart(Id, pricedProductItem);
-
- Apply(@event);
-
- return @event;
- }
-
- private void Apply(ProductItemAddedToShoppingCart productItemAdded)
- {
- var (_, pricedProductItem) = productItemAdded;
- var productId = pricedProductItem.ProductId;
- var quantityToAdd = pricedProductItem.Quantity;
-
- var current = ProductItems.SingleOrDefault(
- pi => pi.ProductId == productId
- );
-
- if (current == null)
- ProductItems.Add(pricedProductItem);
- else
- ProductItems[ProductItems.IndexOf(current)] =
- new PricedProductItem(current.ProductId, current.Quantity + quantityToAdd, current.UnitPrice);
- }
-
- public ProductItemRemovedFromShoppingCart RemoveProduct(PricedProductItem productItemToBeRemoved)
- {
- if (IsClosed)
- throw new InvalidOperationException(
- $"Removing product item for cart in '{Status}' status is not allowed.");
-
- if (!HasEnough(productItemToBeRemoved))
- throw new InvalidOperationException("Not enough product items to remove");
-
- var @event = new ProductItemRemovedFromShoppingCart(Id, productItemToBeRemoved);
-
- Apply(@event);
-
- return @event;
- }
-
- private bool HasEnough(PricedProductItem productItem)
- {
- var currentQuantity = ProductItems.Where(pi => pi.ProductId == productItem.ProductId)
- .Select(pi => pi.Quantity)
- .FirstOrDefault();
-
- return currentQuantity >= productItem.Quantity;
- }
-
- private void Apply(ProductItemRemovedFromShoppingCart productItemRemoved)
- {
- var (_, pricedProductItem) = productItemRemoved;
- var productId = pricedProductItem.ProductId;
- var quantityToRemove = pricedProductItem.Quantity;
-
- var current = ProductItems.Single(
- pi => pi.ProductId == productId
- );
-
- if (current.Quantity == quantityToRemove)
- ProductItems.Remove(current);
- else
- ProductItems[ProductItems.IndexOf(current)] =
- new PricedProductItem(current.ProductId, current.Quantity - quantityToRemove, current.UnitPrice);
- }
-
- public ShoppingCartConfirmed Confirm()
- {
- if (IsClosed)
- throw new InvalidOperationException(
- $"Confirming cart in '{Status}' status is not allowed.");
-
- var @event = new ShoppingCartConfirmed(Id, DateTime.UtcNow);
-
- Apply(@event);
-
- return @event;
- }
-
- private void Apply(ShoppingCartConfirmed confirmed)
- {
- Status = ShoppingCartStatus.Confirmed;
- ConfirmedAt = confirmed.ConfirmedAt;
- }
-
- public ShoppingCartCanceled Cancel()
- {
- if (IsClosed)
- throw new InvalidOperationException(
- $"Canceling cart in '{Status}' status is not allowed.");
-
- var @event = new ShoppingCartCanceled(Id, DateTime.UtcNow);
-
- Apply(@event);
-
- return @event;
- }
-
- private void Apply(ShoppingCartCanceled canceled)
- {
- Status = ShoppingCartStatus.Canceled;
- CanceledAt = canceled.CanceledAt;
- }
-}
-
-public enum ShoppingCartStatus
-{
- Pending = 1,
- Confirmed = 2,
- Canceled = 4,
-
- Closed = Confirmed | Canceled
-}
-
-public interface IProductPriceCalculator
-{
- PricedProductItem Calculate(ProductItem productItems);
-}
-
-public class FakeProductPriceCalculator: IProductPriceCalculator
-{
- private readonly int value;
-
- private FakeProductPriceCalculator(int value)
- {
- this.value = value;
- }
-
- public static FakeProductPriceCalculator Returning(int value) => new(value);
-
- public PricedProductItem Calculate(ProductItem productItem)
- {
- var (productId, quantity) = productItem;
- return new PricedProductItem(productId, quantity, value);
- }
-}
diff --git a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Mixed/BusinessLogicTests.cs b/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Mixed/BusinessLogicTests.cs
deleted file mode 100644
index 7c6be31a6..000000000
--- a/Workshops/IntroductionToEventSourcing/Solved/09-BusinessLogic.EventStoreDB/Mixed/BusinessLogicTests.cs
+++ /dev/null
@@ -1,160 +0,0 @@
-using FluentAssertions;
-using IntroductionToEventSourcing.BusinessLogic.Tools;
-using Xunit;
-
-namespace IntroductionToEventSourcing.BusinessLogic.Mixed;
-
-// EVENTS
-public abstract record ShoppingCartEvent
-{
- public record ShoppingCartOpened(
- Guid ShoppingCartId,
- Guid ClientId
- ): ShoppingCartEvent;
-
- public record ProductItemAddedToShoppingCart(
- Guid ShoppingCartId,
- PricedProductItem ProductItem
- ): ShoppingCartEvent;
-
- public record ProductItemRemovedFromShoppingCart(
- Guid ShoppingCartId,
- PricedProductItem ProductItem
- ): ShoppingCartEvent;
-
- public record ShoppingCartConfirmed(
- Guid ShoppingCartId,
- DateTime ConfirmedAt
- ): ShoppingCartEvent;
-
- public record ShoppingCartCanceled(
- Guid ShoppingCartId,
- DateTime CanceledAt
- ): ShoppingCartEvent;
-
- // This won't allow external inheritance
- private ShoppingCartEvent(){}
-}
-
-// VALUE OBJECTS
-public record ProductItem(
- Guid ProductId,
- int Quantity
-);
-
-public record PricedProductItem(
- Guid ProductId,
- int Quantity,
- decimal UnitPrice
-);
-
-public static class ShoppingCartExtensions
-{
- public static ShoppingCart GetShoppingCart(this IEnumerable