WebFlow.Abstractions
2.1.0
dotnet add package WebFlow.Abstractions --version 2.1.0
NuGet\Install-Package WebFlow.Abstractions -Version 2.1.0
<PackageReference Include="WebFlow.Abstractions" Version="2.1.0" />
<PackageVersion Include="WebFlow.Abstractions" Version="2.1.0" />
<PackageReference Include="WebFlow.Abstractions" />
paket add WebFlow.Abstractions --version 2.1.0
#r "nuget: WebFlow.Abstractions, 2.1.0"
#:package WebFlow.Abstractions@2.1.0
#addin nuget:?package=WebFlow.Abstractions&version=2.1.0
#tool nuget:?package=WebFlow.Abstractions&version=2.1.0
WebFlow
WebFlow — набор небольших библиотек для .NET 10-приложений с разделением на Domain, Application, Infrastructure и Presentation. Он задаёт минимальные контракты команд, запросов и событий, а также предоставляет интеграцию с ErrorsFlow, FluentValidation и ASP.NET Core.
Библиотека не навязывает ORM, брокер сообщений, DI-контейнер, CQRS-фреймворк или архитектуру модулей. Она содержит только общие контракты и адаптеры на границе HTTP.
Цели
- Явные контракты команд, запросов и их обработчиков.
- Единый
Result-подход на основеCSharpFunctionalExtensionsиErrorsFlow. - Стандартизированные HTTP-ответы без повторения маппинга ошибок в контроллерах.
- Разделение доменных и интеграционных событий.
- Никаких зависимостей от EF Core, Npgsql, RabbitMQ или ASP.NET Core в базовом пакете.
Пакеты
| Пакет | Назначение | Зависимости |
|---|---|---|
WebFlow.Abstractions |
Команды, запросы, обработчики, события, Unit of Work и транзакции. | CSharpFunctionalExtensions, ErrorsFlow |
WebFlow.AspNetCore |
HTTP-envelope, базовый контроллер и маппинг ErrorsFlow в HTTP-ответы. |
ErrorsFlow, ASP.NET Core |
WebFlow.FluentValidation |
Преобразование ошибок FluentValidation в ErrorList. |
ErrorsFlow, FluentValidation |
Установка
Подключайте только те пакеты, которые требуются конкретному слою.
<PackageReference Include="WebFlow.Abstractions" Version="2.0.0" />
<PackageReference Include="WebFlow.AspNetCore" Version="2.0.0" />
<PackageReference Include="WebFlow.FluentValidation" Version="2.0.0" />
Все пакеты рассчитаны на .NET 10. Для проектов на .NET 8 остаётся предыдущий монолитный пакет WebApplicationFlow версии 1.x.
Рекомендуемое размещение зависимостей
Domain → не зависит от WebFlow
Application → WebFlow.Abstractions, WebFlow.FluentValidation
Infrastructure → Application, конкретные реализации БД и транспорта
Presentation → Application, WebFlow.AspNetCore
Messaging → WebFlow.Abstractions (только для IIntegrationEvent, если нужен общий контракт)
WebFlow.AspNetCore не должен использоваться в Domain или Application. Домен также не должен зависеть от ASP.NET Core, EF Core и RabbitMQ.
Команды и запросы
Команда меняет состояние приложения, запрос читает его.
using WebFlow.Abstractions.Interfaces;
public sealed record RegisterClientCommand(
string UserName,
string Email,
string Password) : ICommand;
public sealed record GetUserQuery(Guid UserId) : IQuery;
Обработчик команды с полезным результатом возвращает Result<TResponse, ErrorList>:
using CSharpFunctionalExtensions;
using ErrorsFlow.Models;
using WebFlow.Abstractions.Interfaces;
public sealed class RegisterClientHandler
: ICommandHandler<RegisterClientCommand, Guid>
{
public async Task<Result<Guid, ErrorList>> Handle(
RegisterClientCommand command,
CancellationToken cancellationToken = default)
{
// Валидация, создание агрегата и сохранение.
return await Task.FromResult(Result.Success<Guid, ErrorList>(Guid.NewGuid()));
}
}
Для команды без полезного результата используйте ICommandHandler<TCommand> с UnitResult<ErrorList>. Запросы реализуют IQueryHandler<TQuery, TResponse> и также возвращают Result.
Ошибки и Result
WebFlow использует ErrorList из ErrorsFlow как единый тип ошибок обработчиков. Обработчик возвращает ожидаемые ошибки через Result, а не выбрасывает исключения.
if (user is null)
return GeneralErrors.NotFound("User", nameof(command.UserId)).ToErrorList();
Исключения оставляйте для непредвиденных технических сбоев. Их обработку и журналирование следует выполнять централизованно в Presentation или middleware приложения.
FluentValidation
WebFlow.FluentValidation добавляет ToErrorList() для неуспешного ValidationResult. У каждой ошибки сохраняются код, сообщение и имя поля из ValidationFailure.
var validationResult = await validator.ValidateAsync(command, cancellationToken);
if (!validationResult.IsValid)
return validationResult.ToErrorList();
Вызывайте ToErrorList() только при IsValid == false: при успешной валидации метод намеренно выбрасывает ArgumentException, поскольку ErrorList не может быть пустым.
ASP.NET Core
ApplicationController даёт готовые методы для успешных и ошибочных ответов. Контроллер самостоятельно выбирает семантически верный успешный статус: например, 201 Created для создания и 200 OK для чтения или изменения.
using Microsoft.AspNetCore.Mvc;
using WebFlow.AspNetCore.Controllers;
[Route("api/auth")]
public sealed class AuthController : ApplicationController
{
[HttpPost("register/client")]
public async Task<IActionResult> RegisterClient(
RegisterClientRequest request,
RegisterClientHandler handler,
CancellationToken cancellationToken)
{
var result = await handler.Handle(request.ToCommand(), cancellationToken);
if (result.IsFailure)
return Error(result.Error);
return CreatedEnvelope(result.Value);
}
}
Успешный ответ имеет форму:
{
"data": "полезный результат",
"errors": null
}
Ошибочный ответ:
{
"data": null,
"errors": [
{
"code": "value.is.invalid",
"message": "The value is invalid.",
"type": "Validation",
"target": "Email"
}
]
}
Соответствие ошибок HTTP-статусам
ErrorType |
HTTP-статус |
|---|---|
Validation |
400 Bad Request |
Unauthorized |
401 Unauthorized |
Forbidden |
403 Forbidden |
NotFound |
404 Not Found |
Conflict |
409 Conflict |
Failure, InternalServer |
500 Internal Server Error |
Если один ErrorList содержит ошибки разных типов, возвращается 500. Такой набор не имеет одного корректного HTTP-статуса; обработчик должен группировать ошибки по одному смысловому типу.
События
WebFlow различает два вида событий:
public interface IDomainEvent
{
Guid EventId { get; }
DateTimeOffset OccurredAt { get; }
}
public interface IIntegrationEvent
{
Guid EventId { get; }
DateTimeOffset OccurredAt { get; }
}
Интерфейсы намеренно имеют одинаковые базовые поля, но являются разными типами: это не позволяет случайно передать внутреннее доменное событие в publisher внешних сообщений.
IDomainEvent— внутренний факт доменной модели; он не является публичным контрактом и не отправляется в RabbitMQ напрямую.IIntegrationEvent— стабильный сериализуемый контракт для других модулей или внешних систем.
Пример:
public sealed record UserRegisteredDomainEvent(
Guid EventId,
DateTimeOffset OccurredAt,
User User) : IDomainEvent;
public sealed record UserRegisteredIntegrationEvent(
Guid EventId,
DateTimeOffset OccurredAt,
Guid UserId,
string Email) : IIntegrationEvent;
WebFlow не публикует сообщения и не зависит от RabbitMQ. Преобразование доменных событий в интеграционные, Outbox и отправка в брокер — ответственность Application и Infrastructure конкретного приложения.
Unit of Work и транзакции
IUnitOfWork определяет сохранение изменений и создание явной транзакции без зависимости от EF Core:
public interface IUnitOfWork
{
Task<ITransaction> BeginTransactionAsync(CancellationToken cancellationToken = default);
Task<ITransaction> BeginTransactionAsync(
IsolationLevel isolationLevel,
CancellationToken cancellationToken = default);
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}
Обычная команда
Если все изменения выполняются через один DbContext, одного SaveChangesAsync достаточно. EF Core сохранит отслеживаемые изменения одной транзакцией базы данных.
await users.AddAsync(user, cancellationToken);
await outbox.AddAsync(integrationEvent, cancellationToken);
await unitOfWork.SaveChangesAsync(cancellationToken);
Так в одной транзакции БД сохраняются пользователь и Outbox-сообщение. Публиковать RabbitMQ-сообщение внутри этой транзакции нельзя: для этого Outbox обрабатывается отдельным worker-ом после commit.
Явная транзакция
Используйте её только если нужно объединить несколько сохранений или SQL-операций одной базы данных.
using System.Data;
await using var transaction = await unitOfWork.BeginTransactionAsync(
IsolationLevel.Serializable,
cancellationToken);
try
{
// Несколько операций с одной БД и одним DbContext.
await unitOfWork.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
Без параметра используется уровень изоляции базы данных по умолчанию. Не выбирайте Serializable «на всякий случай»: он повышает вероятность блокировок и конфликтов. Реализация ITransaction обязана откатить незавершённую транзакцию при DisposeAsync.
Что намеренно не входит в WebFlow
- EF Core, Npgsql и конкретные реализации репозиториев;
- RabbitMQ, Outbox и dispatcher событий;
- аутентификация, авторизация и HTTP-клиенты;
- value objects, сущности и бизнес-правила конкретных модулей;
- автоматическое выполнение всех команд в транзакции.
Эти решения зависят от конкретного приложения и должны находиться в его Domain, Application или Infrastructure.
Совместимость и миграция
WebFlow 2.0 — breaking change относительно WebApplicationFlow 1.x:
- целевая платформа — .NET 10;
- старый монолитный пакет разделён на три узких пакета;
- удалены зависимости от EF Core, Npgsql и банковской предметной логики;
ErrorsFlowобновлён до 2.x;- контроллеры сами выбирают успешный HTTP-статус, а не получают всегда
200 OK.
Новые приложения используйте с WebFlow 2.0. Существующие проекты на 1.x мигрируйте отдельной задачей после проверки всех публичных контрактов.
Разработка
dotnet restore WebFlow.sln --source https://api.nuget.org/v3/index.json
dotnet test WebFlow.sln --configuration Release
dotnet pack WebFlow.sln --configuration Release
Перед выпуском новой версии выполняйте тесты, проверяйте изменения публичного API и публикуйте все три пакета с согласованной версией.
Выпуск NuGet-пакетов
Публикация выполняется workflow-ом .github/workflows/main.yml после push тега формата vX.Y.Z. Версия пакетов берётся из тега без префикса v.
Перед первым выпуском необходимо:
- Создать на nuget.org Trusted Publishing policy для репозитория
Ionefor/WebFlowи workflow-файлаmain.yml. - Создать repository variable GitHub
NUGET_USERсо значением имени профиля nuget.org, а не email. - Убедиться, что политика разрешает публикацию всех трёх идентификаторов пакетов.
Постоянный NuGet API key в GitHub Secrets не нужен: workflow получает одноразовый ключ через OIDC непосредственно перед публикацией.
git tag v2.0.0
git push origin v2.0.0
Лицензия
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- CSharpFunctionalExtensions (>= 3.7.0)
- ErrorsFlow (>= 2.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.