Tisa.Domain
2025.9.10.1120
dotnet add package Tisa.Domain --version 2025.9.10.1120
NuGet\Install-Package Tisa.Domain -Version 2025.9.10.1120
<PackageReference Include="Tisa.Domain" Version="2025.9.10.1120" />
<PackageVersion Include="Tisa.Domain" Version="2025.9.10.1120" />
<PackageReference Include="Tisa.Domain" />
paket add Tisa.Domain --version 2025.9.10.1120
#r "nuget: Tisa.Domain, 2025.9.10.1120"
#:package Tisa.Domain@2025.9.10.1120
#addin nuget:?package=Tisa.Domain&version=2025.9.10.1120
#tool nuget:?package=Tisa.Domain&version=2025.9.10.1120
Tisa.Domain
Библиотека базовых компонентов доменной модели для .NET приложений, разработанная компанией ТИСА. Предоставляет фундаментальные абстракции и примитивы для построения доменной модели в соответствии с принципами Domain-Driven Design (DDD).
Возможности
- Поддержка .NET 8.0, .NET 9.0 и .NET 10.0
- Базовые классы для DDD:
AggregateRoot,XEntity - Система доменных событий (Domain Events)
- Коллекции сущностей с типизированным доступом
- Ссылки на сущности (
EntityLink) - Интерфейсы для аудита и валидации
- Интеграция с FluentValidation
- Доменные исключения и ошибки
- Интеграция с Tisa.Common
Установка
dotnet add package Tisa.Domain
Основные компоненты
XEntity - Базовая сущность
Базовый класс для всех доменных сущностей с идентификатором типа Guid и поддержкой доменных событий.
Определение сущности
using Tisa.Domain.Primitives;
public class User : XEntity
{
public string Name { get; private set; }
public string Email { get; private set; }
// Защищенный конструктор для EF Core
protected User() { }
public User(Guid id, string name, string email)
: base(id)
{
Name = name;
Email = email;
// Регистрация доменного события
RegisterEvent(new UserCreatedEvent(id, name, email));
}
public void UpdateEmail(string newEmail)
{
Email = newEmail;
RegisterEvent(new UserEmailUpdatedEvent(Id, newEmail));
}
}
Работа с доменными событиями
// Получение всех событий сущности
var events = user.DomainEvents;
// Очистка событий (обычно после сохранения)
user.ClearEvents();
AggregateRoot - Агрегирующий корень
Базовый класс для агрегирующих корней в DDD. Все агрегаты должны наследоваться от этого класса.
Определение агрегата
using Tisa.Domain.Primitives;
public class Order : AggregateRoot
{
private readonly EntityList<OrderItem> _items = new();
public string OrderNumber { get; private set; }
public DateTime CreatedAt { get; private set; }
public IReadOnlyCollection<OrderItem> Items => _items.Items;
protected Order() { }
public Order(Guid id, string orderNumber)
: base(id)
{
OrderNumber = orderNumber;
CreatedAt = DateTime.UtcNow;
RegisterEvent(new OrderCreatedEvent(id, orderNumber));
}
public void AddItem(OrderItem item)
{
_items.Add(item);
RegisterEvent(new OrderItemAddedEvent(Id, item.Id));
}
public void RemoveItem(Guid itemId)
{
_items.Delete(itemId);
RegisterEvent(new OrderItemRemovedEvent(Id, itemId));
}
}
EntityList - Коллекция сущностей
Типизированная коллекция для работы с коллекциями сущностей внутри агрегатов.
Использование EntityList
using Tisa.Domain.Primitives;
using Tisa.Common.Primitives;
public class Order : AggregateRoot
{
private readonly EntityList<OrderItem> _items = new();
public IReadOnlyCollection<OrderItem> Items => _items.Items;
public void AddItem(OrderItem item)
{
// Проверка существования
var existingItem = _items.Get(item.Id);
if (existingItem != null)
{
throw new DomainException("Товар уже добавлен в заказ");
}
_items.Add(item);
}
public void RemoveItem(Guid itemId)
{
// Использование Maybe для безопасного получения
var item = _items.Maybe(itemId);
item.Match(
onValue: _ => _items.Delete(itemId),
onNone: () => throw new NotFoundException("Товар не найден в заказе")
);
}
public OrderItem? GetItem(Guid itemId)
{
return _items.Get(itemId);
}
public bool HasItems()
{
return !_items.IsEmpty;
}
}
EntityLink - Ссылка на сущность
Класс для представления ссылок на другие сущности без полной загрузки.
Использование EntityLink
using Tisa.Domain.Primitives;
public class OrderItem : XEntity
{
public EntityLink Product { get; private set; }
public int Quantity { get; private set; }
public OrderItem(Guid id, Guid productId, string productName, int quantity)
: base(id)
{
Product = new EntityLink("Product", productId, productName);
Quantity = quantity;
}
}
// Использование
var orderItem = new OrderItem(
Guid.NewGuid(),
productId,
"Product Name",
10
);
// Доступ к ссылке
var productId = orderItem.Product.Id;
var productType = orderItem.Product.Type;
var productTitle = orderItem.Product.Title;
Domain Events - Доменные события
Система доменных событий для реализации паттерна Domain Events в DDD.
Определение доменного события
using Tisa.Domain.Messaging;
public class UserCreatedEvent : DomainEvent
{
public Guid UserId { get; }
public string UserName { get; }
public string UserEmail { get; }
public UserCreatedEvent(Guid userId, string userName, string userEmail)
{
UserId = userId;
UserName = userName;
UserEmail = userEmail;
}
}
Обработчик доменного события
using Tisa.Domain.Messaging;
public class UserCreatedEventHandler : IDomainEventHandler<UserCreatedEvent>
{
private readonly IEmailService _emailService;
public UserCreatedEventHandler(IEmailService emailService)
{
_emailService = emailService;
}
public async Task Handle(UserCreatedEvent domainEvent, CancellationToken cancellationToken = default)
{
// Отправка приветственного email
await _emailService.SendWelcomeEmailAsync(
domainEvent.UserEmail,
domainEvent.UserName,
cancellationToken
);
}
}
Регистрация обработчиков событий
using Microsoft.Extensions.DependencyInjection;
using Tisa.Domain.Messaging;
public void ConfigureServices(IServiceCollection services)
{
// Регистрация обработчиков событий
services.AddScoped<IDomainEventHandler<UserCreatedEvent>, UserCreatedEventHandler>();
services.AddScoped<IDomainEventHandler<OrderCreatedEvent>, OrderCreatedEventHandler>();
}
Публикация событий
public class UnitOfWork : IUnitOfWork
{
private readonly IServiceProvider _serviceProvider;
public async Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
// Сохранение изменений в БД
await _dbContext.SaveChangesAsync(cancellationToken);
// Публикация доменных событий
var entities = _dbContext.ChangeTracker
.Entries<XEntity>()
.Select(e => e.Entity)
.ToList();
foreach (var entity in entities)
{
foreach (var domainEvent in entity.DomainEvents)
{
await PublishEventAsync(domainEvent, cancellationToken);
}
entity.ClearEvents();
}
}
private async Task PublishEventAsync(IDomainEvent domainEvent, CancellationToken cancellationToken)
{
var handlerType = typeof(IDomainEventHandler<>).MakeGenericType(domainEvent.GetType());
var handlers = _serviceProvider.GetServices(handlerType);
foreach (var handler in handlers)
{
var method = handlerType.GetMethod(nameof(IDomainEventHandler<IDomainEvent>.Handle));
await (Task)method.Invoke(handler, new object[] { domainEvent, cancellationToken });
}
}
}
IAuditableEntity - Аудит сущностей
Интерфейс для отслеживания дат создания и изменения сущностей.
Реализация аудита
using Tisa.Domain.Abstractions;
public class User : XEntity, IAuditableEntity
{
public DateTimeOffset? CreatedOn { get; private set; }
public DateTimeOffset? ModifiedOn { get; private set; }
public User(Guid id, string name, string email)
: base(id)
{
Name = name;
Email = email;
CreatedOn = DateTimeOffset.UtcNow;
}
public void UpdateEmail(string newEmail)
{
Email = newEmail;
ModifiedOn = DateTimeOffset.UtcNow;
}
}
IValidatableObject - Валидация объектов
Интерфейс для интеграции с FluentValidation.
Использование валидации
using Tisa.Domain.Abstractions;
using FluentValidation;
public class CreateUserCommand : IValidatableObject
{
public string Name { get; set; }
public string Email { get; set; }
private ValidationException _validationException;
public void SetValidationError(ValidationException validationException)
{
_validationException = validationException;
}
}
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Имя обязательно")
.MaximumLength(100).WithMessage("Имя не должно превышать 100 символов");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email обязателен")
.EmailAddress().WithMessage("Неверный формат email");
}
}
Исключения домена
Библиотека предоставляет специализированные исключения для доменной логики.
DomainException
using Tisa.Domain.Exceptions;
using Tisa.Common.Errors;
public class UserService
{
public void ActivateUser(User user)
{
if (user.IsActive)
{
throw new DomainException(
Error.Conflict("User.AlreadyActive", "Пользователь уже активирован")
);
}
user.Activate();
}
}
NotFoundException
using Tisa.Domain.Exceptions;
public class UserRepository
{
public User GetById(Guid id)
{
var user = _dbContext.Users.Find(id);
if (user == null)
{
throw new NotFoundException($"Пользователь с ID {id} не найден");
}
return user;
}
}
ValidateException
using Tisa.Domain.Exceptions;
using FluentValidation;
public class UserService
{
private readonly IValidator<CreateUserCommand> _validator;
public void CreateUser(CreateUserCommand command)
{
var validationResult = _validator.Validate(command);
if (!validationResult.IsValid)
{
throw new ValidateException(validationResult.Errors);
}
// Создание пользователя
}
}
DomainErrors - Статические ошибки домена
Централизованное хранение ошибок домена.
Использование
using Tisa.Domain.Errors;
public static partial class DomainErrors
{
public static Error UserNotFound(Guid userId) =>
Error.NotFound("User.NotFound", $"Пользователь с ID {userId} не найден");
public static Error UserEmailAlreadyExists(string email) =>
Error.Conflict("User.EmailExists", $"Email {email} уже используется");
public static Error InvalidUserStatus =>
Error.BadRequest("User.InvalidStatus", "Неверный статус пользователя");
}
// Использование
throw new DomainException(DomainErrors.UserNotFound(userId));
Структура проекта
Abstractions/- Базовые интерфейсыIAuditableEntity- Интерфейс для аудита сущностейIValidatableObject- Интерфейс для валидации объектов
Primitives/- Базовые примитивы доменаAggregateRoot- Агрегирующий кореньXEntity- Базовая сущность с GuidEntityLink- Ссылка на сущностьEntityList<T>- Типизированная коллекция сущностей
Messaging/- Система доменных событийDomainEvent- Базовый класс доменного событияIDomainEvent- Интерфейс доменного событияIDomainEventHandler- Интерфейс обработчика событий
Errors/- Ошибки доменаDomainErrors- Статические ошибки домена
Exceptions/- Исключения доменаDomainException- Базовое исключение доменаNotFoundException- Исключение для не найденных объектовValidateException- Исключение валидации
Примеры использования
Полный пример агрегата
using Tisa.Domain.Primitives;
using Tisa.Domain.Messaging;
using Tisa.Domain.Abstractions;
public class Order : AggregateRoot, IAuditableEntity
{
private readonly EntityList<OrderItem> _items = new();
public string OrderNumber { get; private set; }
public OrderStatus Status { get; private set; }
public DateTimeOffset? CreatedOn { get; private set; }
public DateTimeOffset? ModifiedOn { get; private set; }
public IReadOnlyCollection<OrderItem> Items => _items.Items;
protected Order() { }
public Order(Guid id, string orderNumber)
: base(id)
{
OrderNumber = orderNumber;
Status = OrderStatus.Draft;
CreatedOn = DateTimeOffset.UtcNow;
RegisterEvent(new OrderCreatedEvent(id, orderNumber));
}
public void AddItem(Guid productId, string productName, int quantity, decimal price)
{
if (Status != OrderStatus.Draft)
{
throw new DomainException(
Error.BadRequest("Order.InvalidStatus", "Нельзя добавлять товары в заказ не в статусе Draft")
);
}
var item = new OrderItem(Guid.NewGuid(), productId, productName, quantity, price);
_items.Add(item);
ModifiedOn = DateTimeOffset.UtcNow;
RegisterEvent(new OrderItemAddedEvent(Id, item.Id));
}
public void Confirm()
{
if (Status != OrderStatus.Draft)
{
throw new DomainException(
Error.BadRequest("Order.InvalidStatus", "Можно подтверждать только заказы в статусе Draft")
);
}
if (_items.IsEmpty)
{
throw new DomainException(
Error.BadRequest("Order.Empty", "Нельзя подтвердить пустой заказ")
);
}
Status = OrderStatus.Confirmed;
ModifiedOn = DateTimeOffset.UtcNow;
RegisterEvent(new OrderConfirmedEvent(Id));
}
}
Требования
- .NET 8.0, .NET 9.0 или .NET 10.0
- Tisa.Common
- FluentValidation 12.1.0
Интеграция с Entity Framework Core
public class ApplicationDbContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Настройка XEntity
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(e => e.Id);
entity.Ignore(e => e.DomainEvents);
});
// Настройка Value Objects через OwnsOne
// ...
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
// Сохранение изменений
var result = await base.SaveChangesAsync(cancellationToken);
// Публикация доменных событий
await PublishDomainEventsAsync(cancellationToken);
return result;
}
private async Task PublishDomainEventsAsync(CancellationToken cancellationToken)
{
var entities = ChangeTracker
.Entries<XEntity>()
.Select(e => e.Entity)
.Where(e => e.DomainEvents.Any())
.ToList();
foreach (var entity in entities)
{
foreach (var domainEvent in entity.DomainEvents)
{
// Публикация события через медиатор или сервис
await _eventPublisher.PublishAsync(domainEvent, cancellationToken);
}
entity.ClearEvents();
}
}
}
Авторы
Команда разработчиков TISA
Лицензия
MIT License
Поддержка
Для получения поддержки или сообщения об ошибках, пожалуйста, напишите нам на support@tisn.ru
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FluentValidation (>= 12.1.0)
- Lindhart.Analyser.MissingAwaitWarning (>= 2.0.0)
- Tisa.Common (>= 2025.9.10.1120)
-
net8.0
- FluentValidation (>= 12.1.0)
- Lindhart.Analyser.MissingAwaitWarning (>= 2.0.0)
- Tisa.Common (>= 2025.9.10.1120)
-
net9.0
- FluentValidation (>= 12.1.0)
- Lindhart.Analyser.MissingAwaitWarning (>= 2.0.0)
- Tisa.Common (>= 2025.9.10.1120)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 2025.9.10.1120 | 343 | 11/17/2025 | |
| 2025.9.9.1111 | 279 | 11/12/2025 | |
| 2025.9.9.1105 | 201 | 10/30/2025 | |
| 2025.9.9.1101 | 290 | 10/28/2025 | |
| 2025.9.9.410 | 232 | 4/9/2025 | |
| 2025.9.9.400 | 312 | 4/8/2025 |