diff --git a/Sample/ECommerce/CartsMinimalApi/Carts.Api/Carts.Api.csproj b/Sample/ECommerce/CartsMinimalApi/Carts.Api/Carts.Api.csproj new file mode 100644 index 000000000..51c65fac0 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts.Api/Carts.Api.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/Sample/ECommerce/CartsMinimalApi/Carts.Api/Program.cs b/Sample/ECommerce/CartsMinimalApi/Carts.Api/Program.cs new file mode 100644 index 000000000..1c09aa229 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts.Api/Program.cs @@ -0,0 +1,9 @@ +using Carter; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddCarter(); + +var app = builder.Build(); +app.MapCarter(); + +app.Run(); diff --git a/Sample/ECommerce/CartsMinimalApi/Carts.Api/ShoppingCartModule.cs b/Sample/ECommerce/CartsMinimalApi/Carts.Api/ShoppingCartModule.cs new file mode 100644 index 000000000..d70b91468 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts.Api/ShoppingCartModule.cs @@ -0,0 +1,53 @@ +using Carter; +using Carter.Request; +using Carts.ShoppingCarts; +using Carts.ShoppingCarts.AddingProduct; +using Carts.ShoppingCarts.CancellingCart; +using Carts.ShoppingCarts.ConfirmingCart; +using Carts.ShoppingCarts.OpeningCart; +using Carts.ShoppingCarts.RemovingProduct; + +public class ShoppingCartModule: ICarterModule +{ + public void AddRoutes(IEndpointRouteBuilder app) + { + app.MapPost("/shoppingcart", OpenCart); + app.MapPost("/shoppingcart/{cartId:guid}/products", AddProduct); + app.MapDelete("/shoppingcart/{cartId:guid}/products/{productId:guid}", RemoveProduct); + app.MapPut("/shoppingcart/{cartId:guid}/confirmation", ConfirmCart); + app.MapDelete("/shoppingcart/{cartId:guid}", CancelCart); + } + + private IResult CancelCart(HttpContext context, Guid cartId, ICancelCartService cancelCartService) + { + cancelCartService.CancelCart(cartId); + return Results.StatusCode(204); + } + + private IResult ConfirmCart(HttpContext context, Guid cartId, IConfirmCartService confirmCartService) + { + confirmCartService.Confirm(cartId); + return Results.StatusCode(204); + } + + private IResult RemoveProduct(HttpContext context, Guid cartId, Guid productId, + IRemoveProductService removeProductService) + { + removeProductService.RemoveProduct(cartId, productId, context.Request.Query.As("quantity"), + context.Request.Query.As("unitPrice")); + return Results.StatusCode(204); + } + + private IResult AddProduct(HttpContext context, Guid cartId, AddProductRequest model, + IAddProductService addProductService) + { + addProductService.AddProduct(cartId, model.ProductId, model.Quantity); + return Results.StatusCode(204); + } + + private IResult OpenCart(HttpContext context, OpenShoppingCartRequest model, IOpenCartService openCartService) + { + var cartId = openCartService.OpenCart(model.ClientId); + return Results.Created($"/shoppingcart/{cartId}", null); + } +} diff --git a/Sample/ECommerce/CartsMinimalApi/Carts.Api/ShoppingCartsRequests.cs b/Sample/ECommerce/CartsMinimalApi/Carts.Api/ShoppingCartsRequests.cs new file mode 100644 index 000000000..afae67983 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts.Api/ShoppingCartsRequests.cs @@ -0,0 +1,27 @@ +namespace Carts.ShoppingCarts; + +public record OpenShoppingCartRequest( + Guid ClientId +); + +public record AddProductRequest( + Guid ProductId, + int Quantity +); + +public record PricedProductItemRequest( + Guid? ProductId, + int? Quantity, + decimal? UnitPrice +); + +public record RemoveProductRequest( + PricedProductItemRequest? ProductItem +); + +public record ConfirmShoppingCartRequest; + +public record GetCartAtVersionRequest( + Guid? CartId, + long? Version +); diff --git a/Sample/ECommerce/CartsMinimalApi/Carts.Api/appsettings.Development.json b/Sample/ECommerce/CartsMinimalApi/Carts.Api/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts.Api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Sample/ECommerce/CartsMinimalApi/Carts.Api/appsettings.json b/Sample/ECommerce/CartsMinimalApi/Carts.Api/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/Carts.csproj b/Sample/ECommerce/CartsMinimalApi/Carts/Carts.csproj new file mode 100644 index 000000000..aefdfc0bd --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/Carts.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/DataAccess/IShoppingCartRepository.cs b/Sample/ECommerce/CartsMinimalApi/Carts/DataAccess/IShoppingCartRepository.cs new file mode 100644 index 000000000..9b90cd2d6 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/DataAccess/IShoppingCartRepository.cs @@ -0,0 +1,7 @@ +namespace Carts.DataAccess; + +public interface IShoppingCartRepository +{ + ShoppingCart GetById(Guid id); + void Save(ShoppingCart cart); +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/Pricing/IProductPriceCalculator.cs b/Sample/ECommerce/CartsMinimalApi/Carts/Pricing/IProductPriceCalculator.cs new file mode 100644 index 000000000..6429af0fd --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/Pricing/IProductPriceCalculator.cs @@ -0,0 +1,8 @@ +using Carts.ShoppingCarts.Products; + +namespace Carts.Pricing; + +public interface IProductPriceCalculator +{ + IReadOnlyList Calculate(params ProductItem[] productItems); +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/Pricing/RandomProductPriceCalculator.cs b/Sample/ECommerce/CartsMinimalApi/Carts/Pricing/RandomProductPriceCalculator.cs new file mode 100644 index 000000000..143aa602b --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/Pricing/RandomProductPriceCalculator.cs @@ -0,0 +1,19 @@ +using Carts.ShoppingCarts.Products; + +namespace Carts.Pricing; + +public class RandomProductPriceCalculator: IProductPriceCalculator +{ + public IReadOnlyList Calculate(params ProductItem[] productItems) + { + if (productItems.Length == 0) + throw new ArgumentOutOfRangeException(nameof(productItems.Length)); + + var random = new Random(); + + return productItems + .Select(pi => + PricedProductItem.Create(pi, Math.Round(new decimal(random.NextDouble() * 100), 2))) + .ToList(); + } +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCart.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCart.cs new file mode 100644 index 000000000..009dd9d9e --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCart.cs @@ -0,0 +1,114 @@ +using Carts.Pricing; +using Carts.ShoppingCarts.Products; +using Core.Extensions; + +namespace Carts; + +public class ShoppingCart +{ + public Guid Id { get; private set; } + + public Guid ClientId { get; private set; } + + public ShoppingCartStatus Status { get; private set; } + + public IList ProductItems { get; private set; } = default!; + + public Money TotalPrice => ProductItems.Sum(pi => pi.TotalPrice); + + public static ShoppingCart Open( + Guid cartId, + Guid clientId) + { + return new ShoppingCart(cartId, clientId); + } + + public ShoppingCart() { } + + private ShoppingCart( + Guid id, + Guid clientId) + { + Id = id; + ClientId = clientId; + ProductItems = new List(); + Status = ShoppingCartStatus.Pending; + } + + public void AddProduct( + IProductPriceCalculator productPriceCalculator, + ProductItem productItem) + { + if (Status != ShoppingCartStatus.Pending) + throw new InvalidOperationException($"Adding product for the cart in '{Status}' status is not allowed."); + + var pricedProductItem = productPriceCalculator.Calculate(productItem).Single(); + + var existingProductItem = FindProductItemMatchingWith(pricedProductItem); + + if (existingProductItem is null) + { + ProductItems.Add(pricedProductItem); + return; + } + + ProductItems.Replace( + existingProductItem, + existingProductItem.MergeWith(pricedProductItem) + ); + } + + + public void RemoveProduct( + PricedProductItem productItemToBeRemoved) + { + if (Status != ShoppingCartStatus.Pending) + throw new InvalidOperationException($"Removing product from the cart in '{Status}' status is not allowed."); + + var existingProductItem = FindProductItemMatchingWith(productItemToBeRemoved); + + if (existingProductItem is null) + throw new InvalidOperationException( + $"Product with id `{productItemToBeRemoved.ProductId}` and price '{productItemToBeRemoved.UnitPrice}' was not found in cart."); + + if (!existingProductItem.HasEnough(productItemToBeRemoved.Quantity)) + throw new InvalidOperationException( + $"Cannot remove {productItemToBeRemoved.Quantity} items of Product with id `{productItemToBeRemoved.ProductId}` as there are only ${existingProductItem.Quantity} items in card"); + + + if (existingProductItem.HasTheSameQuantity(productItemToBeRemoved)) + { + ProductItems.Remove(existingProductItem); + return; + } + + ProductItems.Replace( + existingProductItem, + existingProductItem.Subtract(productItemToBeRemoved) + ); + } + + public void Confirm() + { + if (Status != ShoppingCartStatus.Pending) + throw new InvalidOperationException($"Confirming cart in '{Status}' status is not allowed."); + + Status = ShoppingCartStatus.Confirmed; + } + + + public void Cancel() + { + if (Status != ShoppingCartStatus.Pending) + throw new InvalidOperationException($"Canceling cart in '{Status}' status is not allowed."); + + + Status = ShoppingCartStatus.Canceled; + } + + private PricedProductItem? FindProductItemMatchingWith(PricedProductItem productItem) + { + return ProductItems + .SingleOrDefault(pi => pi.MatchesProductAndPrice(productItem)); + } +} diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCartStatus.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCartStatus.cs new file mode 100644 index 000000000..9fc7429f4 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCartStatus.cs @@ -0,0 +1,8 @@ +namespace Carts; + +public enum ShoppingCartStatus +{ + Pending = 1, + Confirmed = 2, + Canceled = 4 +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/AddingProduct/AddProductService.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/AddingProduct/AddProductService.cs new file mode 100644 index 000000000..37b20c9df --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/AddingProduct/AddProductService.cs @@ -0,0 +1,25 @@ +using Carts.DataAccess; +using Carts.Pricing; +using Carts.ShoppingCarts.Products; + +namespace Carts.ShoppingCarts.AddingProduct; + +public class AddProductService: IAddProductService +{ + private readonly IProductPriceCalculator productPriceCalculator; + private readonly IShoppingCartRepository shoppingCartRepository; + + public AddProductService(IProductPriceCalculator productPriceCalculator, + IShoppingCartRepository shoppingCartRepository) + { + this.productPriceCalculator = productPriceCalculator; + this.shoppingCartRepository = shoppingCartRepository; + } + + public void AddProduct(Guid cartId, Guid productId, int quantity) + { + var cart = this.shoppingCartRepository.GetById(cartId); + cart.AddProduct(this.productPriceCalculator, ProductItem.Create(productId, quantity)); + this.shoppingCartRepository.Save(cart); + } +} diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/AddingProduct/IAddProductService.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/AddingProduct/IAddProductService.cs new file mode 100644 index 000000000..108ae822f --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/AddingProduct/IAddProductService.cs @@ -0,0 +1,6 @@ +namespace Carts.ShoppingCarts.AddingProduct; + +public interface IAddProductService +{ + void AddProduct(Guid cartId, Guid productId, int quantity); +} diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/CancellingCart/CancelCartService.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/CancellingCart/CancelCartService.cs new file mode 100644 index 000000000..71caadbef --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/CancellingCart/CancelCartService.cs @@ -0,0 +1,20 @@ +using Carts.DataAccess; + +namespace Carts.ShoppingCarts.CancellingCart; + +public class CancelCartService: ICancelCartService +{ + private readonly IShoppingCartRepository shoppingCartRepository; + + public CancelCartService(IShoppingCartRepository shoppingCartRepository) + { + this.shoppingCartRepository = shoppingCartRepository; + } + + public void CancelCart(Guid cartId) + { + var cart = this.shoppingCartRepository.GetById(cartId); + cart.Cancel(); + this.shoppingCartRepository.Save(cart); + } +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/CancellingCart/ICancelCartService.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/CancellingCart/ICancelCartService.cs new file mode 100644 index 000000000..bcbce6847 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/CancellingCart/ICancelCartService.cs @@ -0,0 +1,6 @@ +namespace Carts.ShoppingCarts.CancellingCart; + +public interface ICancelCartService +{ + void CancelCart(Guid cartId); +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/ConfirmingCart/ConfirmCartService.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/ConfirmingCart/ConfirmCartService.cs new file mode 100644 index 000000000..ad266c27b --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/ConfirmingCart/ConfirmCartService.cs @@ -0,0 +1,20 @@ +using Carts.DataAccess; + +namespace Carts.ShoppingCarts.ConfirmingCart; + +public class ConfirmCartService: IConfirmCartService +{ + private readonly IShoppingCartRepository shoppingCartRepository; + + public ConfirmCartService(IShoppingCartRepository shoppingCartRepository) + { + this.shoppingCartRepository = shoppingCartRepository; + } + + public void Confirm(Guid cartId) + { + var cart = this.shoppingCartRepository.GetById(cartId); + cart.Confirm(); + this.shoppingCartRepository.Save(cart); + } +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/ConfirmingCart/IConfirmCartService.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/ConfirmingCart/IConfirmCartService.cs new file mode 100644 index 000000000..8ef7925f8 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/ConfirmingCart/IConfirmCartService.cs @@ -0,0 +1,6 @@ +namespace Carts.ShoppingCarts.ConfirmingCart; + +public interface IConfirmCartService +{ + void Confirm(Guid cartId); +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/OpeningCart/IOpenCartService.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/OpeningCart/IOpenCartService.cs new file mode 100644 index 000000000..5583091c9 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/OpeningCart/IOpenCartService.cs @@ -0,0 +1,6 @@ +namespace Carts.ShoppingCarts.OpeningCart; + +public interface IOpenCartService +{ + Guid OpenCart(Guid clientId); +} diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/OpeningCart/OpenCartService.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/OpeningCart/OpenCartService.cs new file mode 100644 index 000000000..9959c12f7 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/OpeningCart/OpenCartService.cs @@ -0,0 +1,20 @@ +using Carts.DataAccess; + +namespace Carts.ShoppingCarts.OpeningCart; + +public class OpenCartService: IOpenCartService +{ + private readonly IShoppingCartRepository shoppingCartRepository; + + public OpenCartService(IShoppingCartRepository shoppingCartRepository) + { + this.shoppingCartRepository = shoppingCartRepository; + } + + public Guid OpenCart(Guid clientId) + { + var cart = ShoppingCart.Open(Guid.NewGuid(), clientId); + this.shoppingCartRepository.Save(cart); + return cart.Id; + } +} diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/Products/Money.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/Products/Money.cs new file mode 100644 index 000000000..9cfa74323 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/Products/Money.cs @@ -0,0 +1,53 @@ +namespace Carts.ShoppingCarts.Products; + +public struct Money: IEquatable +{ + private decimal Value { get; } + + public Money(decimal value) + { + this.Value = value; + + } + + public bool Equals(Money other) + { + return Value == other.Value; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + + return obj is Money money && Equals(money); + } + + public override int GetHashCode() + { + unchecked + { + return ((Value.GetHashCode()) * 397); + } + } + + public static bool operator ==(Money a, Money b) + { + return a.Equals(b); + } + + public static bool operator !=(Money a, Money b) + { + return !(a == b); + } + + public static Money operator *(Money a, int quantity) + { + return new Money(a.Value * quantity); + } + + public static Money operator +(Money a, Money b) + { + return new Money(a.Value + b.Value); + } +} diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/Products/PricedProductItem.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/Products/PricedProductItem.cs new file mode 100644 index 000000000..1766ceba9 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/Products/PricedProductItem.cs @@ -0,0 +1,69 @@ +namespace Carts.ShoppingCarts.Products; + +public class PricedProductItem +{ + public Guid ProductId => ProductItem.ProductId; + + public int Quantity => ProductItem.Quantity; + + public Money UnitPrice { get; } + + public Money TotalPrice => UnitPrice * Quantity; + public ProductItem ProductItem { get; } + + private PricedProductItem(ProductItem productItem, Money unitPrice) + { + ProductItem = productItem; + UnitPrice = unitPrice; + } + + public static PricedProductItem Create(Guid? productId, int? quantity, decimal? unitPrice) + { + return Create( + ProductItem.Create(productId, quantity), + unitPrice + ); + } + + public static PricedProductItem Create(ProductItem productItem, decimal? unitPrice) + { + return unitPrice switch + { + null => throw new ArgumentNullException(nameof(unitPrice)), + <= 0 => throw new ArgumentOutOfRangeException(nameof(unitPrice), + "Unit price has to be positive number"), + _ => new PricedProductItem(productItem, new Money(unitPrice.Value)) + }; + } + + public bool MatchesProductAndPrice(PricedProductItem pricedProductItem) + { + return ProductId == pricedProductItem.ProductId && UnitPrice == pricedProductItem.UnitPrice; + } + + public PricedProductItem MergeWith(PricedProductItem pricedProductItem) + { + if (!MatchesProductAndPrice(pricedProductItem)) + throw new ArgumentException("Product or price does not match."); + + return new PricedProductItem(ProductItem.MergeWith(pricedProductItem.ProductItem), UnitPrice); + } + + public PricedProductItem Subtract(PricedProductItem pricedProductItem) + { + if (!MatchesProductAndPrice(pricedProductItem)) + throw new ArgumentException("Product or price does not match."); + + return new PricedProductItem(ProductItem.Substract(pricedProductItem.ProductItem), UnitPrice); + } + + public bool HasEnough(int quantity) + { + return ProductItem.HasEnough(quantity); + } + + public bool HasTheSameQuantity(PricedProductItem pricedProductItem) + { + return ProductItem.HasTheSameQuantity(pricedProductItem.ProductItem); + } +} diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/Products/ProductItem.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/Products/ProductItem.cs new file mode 100644 index 000000000..76e0b3fe1 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/Products/ProductItem.cs @@ -0,0 +1,58 @@ +namespace Carts.ShoppingCarts.Products; + +public class ProductItem +{ + public Guid ProductId { get; } + + public int Quantity { get; } + + private ProductItem(Guid productId, int quantity) + { + ProductId = productId; + Quantity = quantity; + } + + public static ProductItem Create(Guid? productId, int? quantity) + { + if (!productId.HasValue) + throw new ArgumentNullException(nameof(productId)); + + return quantity switch + { + null => throw new ArgumentNullException(nameof(quantity)), + <= 0 => throw new ArgumentOutOfRangeException(nameof(quantity), "Quantity has to be a positive number"), + _ => new ProductItem(productId.Value, quantity.Value) + }; + } + + public ProductItem MergeWith(ProductItem productItem) + { + if (!MatchesProduct(productItem)) + throw new ArgumentException("Product does not match."); + + return Create(ProductId, Quantity + productItem.Quantity); + } + + public ProductItem Substract(ProductItem productItem) + { + if (!MatchesProduct(productItem)) + throw new ArgumentException("Product does not match."); + + return Create(ProductId, Quantity - productItem.Quantity); + } + + public bool MatchesProduct(ProductItem productItem) + { + return ProductId == productItem.ProductId; + } + + public bool HasEnough(int quantity) + { + return Quantity >= quantity; + } + + public bool HasTheSameQuantity(ProductItem productItem) + { + return Quantity == productItem.Quantity; + } +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/RemovingProduct/IRemoveProductService.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/RemovingProduct/IRemoveProductService.cs new file mode 100644 index 000000000..43af916f8 --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/RemovingProduct/IRemoveProductService.cs @@ -0,0 +1,6 @@ +namespace Carts.ShoppingCarts.RemovingProduct; + +public interface IRemoveProductService +{ + void RemoveProduct(Guid cartId, Guid productId, int? quantity, decimal? unitPrice); +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/RemovingProduct/RemoveProductService.cs b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/RemovingProduct/RemoveProductService.cs new file mode 100644 index 000000000..fe5c955aa --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/ShoppingCarts/RemovingProduct/RemoveProductService.cs @@ -0,0 +1,22 @@ +using Carts.DataAccess; +using Carts.ShoppingCarts.Products; + +namespace Carts.ShoppingCarts.RemovingProduct; + +public class RemoveProductService: IRemoveProductService +{ + private readonly IShoppingCartRepository shoppingCartRepository; + + public RemoveProductService(IShoppingCartRepository shoppingCartRepository) + { + this.shoppingCartRepository = shoppingCartRepository; + } + + public void RemoveProduct(Guid cartId, Guid productId, int? quantity, decimal? unitPrice) + { + var pricedProduct = PricedProductItem.Create(productId, quantity, unitPrice); + var cart = this.shoppingCartRepository.GetById(cartId); + cart.RemoveProduct(pricedProduct); + this.shoppingCartRepository.Save(cart); + } +} \ No newline at end of file diff --git a/Sample/ECommerce/CartsMinimalApi/Carts/SumExtensions.cs b/Sample/ECommerce/CartsMinimalApi/Carts/SumExtensions.cs new file mode 100644 index 000000000..7385bb6fb --- /dev/null +++ b/Sample/ECommerce/CartsMinimalApi/Carts/SumExtensions.cs @@ -0,0 +1,11 @@ +using Carts.ShoppingCarts.Products; + +namespace Carts; + +public static class SumExtensions +{ + public static Money Sum(this IEnumerable source, Func selector) + { + return source.Select(selector).Aggregate((x, y) => x + y); + } +} diff --git a/Sample/ECommerce/PracticalEventSourcing.sln b/Sample/ECommerce/PracticalEventSourcing.sln index 4707393e6..f7c3fdf37 100644 --- a/Sample/ECommerce/PracticalEventSourcing.sln +++ b/Sample/ECommerce/PracticalEventSourcing.sln @@ -6,8 +6,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "..\..\Core\Core.csp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Marten", "..\..\Core.Marten\Core.Marten.csproj", "{9268B3EA-71E1-4B50-A459-5732F4977942}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Testing", "..\..\Core.Testing\Core.Testing.csproj", "{8A50B198-C3ED-4DE8-8D63-11FF005C53C1}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.WebApi", "..\..\Core.WebApi\Core.WebApi.csproj", "{72ACB5C8-FD64-4041-BA20-BFCC5A04E97C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Carts", "Carts", "{190C033E-DF29-4F8F-A048-7E860ADD082D}" @@ -68,6 +66,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Api.Testing", "..\..\C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Serialization", "..\..\Core.Serialization\Core.Serialization.csproj", "{F519CFB6-9B4D-4317-B442-32B5F4EC5A4C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CartsMinimalApi", "CartsMinimalApi", "{1839223D-398E-451E-B250-06ADEA64AA98}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Carts.Api", "CartsMinimalApi\Carts.Api\Carts.Api.csproj", "{E6802F17-997D-4910-ADF6-6CA273D4C827}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Carts", "CartsMinimalApi\Carts\Carts.csproj", "{36387ADB-9DE4-4670-A7EA-1E3D1614D886}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -76,7 +80,6 @@ Global GlobalSection(NestedProjects) = preSolution {607CC0DE-712A-4FFB-9862-FEB4F10100D2} = {CC9592F6-C639-4C1E-A089-E5A7F4B9BAEA} {9268B3EA-71E1-4B50-A459-5732F4977942} = {CC9592F6-C639-4C1E-A089-E5A7F4B9BAEA} - {8A50B198-C3ED-4DE8-8D63-11FF005C53C1} = {CC9592F6-C639-4C1E-A089-E5A7F4B9BAEA} {72ACB5C8-FD64-4041-BA20-BFCC5A04E97C} = {CC9592F6-C639-4C1E-A089-E5A7F4B9BAEA} {6ED65CEC-8661-483F-A7BE-5B6EAAE7E5A2} = {190C033E-DF29-4F8F-A048-7E860ADD082D} {AB956DE1-6927-49D3-986C-6298AD92FC04} = {190C033E-DF29-4F8F-A048-7E860ADD082D} @@ -98,6 +101,8 @@ Global {714E605E-6EB2-41F4-A2BC-FACD7F781907} = {CC9592F6-C639-4C1E-A089-E5A7F4B9BAEA} {3EFCB989-6F16-4D69-8C3D-7F862243FAB9} = {CC9592F6-C639-4C1E-A089-E5A7F4B9BAEA} {F519CFB6-9B4D-4317-B442-32B5F4EC5A4C} = {CC9592F6-C639-4C1E-A089-E5A7F4B9BAEA} + {E6802F17-997D-4910-ADF6-6CA273D4C827} = {1839223D-398E-451E-B250-06ADEA64AA98} + {36387ADB-9DE4-4670-A7EA-1E3D1614D886} = {1839223D-398E-451E-B250-06ADEA64AA98} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {607CC0DE-712A-4FFB-9862-FEB4F10100D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -108,10 +113,6 @@ Global {9268B3EA-71E1-4B50-A459-5732F4977942}.Debug|Any CPU.Build.0 = Debug|Any CPU {9268B3EA-71E1-4B50-A459-5732F4977942}.Release|Any CPU.ActiveCfg = Release|Any CPU {9268B3EA-71E1-4B50-A459-5732F4977942}.Release|Any CPU.Build.0 = Release|Any CPU - {8A50B198-C3ED-4DE8-8D63-11FF005C53C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A50B198-C3ED-4DE8-8D63-11FF005C53C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A50B198-C3ED-4DE8-8D63-11FF005C53C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A50B198-C3ED-4DE8-8D63-11FF005C53C1}.Release|Any CPU.Build.0 = Release|Any CPU {72ACB5C8-FD64-4041-BA20-BFCC5A04E97C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {72ACB5C8-FD64-4041-BA20-BFCC5A04E97C}.Debug|Any CPU.Build.0 = Debug|Any CPU {72ACB5C8-FD64-4041-BA20-BFCC5A04E97C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -196,5 +197,13 @@ Global {F519CFB6-9B4D-4317-B442-32B5F4EC5A4C}.Debug|Any CPU.Build.0 = Debug|Any CPU {F519CFB6-9B4D-4317-B442-32B5F4EC5A4C}.Release|Any CPU.ActiveCfg = Release|Any CPU {F519CFB6-9B4D-4317-B442-32B5F4EC5A4C}.Release|Any CPU.Build.0 = Release|Any CPU + {E6802F17-997D-4910-ADF6-6CA273D4C827}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6802F17-997D-4910-ADF6-6CA273D4C827}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6802F17-997D-4910-ADF6-6CA273D4C827}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6802F17-997D-4910-ADF6-6CA273D4C827}.Release|Any CPU.Build.0 = Release|Any CPU + {36387ADB-9DE4-4670-A7EA-1E3D1614D886}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36387ADB-9DE4-4670-A7EA-1E3D1614D886}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36387ADB-9DE4-4670-A7EA-1E3D1614D886}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36387ADB-9DE4-4670-A7EA-1E3D1614D886}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal