Pull to refresh

Comments 19

1)    Уменьшения связанности между объектами. 

Между какими конкретно объектами? И чем это лучше, чем просто вызывать сервис через интерфейс?

2)    При сложной логике взаимодействия между множеством объектов позволяет настроить передачу сообщений между объектами.

Что это вообще значит? Ну и если у меня сложная логика взаимодействия между объектами, я б предпочёл упростить её, а не делать всё через одно место медиатр, т.к. это даже навигацию по коду усложнит и даст возможность создать циклические зависимости, что невозможно при работе через интерфейсы и сервисы.

3)    Позволяет с меньшими затратами и, не ломая текущую архитектуру проекта, создать расширение и доработку функционала.

Как именно? Какая то общая фраза


Всё ещё не раскрыта тема, зачем оно вообще.

2)    Сложность реализации функционала. Описание дополнительных вспомогательных классов, которые нужны при написании обработчиков.

Это в любом случае нужно, что с медиатром, что без.

3)    Если контекст базы данных передается в обработчик, то это нужно учитывать при создании логики, чтобы не возникло ошибок, связанных с одновременным доступом к контексту из разных потоков.

Это в любом случае стоит учитывать, что с медиатром, что без

А зачем вы в запрос к медиатру пихаете зависимости? Оо

Это дикая связанность какая то, которую вы якобы избежали
Почему бы хэндлеру не получить всё из DI?

Слабая связанность она только кажется таковой. На самом деле код сильно связан, но, благодаря посреднику, ты не знаешь как. Лично для меня это скорее минус.
Плюсы в виде сложной навигации по коду и прочее вы уже описали.
Если делать все тоже самое, но использовать явный вызов, то ничего особо не изменится.
Реально плюсом можно считать простоту распиливания такого кода на микросервисы, где посредник заменяется брокером сообщений. Но такой себе плюс.

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

Я сомневаюсь, что медиатр реально поможет распилить. Выдернуть условный handler в отдельный микросервис нельзя, т.к. он использует какие то общие типы и зависимости чаще всего. Так же где то конфигурится весь этот медиатр, там всякие цепочки и прочее. Но при этом смотря на handler я понятия не имею, от кого он реально зависит и могу ли я его просто так вытащить в отдельный микросервис

Здравствуйте. В данной статье я хотел показать возможности данной библиотеки в разрезе реализации бизнес-логики, а не как реализатора стандартного шаблона “посредник”. С моей точки зрения, наличие у основного обработчика пре-обработчиков и пост-обработчиков дает некоторое преимущество в реализации некоторых стандартных процессов. Как бонус, пре-обработчик и пост-обработчик позволяют уменьшить связанность логики, так как физически они изолированы от основного обработчика и время их выполнения разнесены во времени. Это дает возможность вносить изменения в пред-обработчик и пост-обработчик, при этом не затрагивая логики работы основного обработчика и наоборот. Конечно, все зависит от масштаба доработок.   

Здравствуйте!

наличие у основного обработчика пре-обработчиков и пост-обработчиков дает некоторое преимущество в реализации некоторых стандартных процессов

Некоторое преимущество, некоторые процессы - это слишком общие слова и особого смысла не несут.

Как бонус, пре-обработчик и пост-обработчик позволяют уменьшить связанность логики

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

время их выполнения разнесены во времени

Что это значит? На самом деле они выполняются ровно так как если бы мы их написали руками в виде "предобработка-обработка-постобработка", разве что теперь они неявные и не факт, что выполняются.

Это дает возможность вносить изменения в пред-обработчик и пост-обработчик, при этом не затрагивая логики работы основного обработчика и наоборот

Это всё решается опять общая фраза, если пост- и предобработчики - часть бизнес логики, то лучше (нужно) сделать их явно. Если это какие то инфраструктурные вещи - то можно сделать явно какой то обёрткой или middleware, если речь про asp.net

string error = string.Empty;
try
{
    var response = ///;

    if (!response.StartsWith("OK")) {
        // return ERROR(error)
    }
    // return OK
}
catch (Exception ex)
{
    error = ex.Message;
}
// return ERROR(error)

Зачем вы пишете такой код? Почему бы из catch не вернуть результат и не создавать string error непонятно зачем?

response.StartsWith серьёзно? Почему бы типами не разрулить?

А какие преимущества мы получим, если будем использовать типы? И почему данная реализация недопустима?

Вы серьёзно спрашиваете почему плохо передавать строку и в случае успеха, и в случае ошибки, и разруливать успех по префиксу строки?

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

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

В-третьих, это ненадежно, что если я отдам строку "ok" вместо "OK"? А вы даже не строку проверяете, а префикс, как будто сами не уверены, что там.

Для меня вообще большая загадка как MediatR стал хоть сколько-то используемым в .net сообществе. Его бы в антипаттерны запихнуть.

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

