Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

namespace DMHexagonal.Catalog.Core.BrandAggregate.Events;

public record BrandCreated(BrandId Id, BrandNameValue Name) : DomainEvent;
public record BrandCreated(BrandId Id, BrandNameValue Name) : DomainEvent;
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

namespace DMHexagonal.Catalog.Core.BrandAggregate.Events;

public record BrandDeleted(BrandId Id) : DomainEvent;
public record BrandDeleted(BrandId Id) : DomainEvent;
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

namespace DMHexagonal.Catalog.Core.BrandAggregate.Events;

public record BrandRenamed(BrandId Id, BrandNameValue Name) : DomainEvent;
public record BrandRenamed(BrandId Id, BrandNameValue Name) : DomainEvent;
8 changes: 6 additions & 2 deletions CSharpBase/src/DMHexagonal/Catalog/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Inventory.

Пример основного поддомена инвентаризационной базы
Пример основного поддомена каталога товаров

## Описание

Образец основого поддомена, размещающий в себе простую бизнес-логику.

## Назначение

Инвентаризация предметов на складе, хранение названий и количество элементов.
Хранение в каталоге информации о всех товаров магазина, которые доступны для покупки.

## Сценарии

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,10 @@ public static void Run(IRegisterDispatcher dispatcher)
{
Console.WriteLine($"## Событие переименования товара c Id: {ev.Id} Новое имя: {ev.Name}");
});

dispatcher.RegisterHandler<ProductPriceChanged>(ev =>
{
Console.WriteLine($"## Событие изменения цены товара c Id: {ev.Id} Новая цена: {ev.Price} руб.");
});
}
}
2 changes: 1 addition & 1 deletion CSharpBase/src/DMHexagonal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

## Содержимое

Пример основного поддомена инвентаризационной базы
Пример основного поддомена каталога товаров

Образцовое консольное приложение

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<ProjectReference Include="..\ESCQRS.Catalog.Core\ESCQRS.Catalog.Core.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace ESCQRS.Catalog.Core.Base.Abstractions;

public interface IDispatcher : IDispatchDispatcher
{
void RegisterHandler<T>(Action<T> handler)
where T : IMessage;
}

