Поддержка  •  Дневник  •  Без рекламы  •  О сайте  •  Реклама  •  Поставить баннер  •  Прислать  •  Хроника  •  Translate Гости: 15    Участники: 0 Авторизация Авторизация   Регистрация 
Метод Научного Тыка
RULVEN
Поиск  
Blackball iMag | интернет-журнал
Каталог


Начало » Разработка ПО » Зачем нужен MediatR?
Мне повезёт!

Зачем нужен MediatR?


Зачем нужен MediatR?
Опубликовано: 12 ноябрь 2021 г.
Добавлено: Пн 24.04.2023 • Sergeant
Автор: Иван Якимов
Источник: источник
Просмотров: 616
Комментарии: 0


MediatR

Недавно я натолкнулся в нашем коде на использование пакета MediatR. Это заинтересовало меня. Почему я должен использовать MediatR? Какие преимущества он мне предоставляет? Здесь я собираюсь рассмотреть эти вопросы.

Как пользоваться MediatR

На базовом уровне использование MediatR очень просто. Сначала вы устанавливаете NuGet пакет MediatR. В вашем приложении будет несколько описаний той работы, которую ему нужно выполнять (например, создать запись ToDo, изменить имя пользователя и т. д.). Эти описания в MediatR называются запросами (requests). Это обычные классы, реализующие интерфейс IRequest. Он является маркер-интерфейсом без всяких членов.

class CreateToDoItem : IRequest
{
    public string ToDoItemText { get; set; }
}

Эти классы могут не содержать никакой логики. Они представляют собой просто контейнеры данных, необходимых для выполнения операций.

Но что означает параметр T в интерфейсе IRequest? Видите ли, ваши операции могут возвращать некоторые результаты. Например, при создании записи ToDo вам может потребовать получить ID этой записи. Именно для этого и служит параметр T. В нашем случае, мы хотим получить целочисленный идентификатор записи ToDo.

Теперь нам нужен код, который, собственно, и будет выполнять нашу операцию. В MediatR этот код называется обработчиком запроса (request handler). Обработчики запроса должны реализовывать интерфейс IRequestHandler, где TRequest должен быть IRequest:

class CreateToDoItemHandler : IRequestHandler
{
    public Task Handle(CreateToDoItem request, CancellationToken cancellationToken)
    {
        ...
    }
}

Как видите, этот интерфейс требует реализовать единственный метод Handle, который асинхронно исполняет требуемую операцию и возвращает нужный результат.

Всё, что осталось нам сделать, это соединить запрос с соответствующим обработчиком. MediatR использует для этого контейнер зависимостей. Если вы разрабатываете приложение ASP.NET Core, вы можете воспользоваться пакетом MediatR.Extensions.Microsoft.DependencyInjection. Но MediatR поддерживает и массу других контейнеров.

services.AddMediatR(typeof(Startup));

Здесь services - экземпляр интерфейса IServiceCollection, который обычно доступен вам в методе ConfigureServices класса Startup. Данная команда сканирует сборку, в которой определён класс Startup, и находит там все обработчики запросов.

Теперь вы можете выполнять ваши запросы. Для этого вам потребуется получить экземпляр интерфейса IMediator. Он регистрируется в вашем контейнере зависимостей той же командой AddMediatR.

var toDoItemId = await mediator.Send(createToDoItemRequest);

Вот и всё. MediatR найдёт соответствующий обработчик запроса, выполнит его и вернёт вам результат.

И здесь мы переходим к главному вопросу.

Зачем мне нужен MediatR?

Давайте представим себе, что у нас есть ASP.NET Core контроллер, который поддерживает операции работы с записями ToDo. Мы сравним, как можно реализовать создание такой записи с использованием MediatR и без него. Вот как выглядит код без MediatR:

[ApiController]
public class ToDoController : ControllerBase
{
    private readonly IToDoService _service;

    public ToDoController(IToDoService service)
    {
        _service = service;
    }