В заключение статьи хорошо отмечено, что «MediatR не является реализацией шаблона Посредник, а является внутренней шиной». Как мне кажется, это основной плюс данной библиотеки. Это позволяет отделить в тестах специфику web от бизнес-логики.

Так, если замокать ISender и создать клиента с помощью WebApplicationFactory, то мы можем легко протестировать, что http-запрос корректно преобразовался в Request, который отправился во внутреннюю шину и полученный из нее Response преобразован в ожидаемый http-ответ.

Бизнес-логику, реализованную в хэндлерах (как слой юзкейсов над сервисами), можно тестировать классическими unit-тестами.

Но, автор MediatR попытался превратить библиотеку в «комбайн», использование которого и вызывают такие холивары. Я предпочитаю свою light-реализацию данного функционала.

Реализация на интерфейсах – хорошая практика, если у вас небольшое приложение. Но, если у вашего Web API 100+ методов, то подход MediatR-а позволяет получить слабосвязанный код без нагромождения десятков интерфейсов.

Промахнулся, это был ответ на сообщение @Oceanshiver ".. вообще большая загадка как MediatR стал хоть сколько-то используемым .."

Так, если замокать ISender и создать клиента с помощью WebApplicationFactory, то мы можем легко протестировать, что http-запрос корректно преобразовался в Request, который отправился во внутреннюю шину и полученный из нее Response преобразован в ожидаемый http-ответ.

А в чем сложность сделать то же самое с интерфейсами и сервисами обычными?

  1. Неоднократная и хрупкая регистрация обработчиков:

        container.RegisterInstance<TextWriter>(new StringWriter(sb))
                    .RegisterMediator(new HierarchicalLifetimeManager())
                    .RegisterMediatorHandlers(
                        Assembly.GetAssembly(typeof(StartRequest)));

        container.RegisterInstance<TextWriter>(new StringWriter(sb))
                    .RegisterMediator(new HierarchicalLifetimeManager())
                    .RegisterMediatorHandlers(
                        Assembly.GetAssembly(typeof(RejectRequest)));

         container.RegisterInstance<TextWriter>(new StringWriter(sb))
                   .RegisterMediator(new HierarchicalLifetimeManager())
                   .RegisterMediatorHandlers(
                        Assembly.GetAssembly(typeof(ApproveRequest)));
  • Если сделать предположение что команды и обработчики живут в одной assembly, то запускается три раза регистрация одного и того-же.

  • Если сделать предположение что команды живут в одной assembly, а соответствующие им обработчики живут в другой assembly, как делают при выделении контракта взаимодействия в отдельный сборку, предложенный вариант работать не будет. Я бы рекомендовал в строке Assembly.GetAssembly(typeof(...))); указывать класс обработчика.

  1. return await Task.Run<ActionResult>(async() => { бессмысленное и беспощадное решение. Создаем отдельный таск, через замыкания передаем в него аргументы чтобы подождать пока таск завершит свою работу и вернуть управление вызывающему методу.

  2. Как уже было сказано в комментарии выше, проверять статус результата по префиксу не лучший способ. Возможно будет красивее возвращать не строку, а класс ответа, в котором будет признак успешности бизнес составляющей обработки.

public class StartRequest : IRequest<StartResponse> { }

public class BaseRequest { }

public class StartResponse : BaseResponse {  }

public class BaseResponse 
{
  bool IsSuccess;
  string Error;
}
  1. Про post обработку сообщений медиатора озвучили, но почему бы не использовать тот же самый подход в ASP контроллере и не обрабатывать ошибки в middleware ? Код станет чище и лаконичнее.

  2. IConfig = new AppConfiguration() Я не знаю вашего решения, но обычно конфигурации приложения и тд извлекается через ServiceProvider а не передаются с командой, но могут быть исключения.

return await Task.Run<ActionResult>(async() => { бессмысленное и беспощадное решение. Создаем отдельный таск, через замыкания передаем в него аргументы чтобы подождать пока таск завершит свою работу и вернуть управление вызывающему методу.

Это неверно: await — это не Wait. Управление вызывающему методу возвращается первым же await, выражение после которого ещё не успело выполниться (.GetAwaiter().IsCompleted от него возвращает false) — см. спецификацию языка C#, гл.12, п.12.9.8.4

А зачем эта обёртка то в Task.Run вообще?

Лучше у автора спросить, навереное. У него там среда исполнения какая-то мне непонятная(лень разбираться было), потому не скажу.
А в общем случае применение такой конструкции есть: чтобы запустить ту задачу вне существующего контекста синхронизации (не только лишь все знают про ConfigureAwait).

Управление вызывающему методу возвращается первым же await

То что await != wait никто и не спорит.
Если это хитрая конструкция для принудительного завершения метода асинхронно, то есть более красивый и лаконичный метод Task.Yield.

Дополнение во втором комменте про контекст синхронизации - да, возможно эту проблему так решали.

Sign up to leave a comment.