public interface IDispatchDispatcher
{
void Dispatch(IEnumerable<IMessage> events);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ESCQRS.Catalog.Core.Base.Abstractions;

public interface IEventStore
{
EventStream LoadEventStream(IId id);

void AppendToStream(IId id, ICollection<IMessage> events, int expectedVersion);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace ESCQRS.Catalog.Core.Base.Abstractions;

public interface IId;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace ESCQRS.Catalog.Core.Base.Abstractions;

public interface IMessage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace ESCQRS.Catalog.Core.Base.Abstractions;

public interface IStorage<T> : ITransactionalStorage
where T : EventAggregateRoot, new()
{
T Load(IId id);

void Save(T aggregate);
}

public interface ITransactionalStorage
{
public void BeginTransaction();
public void Commit();
public void Rollback();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using ESCQRS.Catalog.Core.Base.Abstractions;

namespace ESCQRS.Catalog.Core.Base;

public record DomainEvent : IMessage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using ESCQRS.Catalog.Core.Base.Abstractions;

namespace ESCQRS.Catalog.Core.Base;

public abstract class EventAggregateRoot
{
private readonly List<IMessage> _changes = new();

public abstract IId Id { get; }

public int Version { get; private set; } = -1;

public void Apply(EventStream stream)
{
foreach (var @event in stream.Events) Mutate(@event);
Version = stream.Version;
}

public ICollection<IMessage> Changes() => _changes;

public void Reset() => _changes.Clear();

protected void ApplyChange(IMessage @event)
{
Mutate(@event);
_changes.Add(@event);
}

protected abstract void Mutate(IMessage @event);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using ESCQRS.Catalog.Core.Base.Abstractions;

namespace ESCQRS.Catalog.Core.Base;

public record EventStream(ICollection<IMessage> Events, int Version = -1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using ESCQRS.Catalog.Core.Base;
using ESCQRS.Catalog.Core.Base.Abstractions;
using ESCQRS.Catalog.Core.BrandAggregate.Events;
using ESCQRS.Catalog.Core.BrandAggregate.Values;

namespace ESCQRS.Catalog.Core.BrandAggregate;

public class BrandItem : EventAggregateRoot
{
private BrandId _id = null!;
public override BrandId Id => _id;

public static BrandItem Create(BrandId id, BrandNameValue name)
{
var result = new BrandItem();
result.ApplyChange(new BrandCreated(id, name));
return result;
}

public void Rename(BrandNameValue newName)
{
ApplyChange(new BrandRenamed(Id, newName));
}

public void Delete()
{
ApplyChange(new BrandDeleted(Id));
}

protected override void Mutate(IMessage @event) =>
((dynamic)this).when((dynamic)@event);

private void when(BrandCreated ev)
{
_id = ev.Id;
}

private void when(BrandRenamed ev) { }

private void when(BrandDeleted ev) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using ESCQRS.Catalog.Core.Base;
using ESCQRS.Catalog.Core.BrandAggregate.Values;

namespace ESCQRS.Catalog.Core.BrandAggregate.Events;

public record BrandCreated(BrandId Id, BrandNameValue Name) : DomainEvent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using ESCQRS.Catalog.Core.Base;
using ESCQRS.Catalog.Core.BrandAggregate.Values;

namespace ESCQRS.Catalog.Core.BrandAggregate.Events;

public record BrandDeleted(BrandId Id) : DomainEvent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using ESCQRS.Catalog.Core.Base;
using ESCQRS.Catalog.Core.BrandAggregate.Values;

namespace ESCQRS.Catalog.Core.BrandAggregate.Events;

public record BrandRenamed(BrandId Id, BrandNameValue Name) : DomainEvent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using ESCQRS.Catalog.Core.Base.Abstractions;
using Kanadeiar.Common.Functionals;

namespace ESCQRS.Catalog.Core.BrandAggregate.Values;

public record BrandId(Guid Value) : IId
{
public static BrandId New() => new(Guid.NewGuid());

public Guid Value { get; } = Value.Require(Value != Guid.Empty, () =>
throw new ApplicationException("Номер идентификатора должен быть назначен"));

public override string ToString() => Value.ToString();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Kanadeiar.Common.Functionals;

namespace ESCQRS.Catalog.Core.BrandAggregate.Values;

public record BrandNameValue(string Value)
{
public string Value { get; } = Value.Require(Value.Length is >= 3 and <= 300, () =>
throw new ApplicationException("Название бренда товаров должно быть длинной от 3 до 300 символов"));

public override string ToString() => $"{Value}";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace ESCQRS.Catalog.Core.CategoryAggregate;

public class CategoryItem
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using ESCQRS.Catalog.Core.Base;
using ESCQRS.Catalog.Core.CategoryAggregate.Values;

namespace ESCQRS.Catalog.Core.CategoryAggregate.Events;

public record CategoryCreated(CategoryId Id, CategoryNameValue Name) : DomainEvent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using ESCQRS.Catalog.Core.Base.Abstractions;
using Kanadeiar.Common.Functionals;

namespace ESCQRS.Catalog.Core.CategoryAggregate.Values;

public record CategoryId(Guid Value) : IId
{
public static CategoryId New() => new(Guid.NewGuid());

public Guid Value { get; } = Value.Require(Value != Guid.Empty, () =>
throw new ApplicationException("Номер идентификатора должен быть назначен"));

public override string ToString() => Value.ToString();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Kanadeiar.Common.Functionals;

namespace ESCQRS.Catalog.Core.CategoryAggregate.Values;

public record CategoryNameValue(string Value)
{
public string Value { get; } = Value.Require(Value.Length is >= 3 and <= 300, () =>
throw new ApplicationException("Название категории товаров должно быть длинной от 3 до 300 символов"));

public override string ToString() => $"{Value}";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<Compile Include="$(SolutionDir)\shared\Functionals\**\*.*">
<Link>Common\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
</ItemGroup>

<ItemGroup>
<Folder Include="ProductAggregate\Events\" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace ESCQRS.Catalog.Core.ProductAggregate;

public class ProductItem
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Kanadeiar.Common.Functionals;

namespace ESCQRS.Catalog.Core.ProductAggregate.Values;

public record PriceValue(decimal Value)
{
public decimal Value { get; } = Value.Require(Value is >= 0M and <= 100000M, () =>
throw new ApplicationException("Цена товара должна быть от 0 до 100000 руб."));

public override string ToString() => $"{Value}";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using ESCQRS.Catalog.Core.Base.Abstractions;
using Kanadeiar.Common.Functionals;

namespace ESCQRS.Catalog.Core.ProductAggregate.Values;

public record ProductId(Guid Value) : IId
{
public static ProductId New() => new(Guid.NewGuid());

public Guid Value { get; } = Value.Require(Value != Guid.Empty, () =>
throw new ApplicationException("Номер идентификатора должен быть назначен"));

public override string ToString() => Value.ToString();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Kanadeiar.Common.Functionals;

namespace ESCQRS.Catalog.Core.ProductAggregate.Values;

public record ProductNameValue(string Value)
{
public string Value { get; } = Value.Require(Value.Length is >= 3 and <= 300, () =>
throw new ApplicationException("Название категории товаров должно быть длинной от 3 до 300 символов"));

public override string ToString() => $"{Value}";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using ESCQRS.Catalog.Core.BrandAggregate.Events;
using ESCQRS.Catalog.Core.BrandAggregate.Values;

namespace ESCQRS.Catalog.Core.ReadModel;

public record BrandProjection(BrandId Id, BrandNameValue Name, bool IsDeleted = false)
{
public BrandProjection(BrandCreated ev) : this(ev.Id, ev.Name) { }

public BrandProjection Apply(BrandRenamed ev) =>
this with { Name = ev.Name };

public BrandProjection Apply(BrandDeleted ev) =>
this with { IsDeleted = true };

public override string ToString() => $"{Name}";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace ESCQRS.Catalog.Core.ReadModel;

public record CategoryProjection
{
}
Loading