    [HttpPost]
    public async Task CreateToDoItem([FromBody] CreateToDoItem createToDoItemRequest)
    {
        var toDoItemId = await _service.CreateToDoItem(createToDoItemRequest);

        return Ok(toDoItemId);
    }
}

А вот так выглядит реализация, использующая MediatR:

[ApiController]
public class ToDoController : ControllerBase
{
    private readonly IMediator _mediator;

    public ToDoController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task CreateToDoItem([FromBody] CreateToDoItem createToDoItemRequest)
    {
        var toDoItemId = await _mediator.Send(createToDoItemRequest);

        return Ok(toDoItemId);
    }
}

Вы видите здесь какие-нибудь серьёзные преимущества MediatR? Я - нет. На самом деле мне кажется, что версия с MediatR чуть менее читабельна. Она использует обобщённый метод Send вместо более осмысленного CreateToDoItem.

Так зачем же мне использовать MediatR?

Ссылки

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

Но лично я не вижу здесь большого преимущества. Да, вам будет удобнее выполнять некоторые изменения в вашем проекте. Но в то же время вы получите и ряд трудностей. Из кода вашего контроллера вы не можете видеть, кто в действительности выполняет ваш запрос. Чтобы найти обработчик для экземпляра CreateToDoItem, вы должны знать, что такое MediatR, и как он работает. Здесь нет ничего особенно сложного. В конце концов, IToDoService так же не является реализацией обработчика, вам приходится искать классы, реализующие данный интерфейс. Тем не менее у новичка уйдёт больше времени, чтобы понять, что здесь происходит.

Единственная ответственность

Следующее отличие более важно. Видите ли, ваш обработчик запросов - это класс. И весь этот класс ответственен за выполнение единственной операции. В случае же сервиса (например, IToDoService), за выполнение одной операции ответственен один метод. Это означает, что сервис может содержать множество различных методов, ответственных за разные операции. Всё это усложняет понимание кода сервиса. С другой стороны, весь класс обработчика запроса отвечает только за одну операцию. Это делает данный класс меньше и легче для понимания.

Всё это, конечно, здорово, но реальность несколько сложнее. Обычно вам нужно поддерживать несколько связанных операций (например, создать запись, обновить её, изменить статус записи, ...) Все эти операции могут требовать выполнения одинаковых кусков кода. В случае сервиса вы можете вынести этот код в приватный метод. Но обработчики запросов - отдельные классы. Конечно, мы можем использовать наследование и вынести всё, что нужно, в базовый класс. Но это возвращает нас к той же, если не худшей ситуации. В случае сервиса, у нас было множество методов внутри одного класса. Теперь у нас есть множество методов, раскиданных по нескольким классам. Не уверен, какой из вариантов лучше.

Другими словами, если вы собираетесь отстрелить себе ногу, у вас всё ещё есть масса вариантов, как сделать это.

Декораторы

Но существует ещё одно очень важное преимущество MediatR. Видите ли, все ваши обработчики запросов реализуют один и тот же интерфейс IRequestHandler. Это означает, что вы можете создавать декораторы, применимые ко всем ним. В ASP.NET Core вы можете использовать пакет Scrutor для поддержки декораторов. Например, вы можете написать декоратор для логирования так:

class LoggingDecorator : IRequestHandler
    where TRequest : IRequest
{
    private readonly IRequestHandler _handler;
    private readonly Logger _logger;

    public LoggingDecorator(IRequestHandler handler,
        Logger logger)
    {
        _handler = handler;
        _logger = logger;
    }

    public Task Handle(TRequest request, CancellationToken cancellationToken)
    {
        _logger.Log("Log something here.");

        return _handler.Handle(request, cancellationToken);
    }
}

Теперь зарегистрируйте его:

services.AddMediatR(typeof(Startup));
services.Decorate(typeof(IRequestHandler<,>), typeof(LoggingDecorator<,>));

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

