Как стать автором
Обновить

Комментарии 21

Было куда интереснее, если бы автор привел небольшое описание зачем вообще все это нужно. И для каких целей. Для тех людей, которые не в курсе терминов.

В начале был какой то пример про получение книжки. Потом пошел абстрактный код без реальных примеров и всё стало сложно и непонятно.

Всё просто, на самом деле. Смотрите здесь https://github.com/jbogard/MediatR/wiki/Behaviors


Очень удобная штука. Вынесли валидацию и транзакции


DataAnnotation валидация
    public class DataAnnotationsValidationPreProcessor<TRequest> : IRequestPreProcessor<TRequest>
    {
        public Task Process([NotNull] TRequest request, CancellationToken cancellationToken)
        {
            var context = new ValidationContext(request);
            var results = new List<ValidationResult>();

            if (Validator.TryValidateObject(request, context, results))
            {
                return Task.CompletedTask;
            }

            var errors = results.Select(x => new ValidationFailure(x.MemberNames.First(), x.ErrorMessage));
            throw new ValidationException(errors);
        }
    }
К сожалению, ни ваш код, ни описание поведения на вики — не понятны. К чему и куда оно надо?

Где код до и после то?

Ну, в общем-то, в статье. Под заголовками "Код без cross-cutting concern" и "Код с cross-cutting concern"

Так а к чему всё остальное? Где декораторы то? Как стало с декораторами?
Автор вначале приводит пример декоратора и как он регистрируется. После описывает проблему дублирования кода валидации в таких декораторах для Query/Command Handler-ов(хотим одинаково валидировать но придется дублировать и писать несколько декораторов, а не 1). Собственно предлагает обобщить ICommandHandler/QueryHandler до IRequestHandler(точнее унаследовать) и как продолжение идеи до IUseCaseHandler<T1,T2>. Далее мы просто так же как в начале регистрируем декоратор на этот generic интерфейс и все по идее должно заработать, без всякого дублирования. Но вот для сервисов чтобы все работало придется работать явно с IUseCaseHadnler, вместо простого IAppService интерфейса.
Ну т.е. просто делаем очень общий интферфейс и на него вешаем 1 декоратор.
Примеров декораторов статье больше нет, видимо подразумевается что он будет аналогичен начальному.
Я понял так

Спасибо большое за комментарий. Вы поняли все верно. Я теперь знаю, что донёс идею внятно:)

Декораторы это хорошо, но как быть, если, например, в примере обычного кода логировать нужно внутри веток получения из базы или кеша, чтобы различать их? Инжектировать в метод декорированные логером методы получения из базы и кеша?

Добавьте дополнительное логирование в необходимых ветках. Можно явно с помощью императивного кода или с помощью декораторов для декораторов. Это как удобнее. Цель статьи — показать как можно очистить доменную логику от инфраструктурного кода. Взаимодействие инфраструктуры с инфраструктурой — другой вопрос.

Пример "инфраструктуры с инфраструктурой" просто из вашего кода. Пускай надо логировать именно отдельные ветки доменной логики. Причём не ошибки, которые клиенту доменной логики надо как-то обрабатывать, а просто debug/info уровень, о том куда мы пошли, какая ветка бизнес-правила сработала. Приходит в голову возвращать Result или его наследника типа LoggableResult, в котором есть итерируемое поле типа logMessages, из которого декоратор, если он применён, может доставать сообщения и логировать их. Нет декоратора — они просто теряются или добавляются к LoggableResult самого клиента — может клиент клиента решит их залогировать.


Не пробовали такой подход? Из очевидных минусов — потенциально логируемые куски логики, юзкейсы из примеров, всегда должны возвращать Result, а не быть void или какого-то другого типа. Тогда добавить логирование можно будет с минимальным изменением кода, вплоть только в DI-контейнере декоратор добавить. Но логика клиента должна будет всегда учитывать, что ожидаемый результат упакован.

Как мне кажется, декораторы нужны для однотипного кода. Если логирование перестало быть однотипным и усложнилось — надо выкинуть декоратор логирования из цепочки и вести логи по месту.

Очень не хочется в уровень доменной логики вводить логгеры, пускай даже какой-то NullLogger по умолчанию. Лучше уж полноценную систему доменных событий внедрять сразу, пускай по началу только логгеры на неё подписываться и будут или вообще в пустоту события эмитировать.

Думаю, доменные события для такого логирования подойдут лучше декораторов.
marshinov, подскажите, пожалуйста, как у вас в итоге хэндлеры верхнего уровня дергают хэндлеры AppService? Связь идет через Mediatr по типу команды?
Аналогичный вопрос для контроллера, как он запускает пайплайн?

Начал копать эту тему, счас движусь от «быстрорастворимого» в ширину, так что извиняюсь за элементарные вопросы.
public IActionResult Method(
    [FromServices] IHander<CommandOrQuery,...> handler,
    CommandOrQuery obj) 
        => handler
           .Handle(obj)
           .PipeTo(Ok)
Спасибо.
marshinov, еще вопрос, насчет AppService-ов. В этой статье предлагается превращать его в композитный обработчик всех кейсов, которые покрываются публичными методами. Но Богард и остальные отцы-основатели не советуют вызывать хэндлеры из хэндлеров, используя их лишь на самом верхнем уровне архитектуры. Но как быть, когда аппсервис для своей задачи вызывает еще несколько компонентов, которым для работы нужны еще компоненты. Превращать все это в команды-обработчики? Или же оставлять как есть, но тогда мы получаем слабую связанность за счет, например, МедиатРа только на уровнях контроллер и контроллер-1 (где и так логика довольно проста и прямолинейна), а хотелось бы разрубить связи между компонентами, которые делают основную работу.
Нужен способ разделять холистические абстракции и потоврно-используемые. Это может быть, например, так:
ICommandHanler<T1,T2>: IHandler<T1,T2> where T1: ICommand // холистическая
IHandler<T1,T2> // без констрейнтов - обычная

Или не использовать внутри IHandler
другие IHandler.
marshinov, cорри, промахнулся ответом. То есть, ниже уровня контроллера имеем чтото вроде (суть задачи синтетическая, главное иерархия вызовов):

public class CreateUserHandler
    : ICommandHanler<CreateUserRequest, CreateUserResponse>
{
    CreateUserResponse Handle(CreateUserRequest request)
    {
        var hash = _mediator.Send(new HashPasswordRequest(request.Password));

       // save user logic
      
       _mediator.Publish(new UserCreatedRequest(user.Email));
    };
}

public class SecurityService
    : ISecurityService
    : IUseCaseHandler<HashPasswordRequest, HashPasswordResponse>
{
    public HashPasswordResponse HashPassword(HashPasswordRequest request) 
    {
        //...
    }
    
    HashPasswordResponse IUseCaseHandler<HashPasswordRequest, HashPasswordResponse>.Handle(HashPasswordRequest request)
        => HashPassword(request);
}

public class EmailService
    : IEmailService
    : IUseCaseHandler<UserCreatedEvent>
{
    public void SendUserCreatedEmail(UserCreatedEvent event) 
    {
        //...
    }
    
    IUseCaseHandler<UserCreatedRequest>.Handle(UserCreatedEvent event)
        => SendUserCreatedEmail(event);
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории