Skip to content

Commit

Permalink
Create basket domain project.
Browse files Browse the repository at this point in the history
Add basket aggregate in basket domain project.
Add base domain logic in basket domain project.
  • Loading branch information
a-sharifov committed Feb 14, 2024
1 parent 2bc39fd commit e4a5e3a
Show file tree
Hide file tree
Showing 24 changed files with 299 additions and 16 deletions.
9 changes: 8 additions & 1 deletion Eshop.sln
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Email.MessageBus", "crs\Ser
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Idenitty.Grpc", "crs\Services\Identity\Idenitty.Grpc\Idenitty.Grpc.csproj", "{1DEB0D00-7A0F-4F76-85CA-75E2DDA390B5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Catalog.MessageBus", "crs\Services\Catalog\Catalog.MessageBus\Catalog.MessageBus.csproj", "{CE14C952-F456-4632-A73B-AA5F63BB646A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Catalog.MessageBus", "crs\Services\Catalog\Catalog.MessageBus\Catalog.MessageBus.csproj", "{CE14C952-F456-4632-A73B-AA5F63BB646A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Basket.Domain", "crs\Services\Basket\Basket.Domain\Basket.Domain.csproj", "{6A9BF2B3-9B6D-43AC-996E-4CCCA5269922}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -211,6 +213,10 @@ Global
{CE14C952-F456-4632-A73B-AA5F63BB646A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CE14C952-F456-4632-A73B-AA5F63BB646A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE14C952-F456-4632-A73B-AA5F63BB646A}.Release|Any CPU.Build.0 = Release|Any CPU
{6A9BF2B3-9B6D-43AC-996E-4CCCA5269922}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A9BF2B3-9B6D-43AC-996E-4CCCA5269922}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A9BF2B3-9B6D-43AC-996E-4CCCA5269922}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A9BF2B3-9B6D-43AC-996E-4CCCA5269922}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -255,6 +261,7 @@ Global
{492C4BC4-B89A-4E5C-B9F0-FC7A03BFE1FB} = {7CFB86E0-426D-4822-A84E-D2BC5987D187}
{1DEB0D00-7A0F-4F76-85CA-75E2DDA390B5} = {1B13E7AE-01B6-4092-9DD0-1D53F6906791}
{CE14C952-F456-4632-A73B-AA5F63BB646A} = {708EB78A-1D36-4F33-8171-3BC0ADF1C669}
{6A9BF2B3-9B6D-43AC-996E-4CCCA5269922} = {9DF0F008-D19B-4AB9-848F-9C2CB7B759CC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {383EFC19-58A6-4418-98E0-23BD0341BA42}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ public interface IDomainEvent : INotification
/// Gets the id of the event.
/// </summary>
Guid Id { get; }
}
}
13 changes: 13 additions & 0 deletions crs/Services/Basket/Basket.Domain/Basket.Domain.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\CommonComponents\Common\Common.csproj" />
</ItemGroup>

</Project>
106 changes: 106 additions & 0 deletions crs/Services/Basket/Basket.Domain/BasketAggregate/Basket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
namespace Basket.Domain.BasketAggregate;