Но зачем вообще связываться со Scrutor? MediatR предоставляет вам ту же функциональность с поведениями конвейера (pipeline behaviors). Создайте класс, реализующий IPipelineBehavior:

class LoggingBehavior : IPipelineBehavior
{
    private readonly Logger _logger;

    public LoggingBehavior(Logger logger)
    {
        _logger = logger;
    }

    public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next)
    {
        try
        {
            _logger.Log($"Before execution for {typeof(TRequest).Name}");

            return await next();
        }
        finally
        {
            _logger.Log($"After execution for {typeof(TRequest).Name}");
        }
    }
}

Зарегистрируйте его:

services.AddMediatR(typeof(Startup));
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));

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

Подход с поведениями даже лучше декораторов. Давайте посмотрим на следующий пример. Вам может требоваться обрабатывать некоторые запросы внутри транзакций. Чтобы пометить такие запросы, вы используете маркер-интерфейс ITransactional:

interface ITransactional { }

class CreateToDoItem : IRequest, ITransactional
    ...

Как применить поведение только к тем запросам, которые помечены ITransactional? Вы можете использовать ограничения на типы:

class TransactionalBehavior : IPipelineBehavior
    where TRequest : ITransactional
    ...

Проделать то же самое с декораторами Scrutor нельзя. Если вы реализуете декоратор вот так:

class TransactionalDecorator : IRequestHandler
    where TRequest : IRequest, ITransactional
    ...

вы не сможете использовать его, если у вас есть хотя бы один запрос, не реализующий ITransactional.

Создавая поведения конвейера, помните, что они выполняются при каждом вызове метода Send. Это может быть важно, если вы посылаете запросы изнутри обработчиков запросов:

class CommandHandler : IRequestHandler
{
    private readonly IMediator _mediator;

    public CommandHandler(IMediator mediator)
    {
        _mediator = mediator;
    }

    public async Task Handle(Command request, CancellationToken cancellationToken)
    {
        ...

        var result = await _mediator.Send(new AnotherCommand(), cancellationToken);

        ...
    }
}

Если и Command, и AnotherCommand помечены с помощью ITransactional, соответствующее поведение TransactionalBehavior будет выполнено дважды. Поэтому убедитесь, что вы не создаёте две отдельные транзакции.

Другая функциональность

MediatR предоставляет вам и другую функциональность. Так он поддерживает механизм уведомлений (notification). Он может оказаться очень полезным, если вы в вашей системе используете доменные события. Все классы событий должны реализовывать маркер-интерфейс INotification. И вы можете создать любое количество обработчиков для конкретного типа событий с помощью интерфейса INotificationHandler. Разница между запросами и уведомлениями следующая. Запрос передаётся только одному единственному обработчику. Уведомления передаются всем зарегистрированным обработчикам для данного типа уведомлений. Также от обработчика запроса вы можете получить некоторый результат его обработки. Уведомления не позволяют получить никакого результата. Используйте метод Publish для рассылки уведомлений.

MediatR также предоставляет механизм обработки исключений. Он довольно изощренный, и вы можете почитать о нём здесь.

Заключение

В завершение должен сказать, что для меня MediatR представляет собой очень интересный NuGet-пакет. Способность выражать любые операции через единый интерфейс и механизм поведений конвейера делают его очень привлекательным для моих проектов. Не могу сказать, что он представляет собой решение всех проблем, но определённые преимущества у него есть. Удачи вам в его использовании.



Мне нравится 0   Мне не нравится 0



Комментарии

Чтобы добавить видео с YouTube, нужно написать [@youtube=xxxxx] , где xxxxx – ID видео.


Комментарии: 0
Нет ни одного комментария.
RSS-лента
Поделиться ссылкой:
Обзор Samsung Galaxy Tab A 10.5 Обзор Samsung Galaxy Tab A 10.5
Запуск приложений Android на компьютере с Windows Запуск приложений Android на компьютере с Windows
5 ошибок при разработке высоконагруженных сервисов 5 ошибок при разработке высоконагруженных сервисов
11 способов быстро и вкусно засолить скумбрию 11 способов быстро и вкусно засолить скумбрию
Буженина из свинины в домашних условиях: 12 пошаговых рецептов Буженина из свинины в домашних условиях: 12 пошаговых рецептов
Тайна старого особняка Тайна старого особняка
HDMI или Display Port: в чëм разница, и чем лучше выводить изображение на монитор HDMI или Display Port: в чëм разница, и чем лучше выводить изображение на монитор
Соглашаться на... свинг? Соглашаться на... свинг?
15 потрясающих соусов для свиных рёбрышек 15 потрясающих соусов для свиных рёбрышек
Женское обрезание

csharp
Delegates in C#: A comprehensive guide Ср 27.03.2024
Delegates in C#: A comprehensive guide
C# и .NET: Blazor Пн 25.03.2024
C# и .NET: Blazor
Ср 20.03.2024
Creating functions that return multiple values in C#
Задача по языку C#: Игра «Крестики Нолики» в консоли Пн 26.02.2024
Задача по языку C#: Игра «Крестики Нолики» в консоли
Pagination in a .NET Web API with EF Core Ср 21.02.2024
Pagination in a .NET Web API with EF Core
Зачем нужен MediatR? Пн 24.04.2023
Зачем нужен MediatR?
C# 8.0 and .NET Core 3.0 – Modern Cross-Platform Development Build applications with C# NET Core, Entity Framework Чт 02.02.2023
C# 8.0 and .NET Core 3.0 – Modern Cross-Platform Development Build applications with C# NET Core, Entity Framework
Год: 2019
C# 6 and .NET Core 1.0 Modern Cross-Platform Development Чт 26.01.2023
C# 6 and .NET Core 1.0 Modern Cross-Platform Development
Год: 2016
Concurrency in C# Cookbook Чт 12.01.2023
Concurrency in C# Cookbook
Год: 2019
C# 7 and .NET Core: Modern Cross-Platform Development, 2nd Edition Вт 04.01.2022
C# 7 and .NET Core: Modern Cross-Platform Development, 2nd Edition
Год: 2017
Effective C# (Covers C# 6.0): 50 Specific Ways to Improve Your C#, 3rd Edition Вт 21.12.2021
Effective C# (Covers C# 6.0): 50 Specific Ways to Improve Your C#, 3rd Edition
Год: 2017
C# and XML Primer Чт 09.12.2021
C# and XML Primer
Год: 2017
More Effective C#: 50 Specific Ways to Improve Your C#, 2nd Edition Вт 16.11.2021
More Effective C#: 50 Specific Ways to Improve Your C#, 2nd Edition
Год: 2017
Разработка обслуживаемых программ на языке C# Чт 28.10.2021
Разработка обслуживаемых программ на языке C#
Год: 2017
C# 7 and .NET Core Cookbook Вт 05.10.2021
C# 7 and .NET Core Cookbook
Год: 2017
C# для профессионалов. Тонкости программирования, 3-е издание Вт 11.05.2021
C# для профессионалов. Тонкости программирования, 3-е издание
Год: 2014
C# 6.0 in a Nutshell, 6th Edition Чт 29.04.2021
C# 6.0 in a Nutshell, 6th Edition
Год: 2015
CLR via C#, 4th Edition Вт 30.03.2021
CLR via C#, 4th Edition
Год: 2012
.NET Domain-Driven Design with C# Вт 16.03.2021
.NET Domain-Driven Design with C#
Год: 2008
Pro LINQ: Language Integrated Query in C# 2010 Чт 25.02.2021
Pro LINQ: Language Integrated Query in C# 2010
Год: 2010
Pro LINQ: Language Integrated Query in C# 2008 Вт 23.02.2021
Pro LINQ: Language Integrated Query in C# 2008
Год: 2007
Software Architecture with C# 9 and .NET 5, Second Edition Сб 13.02.2021
Software Architecture with C# 9 and .NET 5, Second Edition
Год: 2020
C# 9 and .NET 5 Сб 13.02.2021
C# 9 and .NET 5
Год: 2020
Компиляция и запуск C# и Blazor внутри браузера Пн 07.12.2020
Компиляция и запуск C# и Blazor внутри браузера
C# 7.0. Карманный справочник Вт 24.11.2020
C# 7.0. Карманный справочник
Год: 2017
Professional C# 6 and .NET Core 1.0 Вт 17.11.2020
Professional C# 6 and .NET Core 1.0
Год: 2016
Пн 16.11.2020
Develop and Install a Windows Service in C#
C# 7.1 and .NET Core 2.0 – Modern Cross-Platform Development Вт 10.11.2020
C# 7.1 and .NET Core 2.0 – Modern Cross-Platform Development
Год: 2017
C# 8 Quick Syntax Reference Вт 03.11.2020
C# 8 Quick Syntax Reference
Год: 2020
Problem Solving in Data Structures and Algorithms Using C# Вт 27.10.2020
Problem Solving in Data Structures and Algorithms Using C#
Год: 2018
C# 7 и .NET Core. Кросс-платформенная разработка для профессионалов Вт 01.09.2020
C# 7 и .NET Core. Кросс-платформенная разработка для профессионалов
Год: 2018
Язык программирования C# 7 и платформы .NET и .NET Core Вт 25.08.2020
Язык программирования C# 7 и платформы .NET и .NET Core
Год: 2018
The C# Player’s Guide, 3rd Edition Вт 31.12.2019
The C# Player’s Guide, 3rd Edition
Год: 2016
Design Patterns in C# Пн 09.01.2017
Design Patterns in C#
Год: 2012
Язык программирования C# 5.0 и платформа .NET 4.5 Пн 02.01.2017
Язык программирования C# 5.0 и платформа .NET 4.5
Год: 2013
The C# Player's Guide, 2nd Edition Пн 12.12.2016
The C# Player's Guide, 2nd Edition
Год: 2015
C# Programming Yellow Book Пн 21.11.2016
C# Programming Yellow Book
Год: 2015
Pro WPF 4.5 in C#, 4th Edition Пн 11.04.2016
Pro WPF 4.5 in C#, 4th Edition
Год: 2012
Pro Silverlight 5 in C#, 4th Edition Ср 06.04.2016
Pro Silverlight 5 in C#, 4th Edition
Год: 2012
Pro C# 5.0 and the .NET 4.5 Framework, 6th Edition Пн 21.03.2016
Pro C# 5.0 and the .NET 4.5 Framework, 6th Edition
Год: 2012
Pro ASP.NET 4 in C# 2010, 4th Edition Ср 16.03.2016
Pro ASP.NET 4 in C# 2010, 4th Edition
Год: 2010
Pro .NET 4 Parallel Programming in C# Ср 09.03.2016
Pro .NET 4 Parallel Programming in C#
Год: 2010
A Programmer's Guide to C# 5.0, 4th Edition Ср 24.02.2016
A Programmer's Guide to C# 5.0, 4th Edition
Год: 2012
CLR via C#, 3rd Edition Сб 27.10.2012
CLR via C#, 3rd Edition
Год: 2009
Книги
Fundamentals of Software Architecture вчера, 10:13
Fundamentals of Software Architecture
Год: 2020
Refactoring with C# Вт 23.04.2024
Refactoring with C#
Год: 2023
Building IoT Visualizations using Grafana Вт 09.04.2024
Building IoT Visualizations using Grafana
Год: 2022

Разработано на основе BlackNight CMS
Release v.2024-05-05
© 2000–2024 Blackball
Дизайн & программирование:
О сайтеРеклама
Visitors
Web-site performed by Sergey Drozdov
BlackballРекламаСтатистикаПоддержка | МузыкаПлейлистыКиноВидеоИгрыАудиоПрограммыСтатьиКартинкиЮморФорумДневник сайтаПрислать контент