public sealed class Basket : AggregateRoot<BasketId>
{
private readonly UserId UserId;
private readonly List<BasketItem> _basketItems = [];
public IReadOnlyCollection<BasketItem> BasketItems => _basketItems.AsReadOnly();

private Basket(BasketId id, UserId userId)
{
Id = id;
UserId = userId;
_basketItems = [];
}

public static Result<Basket> Create(BasketId id, UserId userId, bool isUserBasketExist)
{
if (isUserBasketExist)
{
return Result.Failure<Basket>(
BasketErrors.UserBasketExist);
}

Basket basket = new(id, userId);
basket.AddDomainEvent(
new BasketCreatedDomainEvent(Guid.NewGuid(), id));

return basket;
}

public void AddItem(BasketItem basketItem, int quantity)
{
var existingItem = _basketItems
.SingleOrDefault(basketItem => basketItem.Id == basketItem.Id);

if (existingItem != null)
{
existingItem.AddQuantity(quantity);
return;
}

_basketItems.Add(basketItem);
}

public void RemoveEmptyItems() =>
_basketItems.RemoveAll(x => x.Quantity == 0);

public Result SetQuantity(BasketItemId basketItemId, int quantity)
{
var basketItemResult = GetBasketItemById(basketItemId);

if (basketItemResult.IsFailure)
{
return Result.Failure(
basketItemResult.Error);
}

basketItemResult.Value.SetQuantity(quantity);
return Result.Success();
}

public Result AddQuantity(BasketItemId basketItemId, int quantity)
{
var basketItemResult = GetBasketItemById(basketItemId);

if (basketItemResult.IsFailure)
{
return Result.Failure(
basketItemResult.Error);
}

basketItemResult.Value.AddQuantity(quantity);

return Result.Success();
}

public Result RemoveItem(BasketItemId basketItemId)
{
var basketItemResult = GetBasketItemById(basketItemId);

if (basketItemResult.IsFailure)
{
return Result.Failure(
basketItemResult.Error);
}

_basketItems.Remove(basketItemResult.Value);

return Result.Success();
}

public Result<BasketItem> GetBasketItemById(BasketItemId basketItemId)
{
var existingItem = _basketItems.SingleOrDefault(basketItem => basketItem.Id == basketItemId);

if (existingItem == null)
{
return Result.Failure<BasketItem>(
BasketErrors.BasketItemDoesNotExist);
}

return existingItem;
}

public void ClearItems() => _basketItems.Clear();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Basket.Domain.BasketAggregate.DomainEvents;

public sealed record BasketCreatedDomainEvent(Guid Id, BasketId BasketId) : IDomainEvent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Basket.Domain.BasketAggregate.Entities;

public class BasketItem : Entity<BasketItemId>
{
public CatalogProduct Product { get; private set; }
public int Quantity { get; private set; }

private BasketItem(BasketItemId id, CatalogProduct product, int quantity) =>
(Id, Product, Quantity) = (id, product, quantity);

public static Result<BasketItem> Create(BasketItemId id, CatalogProduct product, int quantity)
{
if (quantity <= 0)
{
return Result.Failure<BasketItem>(
BasketItemErrors.QuantityMustBeGreaterThanZero);
}

return new BasketItem(id, product, quantity);
}

public Result AddQuantity(int quantity) => SetQuantity(Quantity + quantity);

public Result SetQuantity(int quantity)
{
if (quantity < 0)
{
return Result.Failure(
BasketItemErrors.QuantityMustBeGreaterThanZero);
}

if (quantity > Product.Quantity)
{
return Result.Failure(
BasketItemErrors.QuantityExceedsProductCount);
}

Quantity = quantity;
return Result.Success();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Basket.Domain.BasketAggregate.Errors;

public static class BasketErrors
{
public static Error BasketItemDoesNotExist =>
new("Basket.BasketItemDoesNotExist", "Basket item does not exist.");

public static Error UserBasketExist =>
new("Basket.UserBasketExist", "User basket already exists.");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Basket.Domain.BasketAggregate.Errors;

public static class BasketItemErrors
{
public static Error QuantityMustBeGreaterThanZero =>
new("BasketItem.QuantityMustBeGreaterThanZero", "Quantity must be greater than zero");

public static Error QuantityExceedsProductCount =>
new("BasketItem.QuantityExceedsProductCount", "Quantity exceeds product count");

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Basket.Domain.BasketAggregate.Ids;

public record BasketId(Guid Value) : IStrongestId;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Basket.Domain.BasketAggregate.Ids;

public record BasketItemId(Guid Value) : IStrongestId;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Basket.Domain.BasketAggregate.Ids;

public record ProductId(Guid Value) : IStrongestId;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Basket.Domain.BasketAggregate.Ids;

public sealed record UserId(Guid Value) : IStrongestId;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Basket.Domain.BasketAggregate.Repositories;

public interface IBasketRepository : IRepository<Basket, BasketId>
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Basket.Domain.BasketAggregate.ValueObjects;

public class CatalogProduct : ValueObject
{
public ProductId ProductId { get; private set; }
public string Name { get; private set; }
public decimal Price { get; private set; }
public string Brand { get; private set; }
public string Category { get; private set; }
public string ImageUrl { get; private set; }
public int Quantity { get; private set; }
public string Sku { get; private set; }

public override IEnumerable<object> GetEqualityComponents()
{
throw new NotImplementedException();
}
}
10 changes: 10 additions & 0 deletions crs/Services/Basket/Basket.Domain/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
global using Common.Domain.Primitives;
global using Basket.Domain.BasketAggregate.Ids;
global using Common.Domain.Primitives.Results;
global using Basket.Domain.BasketAggregate.Errors;
global using Basket.Domain.BasketAggregate.Entities;
global using Common.Extensions;
global using Basket.Domain.BasketAggregate.ValueObjects;
global using Common.Domain.Primitives.Events;
global using Basket.Domain.BasketAggregate.DomainEvents;

Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using Catalog.Domain.ProductAggregate;
using Catalog.Domain.ProductAggregate.Repositories;

namespace Catalog.Application.Products.Queries.GetProductsByFilter;
namespace Catalog.Application.Products.Queries.GetProductsByFilter;

public sealed class GetProductsByFilterQuerieHandler(IProductRepository productRepository) : IQueryHandler<GetProductsByFilterQuerie, IEnumerable<Product>>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ internal sealed class AddSellerCommandHandler(

public async Task<Result> Handle(AddSellerCommand request, CancellationToken cancellationToken)
{
var userInfo = await _identityGrpcService.GetUserInfoAsync(request.UserId);
var userInfo = await _identityGrpcService.GetUserInfoAsync(request.UserId, cancellationToken);

var command = new CreateSellerCommand(userInfo.Email, userInfo.Email);
return await _sender.Send(command);
return await _sender.Send(command, cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Catalog.Domain.ProductAggregate.Errors;

public static class QuantityErrors
{
public static Error QuantityMustBeGreaterThanZero =>
new("Product.QuantityMustBeGreaterThanZero", "Quantity must be greater than zero");
}
11 changes: 8 additions & 3 deletions crs/Services/Catalog/Catalog.Domain/ProductAggregate/Product.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class Product : AggregateRoot<ProductId>
public Brand Brand { get; private set; }
public ImageUrl ProductImage { get; private set; }
public ProductDescription Description { get; private set; }
public Quantity Quantity { get; private set; }

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private Product() { }
Expand All @@ -24,7 +25,8 @@ private Product(
Seller seller,
Brand brand,
ImageUrl productImage,
ProductDescription description)
ProductDescription description,
Quantity quantity)
: base(id)
{
Sku = sku;
Expand All @@ -35,6 +37,7 @@ private Product(
Brand = brand;
ProductImage = productImage;
Description = description;
Quantity = quantity;
}

public static Result<Product> Create(
Expand All @@ -46,7 +49,8 @@ public static Result<Product> Create(
Seller seller,
Brand brand,
ImageUrl productImage,
ProductDescription description)
ProductDescription description,
Quantity quantity)
{
var product = new Product(
id,
Expand All @@ -57,7 +61,8 @@ public static Result<Product> Create(
seller,
brand,
productImage,
description);
description,
quantity);

product.AddDomainEvent(
new ProductCreatedDomainEvent(Guid.NewGuid(), id));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

namespace Catalog.Domain.ProductAggregate.ValueObjects;

public sealed class Quantity : ValueObject
{
public int Value { get; private set; }

private Quantity(int value) => Value = value;

public static Result<Quantity> Create(int value)
{
if (value < 0)
{
return Result.Failure<Quantity>(
QuantityErrors.QuantityMustBeGreaterThanZero);
}

return new Quantity(value);
}

public static Quantity Empty => new(0);

public override IEnumerable<object> GetEqualityComponents()
{
yield return Value;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Catalog.Application.Sellers.Commands.CreateSeller;
using Catalog.Application.Sellers.Commands.AddSeller;

namespace Catalog.MessageBus.Handlers.Events;

Expand All @@ -9,6 +9,8 @@ internal sealed class IdentityVerificationConfirmedEventHandler(ISender sender)

public override async Task Handle(ConsumeContext<IdentityVerificationConfirmedEvent> context)
{
var command = new CreateSellerCommand(context.Message.UserId);
var command = new AddSellerCommand(context.Message.UserId);

await _sender.Send(command);
}
}
Loading

0 comments on commit e4a5e3a

Please sign in to comment.