Model View Dispatcher (cqrs over mvc)

    image

    Доброго всем времени суток, в этой статье хочу осветить ещё один компонент из библиотеки Incoding Framework.
    Model View Dispatcher (MVD) — позволяет избавится от избыточного кода (а именно asp.net mvc controller) и упростить навигацию по проекту, уменьшив количество абстракций между клиентским и серверным кодом.

    На Хабре имеются несколько статей о IML и CQRS, которые входят в состав framework
    1. Знакомство с IML
    2. Incoding CQRS
      примечание: статья была опубликована, но в то время были проблемы с аккаунтом на хабре и она прошла не замеченной
    3. IML vs AngularJs
      примечание: статья подверглась критике (объективной и субъективной)
    4. Ответ на IMl vs AngularJs
      примечание: нельзя назвать успехом, но уже немного лучше

    MVD фигурировал в некоторых статьях, но как часть примера демонстрирующая возможности IML, поэтому я решил рассказать о нем отдельно, но обо всем по порядку…

    Зачем?


    Рассмотрим сценарий применения asp.net mvc + cqrs
    • Controller
      public ActionResult Details(GetUserDetailsQuery query)
      {
          var model = dispatcher.Query(query);
          return Json(model);
      }
      

      примечание: action отвечает за binding и передачу query в Dispatcher для возврата полученных данных в виде Json
    • View
      $.get('@Url.Action("Details","Controller")',callback)
      


    примечание: не хватает листинга Query для полной картины, но становится очевидно избыточность Action

    Диета


    Чтобы посадить controller на «диету» можно воспользоватся паттерном Mediator (как один из вариантов), который опирается на Interface и Generic, но это только позволяет упростить и объединить код, но не решить проблему полностью, потому что все равно приходится писать однотипные Controller/Action.

    Model View Dispatcher (MVD) — позволяет выполнять Command/Query в «обход» asp.net mvc. Для демонстрации перепишем предыдущую задачу, но с MVD
    $.get('@Url.Dispatcher().Query(new GetUserDetailsQuery()).AsJson()',callback)
    

    Подсчитаем бонусы

    • Не нужен Action
    • Проще навигация, потому что теперь действия на странице отражают картину (go to delcaration без посредников) на серверного кода
    • Строгая типизация при построении URL (visual studio intelisence, refactor utilities)


    Нет MVC ???


    Чтобы получить ответ, посмотрим как подключить MVD к проекту:

    Создать DispatcherController (начиная с версии 1.1 устанавливается через nuget), который унаследовать от DispatcherControllerBase
    public class DispatcherController : DispatcherControllerBase
    {        
        public DispatcherController()
       : base(typeof(T).Assembly)
        {    }    
    }
    

    примечание: конструктор принимает Assembly в котором объявлены Command/Query

    DispatcherControllerBase содержит следующие методы (Actions):
    Query(string incType, string incGeneric, bool? incValidate)
    Render(string incView, string incType, string incGeneric)
    Push(string incType, string incGeneric)
    Composite(string incTypes)
    QueryToFile(string incType, string incGeneric, string incContentType, string incFileDownloadName)
    

    пример url, после вызова которого будет выполнен Push command
    Url.Action("Push", "Dispatcher", new  { incType = typeof(Command).Name } )
    

    Альтернативный способ через DSL (domain specific language)
    Url.Dispatcher().Push(new Command())
    

    примечание: дело не только в лаконичности синтаксиса (хотя это важно), но также в абстракции от деталей (имена параметров и т.д.)

    Обратить внимание
    • MVD использует IDispatcher, который зарегистрирован в IoCFactory (пример со StructureMap в качестве провайдера)
    • В качестве ActionResult возвращается IncodingResult, который формирует JSON
      { success: true/false , data:something/null , redirectTo:url/null }


    Вывод: текущая реализация MVD опирается на asp.net mvc, по факту просто делает обобщенный (не generic) Controller, но можно использовать httphandler или другой http обработчик в качестве платформы.

    Что умеет ?


    MVD покрывает большинство сценариев, которые встречаются при веб-разработке на платформе asp.net mvc:
    примечание: исходный код примеров на GitHub
    • Push
      Url.Dispatcher().Push(new AddUserCommand
                                                {
                                     Id = "59140B31-8BB2-49BA-AE52-368680D5418A",
                                     Name = "Vlad"
                                                })
      

      примечание: вопросы валидирования далее
    • Push generic
      Url.Dispatcher().Push(new AddEntityCommand<T>())
      

    • Сomposite
      Url.Dispatcher()
           .Push(new AddUserCommand { Id = "1", Name = "Name" })
           .Push(new ApproveUserCommand { UserId = "2" })
      

    • Query as json
      Url.Dispatcher()
           .Query(new GetCurrentDtQuery())
           .AsJson()
      

    • Query generic
      Url.Dispatcher()
           .Query(new GetTypeNameQuery<T>())
           .AsJson()
      

    • Query as view
      Url.Dispatcher()
           .Query(new GetUserQuery())
           .AsView("~/Views/Home/User.cshtml")
      

      примечание: путь к View строится от корневой (в asp.net mvc относительно Controller) директории сайта, что позволяет строить любую структуру папок.
    • Query as file
      <a href="@Url.Dispatcher().Query(new GetFileQuery()).AsFile(incFileDownloadName: "framework")">Download</a>
      

      примечание: требуется реализация QueryBase<byte[]> для Query
    • Model as view
      Url.Dispatcher()
           .Model(new GetUserQuery.Response
                     {
                    Id = "2",
                    Name = "Incoding Framework"
                    })
           .AsView("~/Views/Home/User.cshtml")
      

    • View
      Url.Dispatcher().AsView("~/Views/Home/Template.cshtml")
      


    Сценарии


    MVD состоит из DispatcherController (серверная часть), который используя инфраструктуру CQRS выполняет Command/Query и Url.Dispatcher (адрес строится на сервере, но далее используется на клиенте) для построения url, но не является самостоятельным компонентом, поэтому примеры будут в контексте с IML.
    • Post
      @(Html.When(JqueryBind.Change)
            .AjaxPost(Url.Dispatcher().Push<AddAcoGroupCommand>(new {Value = Selector.Jquery.Self() } ))
            .OnSuccess(dsl => dsl.Utilities.Window.Alert("Success"))
            .AsHtmlAttributes()
            .ToCheckBox(true))
      

      примечание: в качестве параметра используется анонимный объект, но проверка на соответствие (если Value отсутствует в AddAcoGroupCommand будет exception) полей будет.
    • Post form
      @model AddAcoGroupCommand
      <form action="@Url.Dispatcher().Push(new AddAcoGroupCommand())">
          @Html.HiddenFor(r=>r.Id)
          <input type="submit"/>
      </form>
      

    • Render View
      @(Html.When(JqueryBind.InitIncoding)
            .AjaxGet(Url.Dispatcher().AsView("~/Views/Patient/BenefitListControl.cshtml"))
            .OnSuccess(dsl => dsl.Self().Core().Insert.Html())
            .AsHtmlAttributes()
            .ToDiv())
      

      примечание: удобно для поиска template
      var urlTmpl  = Url.Dispatcher().AsView("~/Views/Medication/MedicationTmpl.cshtml");
      dsl.Self().Core().Insert.WithTemplateByUrl(urlTemplate).Append();
      

    • Render model
      @(Html.When(JqueryBind.InitIncoding)
            .AjaxGet(Url.Dispatcher()
            .Model( new BenefitModel()
                        {
                                GroupName = Selector.Incoding.QueryString<BenefitModel>(r=>r.GroupName),
                                IsPrimary = true
                        })
            .AsView("~/Views/Patient/BenefitListControl.cshtml"))
            .OnSuccess(dsl => dsl.Self().Core().Insert.Html())
            .AsHtmlAttributes()
            .ToDiv())
      

      примечание: возможность использовать Selector (вычисляется на клиенте) при формировании url позволяет не прибегать к построению routes на клиенте.

    примечание: больше примеров в Inc-todo

    Action Attributes


    Это мощный механизм для реализации АОП (Аспектно-ориенти́рованное программирование‎), частым сценарием где он применяется, может быть проверка авторизации, поэтому рассмотрим возможные пути решения:
    • Отметить атрибутом DispatcherController
      примечание: способ очень удобен, если Вы пишите CRM систему и все действия проходя с авторизацией
    • Dispatcher event — поскольку все Command/Query выполняются через единую точку, это позволяет реализовать аналогичное поведение как при Action attributes.
      примечание: акцент с Action сдвигается на Command/Query

    Валидация


    В рамках Incoding Framework есть готовая инфраструктура для валидации (в отличии от js framework тут Server/Client покрытие) и MVD интегрирован с ней.
    @(using(Html.When(JqueryBind.InitIncoding)      
                             .Direct()
                             .OnSuccess(dsl => dsl.Self().Core().Form.Validation.Parse())
                             .When(JqueryBind.Submit)
                             .PreventDefault()
                             .Submit()
                             .OnError(dsl => dsl.Self().Core().Form.Validation.Refresh())
                             .AsHtmlAttributes()
                             .ToBeginForm(Url.Dispatcher().Push(new AddUserCommand()))))
    {
     @Html.TextBoxFor(r=>r.Name)
     @Html.ValidationMessageFor(r=>r.Name)
    }
    

    примечание: поскольку код будет однотипный для большинства форм, то можно написать html helper
    Код знаком большинству разработчиков на asp.net mvc по стандартному Html.BeginForm (можно использовать его передав iml в html attributes), но с несколькими отличиями:
    • При первом появление элемента на странице делаем разбор текущей формы на наличие валидации
    • По событию Submit останавливаем поведение по умолчанию (чтобы форма не отправилась) и используя ajax делаем post на указанный адрес (Url.Dispatcher().Push(new AddUserCommand())
    • В случае возраста ошибки (пользовательской, а не фатальной) в которой обновляем форму на основе Model State (ниже подробней)


    Для понимания, как работает метод Validation.Refresh посмотрим реализацию Push в dispatcher contoller
         if (!ModelState.IsValid)
            return IncodingResult.Error(ModelState)
    
         try
         {
             dispatcher.Push(composite);
             return IncodingResult.Success();
          }
          catch (IncWebException exception)
          {
                    foreach (var pairError in exception.Errors)
                    {
                        foreach (var errorMessage in pairError.Value)
                            ModelState.AddModelError(pairError.Key, errorMessage);
                    }
                    return IncodingResult.Error(ModelState)
          }
    

    примечание: реальный код несколько сложнее из-за дополнительной логики, но пользователь Incoding Framework абстрагирован от этих деталей и работает с TryPush или MVD push
    В catch мы перехватываем только IncWebException (на основе которого заполняем ModelState), а остальные exception считаем провальными и оставляем их обработку на совесть global.asax.

    А как было раньше ?

    Ответ поможет проанализировать, чем же решение на базе Incoding Framework лучше того, что имеется в стандартном asp.net mvc
    public ActionResult Add(AddUserCommand command)
    {
        if (ModelState.IsValid)
            return View(command);
    
        return Execute(command);
    }
    

    Если ModelState содержит ошибки, возвращается View, которое строится на основе command (хорошо, если не используется Container с дополнительными списками, которые тоже придется заново строить), чтобы сохранить состояние. Такое поведение введет к следующим проблема:
    • Заново строится форма, что занимает время и увеличивает трафик
    • Возвращаемый результат является html (пусть и упакованный в json), что не позволяет повторно использовать action, как API для сторонних (мобильных) приложений


    Итог


    MVD является очень хорошим союзником для борьбы с однотипными Action, которые приводят к «разбуханию» кода, что особенно критично на поздних этапах проекта. MVD можно использовать без IML, но тогда теряется возможность использовать Selector в routes, что негативно скажется на типизации, из-за того, что придется в «ручную» (pure js) собирать параметры.
    Конечно, могут быть сценарии с которыми MVD не способен (временно или просто не возможно реализовать) справится, но ничто не мешает написать Controller и Action для конкретных (может это всего 5% — 10%) случаев.

    Получается все ради того, чтобы убрать дубляж ?


    Если MVD поможет сократить код на 10-15% и ускорить разработку, то это уже очень хороший результат, но мы пошли дальше и реализовали возможность строить схему end point (идея взята у wcf endpoint)…

    Исходный код, как документация ?

    Диалог между разработчиком api и мобильного приложения:

    • Api — я добавил новый запрос, по адресу /GetUsers?Active=true
    • Api — а также новые поля для создания user ( Comment, City, State )
    • Api — о, чуть не забыл City это справочник и его можно получить по запросу /GetCities
    • Api — и ещё один момент, заказчик просил, чтобы State был числом, так что учти это
    • Мобильное приложение — ок, опиши в документе и назови %sitename%-api-%current-version%
      примечание: основной проблемой является %current-version%, потому что приходится поддерживать актуальность документации, после каждого изменения в коде

    Тот же диалог, но с mvd end-points

    • Api — обновил код
    • Мобильное приложение — уже можно смотреть appDomain/Dispatcher/Endpoints?
    • Api — конечно, там кстати песочница где можно проверить command/query
      примечание: песочница, так же удобна и для разработчика API, так как часто разработка идет без UI (user interface) и чтобы быстро проверить на работоспособность (unit test покрывает код, но интеграционные тоже нужны) можно воспользоватся авто-сгенерированной формой

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


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

    P.S. опубликована новая версия (пока beta) Incoding Framework в nuget, где появилось много нововведений и доработок. Я приведу несколько, а остальные постараюсь расписать в отдельных статьях (полный список на нашем bugtracker)
    • Упрощения условий
      Break.If(r=>r.Is(()=>Selector.Jquery.Self()).And.Is(()=>"id".ToId()== 12)) // Old
      Break.If(()=>Selector.Jquery.Self() && id.ToId() == 12) // New
      

    • Реализация EF, RavenDB, MongoDb провайдеров (одна command и разные ORM на github и inc-todo-ravendb)
    • Упрощения синтаксиса
      inDsl.Core().JQuery.Attributes.SetAttr(HtmlAttribute.Checked) // Now
      inDsl.Attr.Set(HtmlAttr.Checked) // Future
      


    примечание: причина статуса beta (в конце мая уже будет стабильная версия) в том, что ещё не все наши проекты переведены на эту версию и остался ряд задач, которые планировали для этого релиза
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 172
    • +2
      Вы исходите из того, что код в экшне контроллера — тривиальный. Но это так только в том случае, если вы так решили; и именно вы породили лишнюю абстракцию в виде «диспетчера» (к которому обращается контроллер). Но зачем она? Не видя кода этого «диспетчера», сложно судить, но почему просто не внести его в контроллер?
      • –1
        Но это так только в том случае, если вы так решили; и именно вы породили лишнюю абстракцию в виде «диспетчера» (к которому обращается контроллер)

        Сравнивать Controller с Dispatcher не верно, потому что они выполняются разные задачи, далее я приведу аргументы.

        Не видя кода этого «диспетчера», сложно судить, но почему просто не внести его в контроллер?

        Есть несколько причин:

        1.Dispatcher можно использовать в background service, console application, Win/Wpf form, потому что он не завязан на особенности работы Web.
        примечание: пример использования Disaptcher в Fluent Validation
        RuleFor(r => r.Name)
                 .Must((command, name) => IoCFactory.Instance.TryResolve<IDispatcher>()
                                                             .Query(new IsUniqueValueQuery<Protocol>
                                                                                                    {
                                                                                        HcsId = command.HcsId,
                                                                                        Name = name
                                                                                                   }))
                  .WithMessage("'Name' already exists. Please change to a unique name.") 
        

        2. Вынести код Query и Command в controller не получится, потому что все равно нужен будет общий метод Push/Query, который откроет Unit Of Work и выполнит код Command/Query (ниже пример кода)
            public class AddUserCommand : CommandBase
            {
                public string Name{ get; set; }
                public string Password { get; set; }
                public override void Execute()
                {     
                    Repository.Save(new User() { Name = Name, Password = Password  });
                }
            }
        

        примечание: подробней о том, как работает Push можно посмотреть в исходниках

        P.S. Controller все равно нужен и об этом я расписал в статье, MVD это просто способ обобщить все (или большинство) Action к одному.
        • +1
          Сравнивать Controller с Dispatcher не верно, потому что они выполняются разные задачи, далее я приведу аргументы.

          Так их никто и не сравнивает.

          Dispatcher можно использовать в background service, console application, Win/Wpf form, потому что он не завязан на особенности работы Web.

          Ага, так это внезапно оказался сервисный слой (в терминах Эспозито). Тогда к нему нельзя обращаться напрямую из view, потому что вы нарушаете изоляцию. Да, бывает так, что контроллеры при работе с сервисным слоем выглядят избыточными, но их задача — изоляция одного уровня (UI) от другого (бизнес).

          А еще, если вы говорите, что диспетчер можно использовать без всего остального, то ваш термин (model-view-dispatcher) некорректен, потому что триада подразумевает неделимость и взаимосвязанность, а у вас диспетчер ничего не знает ни про модель, ни про view.

          Так что что-то совершенно не понятны бонусы от того, что вы предлагаете, зато видно, что связность растет.
          • 0
            Ага, так это внезапно оказался сервисный слой (в терминах Эспозито). Тогда к нему нельзя обращаться напрямую из view, потому что вы нарушаете изоляцию. Да, бывает так, что контроллеры при работе с сервисным слоем выглядят избыточными, но их задача — изоляция одного уровня (UI) от другого (бизнес)

            Url dispatcher строит адрес, по этому Url.Dispatcher.Push(new AddUserCommand()) это тоже самое, что Url.Action(«Add»,User"), но с тем отличием, что не надо создавать Action.

            Так что что-то совершенно не понятны бонусы от того, что вы предлагаете, зато видно, что связность растет.

            1. Убираем дубляж в коде, потому что заменяем Action, на Url.Dispatcher
            2. Типизированный синтаксис для построения адреса
            Url.Dispatcher().Query(new GetMyEntity<T>(){ Status = EntityOfStatus.New}).AsJson()
            

            3. Быстрый go to declaration сразу из view
            4. Каждое действие на странице (view) проецируется на Command/Query, что позволяет общаться терминами предметной области проекта
            примечание: dom элемент связан с конкретными Query/Command
            5. Возможность построить карту сайта на основе CQRS ( Command/Query) для документации ( в статьей есть пример )

            • +1
              (я в этом комменте буду исходить из того, что Dispatcher — это «универсальный», т.е., равноприменимый к различным окружениям и сценариям использования сервисный слой, и у него существует несколько потребителей, например, веб-приложение, агент в виде windows-сервиса и веб-сервис; в противном случае — если потребитель строго один — я, на самом деле, не вижу смысла в этом слое)

              Url dispatcher строит адрес, по этому Url.Dispatcher.Push(new AddUserCommand()) это тоже самое, что Url.Action(«Add»,User"), но с тем отличием, что не надо создавать Action.

              Это не то же самое. В первом случае вы обращаетесь напрямую к сервисному слою, во-втором — к UI-слою.

              Убираем дубляж в коде, потому что заменяем Action, на Url.Dispatcher

              Где дублирование-то?

              Типизированный синтаксис для построения адреса

              Это не преимущество вашего дизайна, в «обычном» MVC так тоже можно — хелпер пишется за несколько часов, после чего код выглядит так:
              Url.Action<TicketController>(t => t.View(ticketId))
              


              Быстрый go to declaration сразу из view

              Работает и в «обычном» MVC.

              Каждое действие на странице (view) проецируется на Command/Query, что позволяет общаться терминами предметной области проекта

              А правильно ли это? Страница (представление) должна оперировать терминами сценариев использования, которые не обязательно эквивалентны терминам предметной области (во-первых, могут расходиться термины действий, а во-вторых, предметная область оперирует моделью домена, а представление — моделью представления (view model)).

              Возможность построить карту сайта на основе CQRS ( Command/Query) для документации ( в статьей есть пример )

              В статье я нашел пример только автодокументируемого API; и это тоже не уникальное преимущество вашего дизайна, это можно сделать и в WebAPI (где ему больше места), и в MVC (в ServiceStack, например, сделано).

              Понимаете ли, вы упускаете из вида одну простую вещь: бизнес не эквивалентен UI (я специально начал комментарий с этой оговорки; когда они эквивалентны, бизнес вливается в UI, а не наоборот). Как следствие, многие нюансы современных архитектур направлены именно на то, чтобы изолировать одни изменения от других. А вы, напротив, нарушаете эту изоляцию, и напрямую связываете компоненты UI (например, view) с сервисным слоем и доменной моделью.

              Пара примеров.

              Предположим, у вас есть операция в домене, котора возвращает список заказов, созданных в магазине (в контексте «менеджер магазина просматривает созданные заказы»). Все менеджеры видят все заказы, поэтому контракт операции сводится к IEnumerable<Order> GetPlacedOrders(). Проходит время, бизнес становится территориально распределенным, и теперь каждый менеджер должен видеть только те заказы, которые относятся к курируемому им подразделению. Тут мы сразу скажем, что мы умные, и вместо того, чтобы сделать операцию GetPlacedOrdersByDepartment, сделаем GetPlacedOrdersForManager, исходя из того, что логика вычисления доступных заказов может потом меняться, а вот сценарий использования — уже нет. Так или иначе, контракт операции поменялся (IEnumerable<Order> GetPlacedOrdersForManager(managerId)). Что происходит дальше?

              В случае с «традиционной» декомпоновкой, эти изменения вносятся в соответствующий Action (достаем из контекста пользователя, определяем идентификатор, передаем в сервисный слой). А что происходит у вас? Насколько я могу понять ваше описание, есть несколько вариантов: либо идентификатор будет передаваться в составе query (что означает его передачу с клиента и небезопасность), либо же запрос пойдет не в диспетчер, а в свежесозданный action, который сделает то же самое, что и при традиционном подходе (еще один вариант — это «умные» query, которые при проходе через сервер сами дособерут нужную информацию из контекста, но это означает, что query специфична для ui). Второй путь приведет к тому, что какие-то места UI будут общаться с диспетчерами напрямую, а какие-то — через контроллер, что, как и любая неконсистентность, усложняет анализ.

              Второй пример.

              В том же контексте, предположим, что появилось новое требование: заказы, поступившие от vip-заказчиков, должны подсвечиваться в интерфейсе розовеньким (естественно, это касается только веб-приложения, и агентам, и веб-сервисам на это плевать). Контракт сервисного слоя, на самом деле, не меняется (информация о заказчике все равно есть в Order, информация о vip — в заказчике). Что же происходит?

              Во view добавляется условие (грубо) if (order.IsVip) row.Color = VipColor; во viewmodel добавляется атрибут IsVip, вычисляемый внутри viewmodel на основании переданной доменной модели. Это традиционный подход. А у вас? Либо (при неизменности модели) необходимо внести логику во view (привет тестируемости), либо внести прослойку из контроллера-viewmodel (еще один вариант — промежуточная модель прямо поверх возвращаемого диспетчером результата, с помощью метода расширения, но тоже увеличивается количество кода во view).

              На всякий случай, чтобы не было аргумента «можно же ввести контроллеры там, где они нужны» — это нарушает единообразность архитектуры, что резко усложняет ее анализ и валидацию (в том числе и статическую).
              • 0
                Url.Dispatcher.Push(new AddUserCommand()) это тоже самое, что Url.Action(«Add»,User").
                Это не то же самое. В первом случае вы обращаетесь напрямую к сервисному слою, во-втором — к UI-слою.

                MVD это URL адрес, который построит СТРОКУ /Dispatcher/Push?Type=AddUserCommand и все, ни каких слоев ( все тот же MVC, который теперь более динамичный, но так же типизированный )
                В MVD, вам надо создать controller User и action Add, который потом вызвать через Url.Action(«Add»,«User»), а тут просто Url.Dispatcher().Push(new AddUserCommand())

                Это не преимущество вашего дизайна, в «обычном» MVC так тоже можно — хелпер пишется за несколько часов, после чего код выглядит так:
                Url.Action<TicketController>(t => t.View(ticketId))

                Вам, надо создать controller Ticket и в нем action View, но с MVD надо только указать URL Url.Dispatcher().AsView(«Ticket/View.cshtml»)

                А вы, напротив, нарушаете эту изоляцию, и напрямую связываете компоненты UI (например, view) с сервисным слоем и доменной моделью.

                Ещё раз, MVD это один обобщенный Controller и построитель для URL и хватит боятся слоев )))

                Коротко по примерам:
                (еще один вариант — это «умные» query, которые при проходе через сервер сами дособерут нужную информацию из контекста, но это означает, что query специфична для ui

                Создаем ISessionContext и далее SessionContext.Current.UserId. Поле Current это Singleton, который можно подменить через IoC, если надо вместо Cookies использовать mock для тестов или иную реализацию.

                Во view добавляется условие (грубо) if (order.IsVip) row.Color = VipColor; во viewmodel добавляется атрибут IsVip, вычисляемый внутри viewmodel на основании переданной доменной модели. Это традиционный подход. А у вас?

                Query возвращает данные, какие хотите, так что я не понял, в чем подвох. Может Вы считаете, что Query может возвращать только Entity из базы данных ?).
                Пример Query
                public class GetGapsQuery : QueryBase<List<GetGapsQuery.Response>>
                  {
                    public Guid Status { get; set; }
                
                    public bool ShowHistory { get; set; }
                
                    public class Response 
                    { 
                     public string Type { get; set; }
                
                     public string Status { get; set; }
                
                     public bool Active { get; set; }
                    }
                
                    protected override List<Response> ExecuteResult()
                    {
                       return Repository
                            .Query(whereSpecification: new GapByStatusOptWhereSpec(Status)
                                                          .And(new ActiveEntityWhereSpec<Gap>(ShowHistory)))
                            .ToList()                 
                            .Select(gap => new Response
                                               {                                                            
                                                       Type = gap.Type.Name,
                                                       Active = gap.Active,
                                                       Status = gap.Status.Name,                            
                                               })
                            .ToList();
                     }
                
                   } 
                


                • +1
                  В MVD, вам надо создать controller User и action Add, который потом вызвать через Url.Action(«Add»,«User»), а тут просто Url.Dispatcher().Push(new AddUserCommand())

                  Что плохого в необходимости создать экшн и контроллер? Чем это хуже необходимости создать команду?

                  Вам, надо создать controller Ticket и в нем action View, но с MVD надо только указать URL Url.Dispatcher().AsView(«Ticket/View.cshtml»)

                  А это уже вопиющее нарушение инкапсуляции — у вас один view начинает знать о другом.

                  Создаем ISessionContext и далее SessionContext.Current.UserId. Поле Current это Singleton, который можно подменить через IoC, если надо вместо Cookies использовать mock для тестов или иную реализацию.

                  Во-первых, как уже говорилось, глобальный контекст — это плохо. Если не верите мне — посмотрите на путь, по которому шел ASP.net, от HttpContext.Current в старых вебформах, через Controller.Context в MVC и к RequestMessage.Context в WebAPI.
                  А во-вторых, в этот момент ваша команда (внутри которой вы обращаетесь к ISessionContext) начинает зависеть от конкретного сценария использования (ее уже нельзя применить, скажем, в агенте, который работает сразу для всех пользователей), и вся выгода от ее использования пропадает.

                  Query возвращает данные, какие хотите, так что я не понял, в чем подвох.

                  «Подвох» в том, что как только Query начинает возвращать данные, подогнанные под формат конкретного представления, как она тоже начинает терять универсальность.

                  Смотрите, вы последовательно предложили привязать команду (через SessionContext) и запрос (через вид возвращаемой модели) к конкретному сценарию использования и представлению. Их теперь нельзя использовать повторно в других сценариях и контекстах. В чем выигрыш по сравнению с обычными controller/action?
                  • 0
                    Что плохого в необходимости создать экшн и контроллер? Чем это хуже необходимости создать команду?

                    Тем, что код Command Вам все равно надо написать, а без Action можно обойтись

                    HttpContext.Current в старых вебформах

                    Current это значит текущий и у каждого потока он будет свой. Все это есть в asp.net mvc

                    «Подвох» в том, что как только Query начинает возвращать данные, подогнанные под формат конкретного представления, как она тоже начинает терять универсальность.

                    Напишите другой Query, а общие вынесите в базовый. Опыт показал, что делать универсальный Query это не всегда хорошая идея, но что бы не порождать copy&paste можно использовать Specification для компоновки Where/Order/Pagianated и другие меры.

                    Их теперь нельзя использовать повторно в других сценариях и контекстах. В чем выигрыш по сравнению с обычными controller/action?

                    В том, что Вам не надо писать Controller, а можно обратится (через Controller, но обобщенный) к Dispatcher. Я так понимаю, что вы предполагаете вместо AddUserCommand написать controller User и в нем метод Add, где и реализовать логику сохранения? Если да, то сразу укажу минусы:
                    1. Action нельзя использовать повторно в отличии от Command/Query, которая может быть частью Composite
                    2. Вам все равно придется написать базовый Controller, где будет метод Run/Execute, который откроет Unit of Work или же открывать UoW для каждого (можно открывать для Begin и End request, но это вообще не универсально) Action.
                    На самом деле проблем больше.

                    P.S. проблема в том, что вы пытаетесь осмыслить MVD ( и Incoding CQRS ), но опираетесь на N-Layer подход, что в корне другое (в статье про CQRS я привел сравнение N-layer vs CQRS)
                    • +1
                      Тем, что код Command Вам все равно надо написать, а без Action можно обойтись

                      Только в вашей конкретной реализации CQRS.

                      Current это значит текущий и у каждого потока он будет свой. Все это есть в asp.net mvc

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

                      Напишите другой Query, а общие вынесите в базовый.

                      Повторное использование через наследование? Плохая идея.

                      Action нельзя использовать повторно в отличии от Command/Query, которая может быть частью Composite

                      А надо? Ну и да, традиционный способ решения этой проблемы — это вынесение прикладного сервиса.

                      Вам все равно придется написать базовый Controller, где будет метод Run/Execute, который откроет Unit of Work или же открывать UoW для каждого (можно открывать для Begin и End request, но это вообще не универсально) Action.

                      Эээ? Это еще зачем? У меня UoW может быть один на несколько действий, а может не быть вообще. Ну и да, снова говорим о том, что это прекрасно решается прикладными сервисами.

                      но опираетесь на N-Layer подход, что в корне другое (в статье про CQRS я привел сравнение N-layer vs CQRS)

                      Не-а. CQRS не противоречит n-layer.
                      • 0
                        Только в вашей конкретной реализации CQRS.

                        Да, в этом и смысл MVD.

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

                        По этому, мы делаем ISessionContext, который потом подменяем для тестов.
                         const string userid = "userId";
                         var sessionContext = Pleasure.MockAsObject<ISessionContext>(s=> s.SetupGet(r => r.UserId)
                                                                                          .Returns(userid));
                         IoCFactory.Instance.StubTryResolve(sessionContext);
                        


                        Повторное использование через наследование? Плохая идея.

                        IF это тоже не венец идеального кода

                        А надо? Ну и да, традиционный способ решения этой проблемы — это вынесение прикладного сервиса.

                        Лет 7 назад, можно было сказать, что asp это традиционный способ разработки веб сайтов для MS

                        Не-а. CQRS не противоречит n-layer.

                        То есть, Вы пишите UserService.Add в котором вызываете new AddUserCommand() ??
                        • +1
                          По этому, мы делаем ISessionContext, который потом подменяем для тестов.

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

                          Лет 7 назад, можно было сказать, что asp это традиционный способ разработки веб сайтов для MS

                          Нельзя. Лет семь назад asp.net было уже лет пять.

                          То есть, Вы пишите UserService.Add в котором вызываете new AddUserCommand() ??

                          Нет, я кидаю AddUserCommand, которая (может) обрабатываться в слое, отличном от того, который ее кинул.
                          • 0
                            … и следите за тем, чтобы каждый тест корректно регистрировал и разрегистрировал статические контекста, ага. Спасибо, я с тех пор как потратил пару часов на отладку десятка тестов, который падал в произвольном порядке именно из-за статических контекстов, больше стараюсь их не видеть никогда.

                            Многие framework поддерживают глобальные SetUp и TearDown ( в частности MSpec ), так что это не проблема.

                            Нет, я кидаю AddUserCommand, которая (может) обрабатываться в слое, отличном от того, который ее кинул.

                            А как же Unit Of Work, кто его открывает? Я не совсем понимаю, как вы работаете без Dispatcher. Потом вопрос атомарности Command, если они «шарятся» по разным слоям, то это тоже не совсем ясно.

                            P.S. Вы постоянно ссылаетесь на «какие» то слои, хотя чем их меньше, тем и лучше.
                            • +1
                              Многие framework поддерживают глобальные SetUp и TearDown ( в частности MSpec ), так что это не проблема.

                              Я не знаю ни одного фреймворка, который бы поддерживал глобальный teardown, который бы применялся после каждого теста (а глобальные контексты надо зачищать именно так). Обычно есть setup/teardown уровня теста (будут выполняться для каждого теста в сьюте), уровня сьюта (будут выполнены один раз на сьют) и (иногда) уровня пакета/сборки/проекта.

                              А как же Unit Of Work, кто его открывает?

                              Тот, кому он нужен. Если он нужен.

                              Потом вопрос атомарности Command, если они «шарятся» по разным слоям, то это тоже не совсем ясно.

                              За атомарность команды отвечает тот, кто ее выполняет, а он находится в конкретном слое.

                              Вы постоянно ссылаетесь на «какие» то слои, хотя чем их меньше, тем и лучше.

                              Вы можете аргументировать эту максиму?
                              • 0
                                Я не знаю ни одного фреймворка, который бы поддерживал глобальный teardown,

                                К примеру MSpec

                                Тот, кому он нужен. Если он нужен.

                                Он всегда нужен, только если у Вас Command не работает с базой, а это крайне редко. Открывать его постоянно через using(new UnitOfWork()) { // some code } это повышает copy & paste

                                Вы можете аргументировать эту максиму?

                                Ответ кроется в Вашем замечание «За атомарность команды отвечает тот, кто ее выполняет, а он находится в конкретном слое», зачем все эти слои, если можно убрать всех посредников и оставить Dispatcher и Command/Query. Давайте пройдемся по плюсам:
                                1. Проще навигация, потому что Command/Query это атомарная задача, которая имеет логическое имя.
                                2. Unit Test не превращается в «зоопарк» из Mock IUserFacade, потом IUserService, потом IUserRepository и т.д.
                                3. Появляется возможность использовать MVD и ещё уменьшить кол-во кода.

                                • +1
                                  К примеру MSpec

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

                                  Он всегда нужен, только если у Вас Command не работает с базой

                                  Вы работы с базой без UoW не представляете? Про неявные UoW вы не слышали? Про то, что UoW может быть ограничен внутри слоя работы с данными — тоже?

                                  зачем все эти слои, если можно убрать всех посредников и оставить Dispatcher и Command/Query

                                  Зачем все эти диспетчеры и команды, если можно напрямую с БД работать?

                                  Проще навигация, потому что Command/Query это атомарная задача, которая имеет логическое имя.

                                  Это не зависит от количества слоев.

                                  Unit Test не превращается в «зоопарк» из Mock IUserFacade, потом IUserService, потом IUserRepository и т.д.

                                  Каждый юнит-тест тестирует ровно один слой, поэтому никакого зоопарка там нет.

                                  Появляется возможность использовать MVD и ещё уменьшить кол-во кода.

                                  Уменьшение количества кода — не догма.

                                  А вообще слои нужны для одной простой вещи — управления сложностью через инкапсуляцию. Создав «слой» работы с БД, я больше не думаю о самой БД, я от нее абстрагирован. Создав «слой» работы с бизнесом, я больше не думаю про то, как бизнес работает с БД, я абстрагирован и от этого. Information hiding в чистом виде.
                                  • 0
                                    Каждый юнит-тест тестирует ровно один слой, поэтому никакого зоопарка там нет.

                                    Я это и имел ввиду, что надо написать 10 тестов, что бы покрыть один сценарий из-за того, что надо все куда то пробрасывать.

                                    Уменьшение количества кода — не догма.

                                    Это одна из задач

                                    А вообще слои нужны для одной простой вещи — управления сложностью через инкапсуляцию. Создав «слой» работы с БД, я больше не думаю о самой БД, я от нее абстрагирован. Создав «слой» работы с бизнесом, я больше не думаю про то, как бизнес работает с БД, я абстрагирован и от этого. Information hiding в чистом виде.

                                    Если бы его создал «кто то», может это и имело смысл, а так сами создали, а потом больше о нем не думаете, но так не бывает.

                                    Вы работы с базой без UoW не представляете? Про неявные UoW вы не слышали? Про то, что UoW может быть ограничен внутри слоя работы с данными — тоже?

                                    То есть Вы считаете, что каждый слой сам решает, когда и зачем ему открывать это очень однотипное и прозрачное решение?

                                    На счет Unit of work, как Вам такая транзакция
                                    dispatcher.Push(composite =>
                                                            {
                                                                composite.Quote(step1);
                                                                composite.Quote(step2);
                                                                composite.Quote(step3);
                                                            });
                                    

                                    Какой слой, что будет делегировать?

                                    Вы работы с базой без UoW не представляете?

                                    Можно без, только для Query в ReadUncommited, а иначе как гарантировать успешность завершенных действий?

                                    P.S. Разве плохо оперировать AddUserCommand, ApproveChangeStatusCommand, GetPatientBySearch и не думать о какой слой и зачем?
                                    • +1
                                      Я это и имел ввиду, что надо написать 10 тестов, что бы покрыть один сценарий из-за того, что надо все куда то пробрасывать.

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

                                      Если бы его создал «кто то», может это и имело смысл, а так сами создали, а потом больше о нем не думаете, но так не бывает.

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

                                      То есть Вы считаете, что каждый слой сам решает, когда и зачем ему открывать это очень однотипное и прозрачное решение?

                                      Не слой, а обработчик. Да, я считаю, что определение UoW по конкретному сценарию — это правильно.

                                      Какой слой, что будет делегировать?

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

                                      Можно без, только для Query в ReadUncommited, а иначе как гарантировать успешность завершенных действий?

                                      Атомарными операциями, например.

                                      Разве плохо оперировать AddUserCommand, ApproveChangeStatusCommand, GetPatientBySearch и не думать о какой слой и зачем?

                                      Хорошо, только так не бывает. Команда не выполняется магически и сама собой, она будет выполнена где-то и кем-то, и это нужно учитывать (в том числе и при их композиции).
                                      • 0
                                        Зато вы имеете точную локализацию ошибок. Можно ведь не писать десять тестов, а написать один компонентный, просто локализация будет хуже.

                                        Я думаю у нас разный взгляд на код, потому что я все время старался минимизировать количество действий, что бы выполнить задачу, поэтапно убирая слои (или объединяя), так что мой рефактор чаще направлен на уменьшение кода.

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

                                        Вот, за это отвечает ТОЛЬКО dispatcher

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

                                        А когда Unit of work не нужен и сколько таких?
                                        Я помню временна, когда делал
                                        public void Edit(Guid id, UserInput input)
                                        {
                                                    using (IUnitOfWork unitOfWork = CreateUnitOfWork())
                                                    {
                                                        User user = this._userRepository.Find(id);             
                                                        user.SetType(input.Type);
                                                        unitOfWork.Commit();
                                                    }
                                        }
                                        

                                        Вы такой способ используете?

                                        В команде бывает больше одного разработчика вообще-то. И разделение ответственности по слоям очень неплохо работает

                                        CQRS прекрасно разделяет и позволяет планировать задачи на основе Command/Query. По поводу разделения, я так понимаю беседа будет «Мне нужно доделать User Service, скоро там будет Data Facade завершен ?».
                                        Может Вы имеет «слои», как части приложения, то есть Admin, Private Office и т.д, тогда да, но не иначе, потому что разработчик все равно должен написать каждый слой, так что тоже самое, но больше писать.

                                        Ну и да, я сам тоже прекрасно абстрагируюсь — если я (для конкретного проекта) написал data facade месяц назад, то сейчас мне все равно, как он работает, главное, что он работает — а дальше я просто проверяю правильно делегирования ему вызовов.

                                        О чем я и говорю, что бы понять, как работает код, который решает задачу, нужно пробежать 10 слоев, которые от части занимаются делегированием и добавляют по 2 — 3 уникальные строчки, а потом на все это ещё и 10 тестов написать.
                                        А вот если выделить все уникальные строчки и убрать дубляж, то будет в самый раз на Command (можно парочку в Composite)
                                        • 0
                                          я все время старался минимизировать количество действий, что бы выполнить задачу, поэтапно убирая слои (или объединяя)

                                          Объединение слоев часто означет нарушение принципа разделения ответственностей. Проще говоря, спагетти-код.

                                          Вот, за это отвечает ТОЛЬКО dispatcher

                                          В CQRS за это отвечает шина и обработчик команды, если быть точным.

                                          Вы такой способ используете?
                                          Нет.

                                          По поводу разделения, я так понимаю беседа будет «Мне нужно доделать User Service, скоро там будет Data Facade завершен ?».

                                          При неправильном планировании и отсутствии shared code ownership — да.

                                          Может Вы имеет «слои», как части приложения, то есть Admin, Private Office и т.д, тогда да, но не иначе, потому что разработчик все равно должен написать каждый слой, так что тоже самое, но больше писать.

                                          Не очень понял, что вы хотите сказать.

                                          что бы понять, как работает код, который решает задачу, нужно пробежать 10 слоев,

                                          Значит, у вас неправильно декомпонована задача.
                • 0
                  Быстрый go to declaration сразу из view. Работает и в «обычном» MVC.
                  Вы можете перейти в Action, но не в Query, опять тот же пример с AddUser

                  MVC
                  Url.Action("Add","Controller")
                  

                  примечание: go to delcaration попадет в ACTION Add, который просто делает Push AddUserCommand, что заставит Вас далее перейти в AddUserCommand

                  MVD
                  Url.Dispatcher().Push(new AddUserCommand())
                  

                  примечание: go to delcaration попадет в AddUserCommand, а то, как делается Push не важно, потому что он один для всех.
                  • +1
                    go to delcaration попадет в ACTION Add, который просто делает Push AddUserCommand

                    Если он просто делает Push. А если нет?

                    Понимаете, вы исходите из того, что контроллеры (всегда!) наивны. А это не так.
                    • 0
                      Понимаете, вы исходите из того, что контроллеры (всегда!) наивны. А это не так.

                      Все, что может Action можно сделать в Command, по этому Command == Action.
                      Давай те список не «наивных» action:
                      1.Получить Cookies, Request и т.д. — это можно или напрямую HttpContext.Current (это не глобальный, а текущий) или передать из View, к примеру Cookies, Url и многое другое доступно через Incoding Selector
                      Url.Dispatcher().Push(new AddUserCommand()
                                                                {
                                                                        AdrTypePrimary =  Selector.Incoding.Cookie(Key)
                                                                })
                      

                      2. Получить доступ к IEmailSender, IReport, ITwitterSdk и т.д
                      Пример работы с Amazon (исходники и тест)
                          public class GetProductByAmazonQuery : QueryBase<List<ProductAmazonVm>>
                          {
                              public string StoreId { get; set; }
                              public string Title { get; set; }
                              public int Page { get; set; }
                      
                              protected override List<ProductAmazonVm> ExecuteResult()
                              {
                                  var amazonService = IoCFactory.Instance.TryResolve<IAmazonService>();
                                  var store = Repository.GetById<Store>(StoreId);
                      
                                  var items = amazonService.Fetch(Title, Page, store.CategoryAsAmazon);
                                  return items
                                          .Select((r, i) => new ProductAmazonVm(r)
                                                                {
                                 Exist = Repository.Query(whereSpecification: new ProductByStoreWhereSpec(StoreId)
                                                .And(new ProductByASINWhereSpec(r.ASIN)))
                                                 .Any(), 
                                 IsLast = items.Count - 1 == i  })
                                          .ToList();
                              }
                          }
                      


                      Если есть, что дополнить, то давайте обсудим.

                      • +1
                        напрямую HttpContext.Current (это не глобальный, а текущий)

                        Это привязка к конкретному контексту выполнения — прощай, универсальность, ради которой вы все затевали.

                        Получить доступ к IEmailSender, IReport, ITwitterSdk и т.д

                        Я так понимаю, это в контексте соседнего треда про IoC? Так посмотрите в свой же код, вы напрямую делаете IoCFactory.Instance.TryResolve, что явно указывает на то, что у вас реализован сервис-локатор (со всеми его недостатками).
                        • 0
                          что явно указывает на то, что у вас реализован сервис-локатор (со всеми его недостатками)..

                          А как без TryResolve? В любом случаи где то хоть один раз это будет вызвано, к примеру ControllberBuilder и потом инъекция в ctor (того же Controller). Ctor это тот же TryResolve.

                          P.S. На самом деле у нас не дискуссия, а просто каждый доказывает свою точку зрения, которую удачно применяет в проектах, но мы ушли от темы MVD и переключились на все аспекты сразу, что как мне кажется в комментариях не реально обсудить. Если у Вас есть желание, то можно по скайпу спокойно разобрать каждый момент и тем самым прийти к консенсусу
                          • +1
                            А как без TryResolve?

                            Не «без TryResolve», а «без сервис-локатора». Через DI, примеров море.

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

                            Лично я обсуждаю достоинства и недостатки вашего конкретного проекта. Сервис-локатор и невозможность использовать DI — (для меня) фундаментальный и критический недостаток.
                            • 0
                              Лично я обсуждаю достоинства и недостатки вашего конкретного проекта. Сервис-локатор и невозможность использовать DI — (для меня) фундаментальный и критический недостаток.

                              А почему нельзя использовать DI, если Structure map это DI framework? Напишите, что конкретно Вы не можете реализовать в рамках той инфраструктуры, что я предлагаю?

                              Для меня Ioc/Di в целом очень близкие вещи, я знаю что один это частный случай другого, но я придерживаюсь мнения, что шаблоны (паттерны) это советы и не обязательно всегда придерживаться строго описания.
                              Что касается Service Locator, то я так понимаю у Вас проблема с тестами для Singleton объектов, потому что Вы не очищаете context перед каждым unit test верно? или что то ещё…? Cвою позицию о ctor vs IoCFactory.Instance я где то уже написал, так что буду рад услышать Ваш вариант.
                              • +1
                                А почему нельзя использовать DI, если Structure map это DI framework?

                                Потому что ваши «команды» построены на SL, а смешивать эти два паттерна не надо.

                                или что то ещё…?

                                Стоимость сетапа (и отслеживания некорректного сетапа) для сервис-локаторов намного выше.

                                Вы статью Симана прочитали?
                                • 0
                                  Вы статью Симана прочитали?

                                  Для того, что бы быть уверенным, что все зависимости зарегистрированы, можно:
                                  1. Многие IoC framework позволяют использовать Conventions, что позволяет явно не указывать For<TInterface>.Use<TImp>() если он один к одному ( для других случаев Named )
                                  2. Написать тест, который будет прогонять все Interface на предмет получения TryResolve.
                                  примечание: если у Вас Named по Enum, то можно Enum.GetValues(typeof(TEnum)) и потом IoCFactory.Instance.TryResolve<T>(enumValue).ShouldNotBeNull()

                                  • 0
                                    Вот и начались обходные маневры. При этом второй подход мы пробовали, и он порочен — далеко не все зависимости можно так отловить. Намного дешевле проверять разрешимость всех входных точек приложения (при constructor injection это покрывает практически все ошибки зависимостей, за исключением сложно вложенных фабрик).
        • +1
          (залез поглубже в исходники)

          var eventBroker = IoCFactory.Instance.TryResolve<IEventBroker>();
          

          Сервис-локатор, srsly?

          Более того, сервис-локатор без возможности подмены:

          public class IoCFactory : FactoryBase<IoCInit>
           {
                 static readonly Lazy<IoCFactory> instance = new Lazy<IoCFactory>(() => new IoCFactory());
                 public static IoCFactory Instance { get { return instance.Value; } }
          }
          

          Вы Симана читали? В код WebAPI смотрели?
          • 0
            Более того, сервис-локатор без возможности подмены:

            Подменять можно Provider, а не сам Instance
            IoCFactory.Instance.Initialize(init => init.WithProvider(new StructureMapIoCProvider(new WebIoCInit()))); 
            


            Вы Симана читали? В код WebAPI смотрели?

            Может больше конкретики?
            • +1
              Подменять можно Provider, а не сам Instance

              Зачем так сложно? Мне казалось, вы ратуете за отсутствие лишних абстракций, а тут ввели на пустом месте. Классическая реализация ServiceLocator выглядит так (с сохранением вашей семантики:

              ServiceLocator.SetCurrent(new StructureMapServiceLocator(new WebIoCInit()));
              


              Может больше конкретики?

              (ну вообще, это очень конкретные вопросы, с простым ответом).

              Service Locator is an Anti-Pattern

              А в WebAPI (в отличие от MVC, кстати, где использован как раз ServiceLocator, и это… больно) сделано максимально, в моем понимании, корректно — dependency resolver доступен у глобальной конфигурации (HttpConfiguration) и у конкретного запроса (через RequestContext), причем если я правильно помню, то второй автоматически ограничен по самому запросу, что упрощает работу с disposable-зависимостями. Ну и понятное дело, работают все типы DI, которые поддерживаются выбранным IoC-контейнером, поэтому обращение к dependency resolver за пределами инфраструктуры практически не нужно (я, по крайней мере, не сталкивался с такой необходимостью).

              А у вас команды создаются в представлении (Url.Dispatcher().Push(new Command())), а доступ к инфраструктуре получают через глобальный локатор (например, DeleteEntityByIdCommand<TEntity> получает репозиторий через IoCFactory.Instance.TryResolve<IRepository>() — очень, кстати, много говорит о вашем коде то, сколько усилий нужно приложить, чтобы найти эту информацию, — вместо того, чтобы объявить параметр конструктора или вбрасываемое свойство IRepository<TEntity> Repository), что затрудняет и рефакторинг (попробуйте найти все команды, использующие конкретный бизнес-сервис), и тестирование (попробуйте угадать, какие зависимости нужны конкретной команде для тестирования). Впрочем, я повторяюсь, у Симана это все описано.
              • 0
                IoCFactory.Instance.TryResolve()

                Это используется только в рамках метода Push, который находится в DefaultDispatcher, для Command/Query Repository доступен и unit of work открыт.

                очень, кстати, много говорит о вашем коде то, сколько усилий нужно приложить, чтобы найти эту информацию, — вместо того, чтобы объявить параметр конструктора или вбрасываемое свойство IRepository(TEntity) Repository), что затрудняет и рефакторинг (попробуйте найти все команды, использующие конкретный бизнес-сервис), и тестирование (попробуйте угадать, какие зависимости нужны конкретной команде для тестирования).


                Не надо ни каких TryResolve или зависимостей в ctor. Перед тем, как строить догадки надо было посмотреть, как работает. Примеры на GitHub (а так же в начале статьи ссылки на Incoding CQRS), работы Command/Query.

                Service Locator is an Anti-Pattern

                Мы используем IoC для того, что бы подменять реализации IRepository, IUnitOfWork, IDispatcher, ITemplateFactory и т.д. (пример)

                попробуйте найти все команды, использующие конкретный бизнес-сервис)

                Вы описываете больше N-layer нежели CQRS, то есть я так понимаю в своей практике вы используете Service?

                тестирование (попробуйте угадать, какие зависимости нужны конкретной команде для тестирования)

                Как я говорил выше, ни каких зависимостей не надо, все уже доступно сразу и для тестирования у нас целый набор готовых сценариев ( command/query и примеры кода из Inc-music-store)
                примечание: про тесты пока освещенное не много (пара статей в блоге), но скоро будет больше.

                P.S. Inc Music store реализован на старой (сейчас ещё лучше и проще) версии framework, но отражает общую картину.
                • +1
                  Это используется только в рамках метода Push, который находится в DefaultDispatcher,

                  Не надо ни каких TryResolve или зависимостей в ctor.

                  А как бизнес-то реализовывать? Или у вас команды ни от чего, кроме репозитория, зависеть не могут? Вот хотите вы в рамках создания пользователя проверить валидность емейла через специальный внешний сервис — как вы это будете делать?

                  для Command/Query Repository доступен и unit of work открыт.

                  Так внутри-то все равно сервис-локатор.

                  Мы используем IoC для того, что бы подменять реализации IRepository, IUnitOfWork, IDispatcher, ITemplateFactory и т.д.

                  Это как-то делает service locator менее антипаттерном?

                  ни каких зависимостей не надо

                  Так не бывает.

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

                  Как вы будете это реализовывать?

                  PS Я уже писал, что ваши тесты весьма слабочитаемы и специфичны для вашего же фреймворка, поэтому давать на них ссылку без пояснений достаточно бессмысленно.
                  • 0
                    Давайте на простом примере. Вот есть сценарий регистрации пользователя: получили емейл и пароль пользователя, приложение должно проверить, что емейл корректен (для этого есть внешний сервис, выраженный в виде пары интерфейс/реализация), дальше сохранить данные в БД и выслать пользователю по email уведомление, что эккаунт создан (рассылка емейлов, конечно же, тоже реализована через сервис с интерфейсом и реализацией). Для простоты считаем, что это веб-сервис, поэтому интерфейс нас не интересует, мы просто получили два строковых значения, и бросили эксепшн, если что-то не так.


                    Именно этот пример я рассматривал в статье про CQRS (хотя в последний версии мы уходим от Event Broker в строну внутреннего Dispatcher, но много не изменится)
                    • 0
                      Там нет взаимодействия с внешними сервисами, о котором сейчас идет речь.
                      • 0
                        Там нет взаимодействия с внешними сервисами, о котором сейчас идет речь.

                        Вот пример Query, который работает с amazon

                         public class GetProductByAmazonQuery : QueryBase<List<ProductAmazonVm>>
                         {
                            public string StoreId { get; set; }
                            public string Title { get; set; }
                            public int Page { get; set; }
                        
                            protected override List<ProductAmazonVm> ExecuteResult()
                            {
                                    var amazonService = IoCFactory.Instance.TryResolve<IAmazonService>();
                                    var store = Repository.GetById<Store>(StoreId);
                        
                                    var items = amazonService.Fetch(Title, Page, store.CategoryAsAmazon);
                                    return items
                                            .Select((r, i) => new ProductAmazonVm(r)
                                                                  {
                                   Exist = Repository.Query(whereSpecification: new ProductByStoreWhereSpec(StoreId)
                                                  .And(new ProductByASINWhereSpec(r.ASIN)))
                                                   .Any(), 
                                   IsLast = items.Count - 1 == i  })
                                            .ToList();
                            }
                         }
                        
                        • +1
                          Угу.

                          ни каких зависимостей не надо,

                          IoCFactory.Instance.TryResolve()

                          Одно противоречит другому, уж простите.

                          Как по внешнему интерфейсу команды GetProductByAmazonQuery понять, что она зависит от IAmazonService?
                          • 0
                            Одно противоречит другому, уж простите.

                            Вы приводили пример с IRepository и я написал, что это не нужно.
                            • 0
                              Я писал про зависимости в общем как раз, это вы почему-то ограничились репозиторием.
                    • 0
                      поэтому давать на них ссылку без пояснений достаточно бессмысленно.

                      я указал ссылку, где детально разбираю сценарий тестирования Command и Query
                      • 0
                        Необходимость писать собственные (весьма непрозрачные) врапперы для тестировани собственных же компонентов — плохой признак.

                        Ну и да, если вы думаете, что ваш сценарий сильно понятнее — вы ошибаетесь. Слишком много неаккуратности и ошибок (вплоть до некорректно отформатированного кода).
                      • 0
                        Это как-то делает service locator менее антипаттерном?

                        Я слышал, что Repositiory антипатерн и много чего ещё, так что аргумент не убедительный для ухода от IoCFactory. Вы бы лучше привели пример, как реализовать подмену агрегатов (IRepository, IUnitOfWork и т.д. ) framework из вне (не меняя исходники) без Dependnecy Injection.
                        • +1
                          аргумент не убедительный для ухода от IoCFactory

                          Вы статью Симана прочитали? Я специально дал ссылку.

                          Вы бы лучше привели пример, как реализовать подмену агрегатов (IRepository, IUnitOfWork и т.д. ) framework из вне (не меняя исходники) без Dependnecy Injection.

                          Во-первых, repository и UoW — это не агрегаты. А во-вторых, я-то как раз считаю, что это надо реализовывать с Dependency Injection, а вот у вас реализовано без.
                          • 0
                            Во-первых, repository и UoW — это не агрегаты. А во-вторых, я-то как раз считаю, что это надо реализовывать с Dependency Injection, а вот у вас реализовано без.


                            Во первых repository и unit of work я рассматриваю в контексте внутренних инструментов framework, а не понятия «паттерн», так что я все правильно сказал.

                            Во вторых, Structure Map это DI framework, который мы используем в качестве одного из провайдеров, так что Вы бы аргументировали свои слова и объяснили почему же у меня нету DI?
                            • +1
                              Во первых repository и unit of work я рассматриваю в контексте внутренних инструментов framework, а не понятия «паттерн», так что я все правильно сказал.

                              Агрегатами это их никак не делает.

                              Во вторых, Structure Map это DI framework, который мы используем в качестве одного из провайдеров, так что Вы бы аргументировали свои слова и объяснили почему же у меня нету DI?

                              Потому что dependency injection — это вбрасывание зависимости в потребителя снаружи (через через конструктор, свойство или параметр метода). А то, что у вас — это service location, получение потребителем зависимости через обращение к внешнему реестру (в вашем случае — IoCFactory.Current). На примерах показывать надо?

                              Structure Map — это Dependency Injection/Inversion of Control, и вы его используете в качестве второго, а не первого.
                              • 0
                                Потому что dependency injection — это вбрасывание зависимости в потребителя снаружи (через через конструктор, свойство или параметр метода). А то, что у вас — это service location, получение потребителем зависимости через обращение к внешнему реестру (в вашем случае — IoCFactory.Current). На примерах показывать надо?

                                Вам все равно придется вызвать TryResolve в ControllerBuilder или ещё где то. Вы не забывайте, что не всегда можно указывать зависимости в ctor к примеру AbstractValidator ( fluent validation), он не поддерживает параметров или если нужна поддержка сериализация, то приходится делать 2 ctor (пустой и с зависимостями)

                                На самом деле нету ни какой ризницы, как получить Instance через ctor или через singleton container, но ctor занимает больше кода, по этому я выбрал IoCFactory, а почему Вам он так противен ?)
                                • +1
                                  к примеру AbstractValidator ( fluent validation), он не поддерживает параметров

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

                                  . Вы не забывайте, что не всегда можно указывать зависимости в ctor

                                  Ну так есть еще вбрасывание через свойства и методы.

                                  На самом деле нету ни какой ризницы, как получить Instance через ctor или через singleton container,

                                  Есть, и фундаментальная. Явные vs неявные зависимости.

                                  почему Вам он так противен ?)

                                  Я же вам дал ссылку на статью Симана; вы ее прочитали? Там все прекрасно показано, в том числе с примерами.

                                  А лично мне сервис-локатор противен именно тем, что решения на его основе тяжелы в тестировании, и единожды этого наевшись, я больше возвращаться не хочу.
                                  • 0
                                    Я же вам дал ссылку на статью Симана; вы ее прочитали? Там все прекрасно показано, в том числе с примерами.

                                    var order = new Order();
                                    var locator = new Locator();
                                    var sut = new OrderProcessor(locator);
                                    sut.Process(order);
                                    

                                    Зачем передавать locator в ctor, если есть Singleton? Статья показывает, какой то примитивный вариант мелкого Container для хранения dictionary Type,Instance. Если у Вас вопрос о тестах, то я приводил ссылки, как делать глобальный TearDown.
                                    Вот пример, как подменять instance в рамках framework IoCFactory.Instance.StubTryResolve(mock.Object), но по скольку есть оболочки для тестов, где уже все настроено такие ситуации будут редкие.

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

                                    Может Вы не правильно использовали?
                                    Между
                                    public MyClass(IService service)
                                    {
                                    this.service = service;
                                    }
                                    

                                    и
                                    public MyClass()
                                    {
                                    this.service = IocFactory.Instance.TryResolve<IService>();
                                    }
                                    

                                    нету разницы

                                    Кстати, как на счет named, то есть когда Вам надо IocFactory.Instance.TryResolve<T>(Provider.TwitterSdk), Вы тоже откажитесь от упрощения кода ради «мании» не использовать Service Locator?
                                    • 0
                                      Зачем передавать locator в ctor, если есть Singleton?

                                      Для явного контроля за зависимостями.

                                      Статья показывает, какой то примитивный вариант мелкого Container для хранения dictionary Type,Instance.

                                      Вообще-то, все IoC-контейнеры внутри устроены именно так.

                                      Если у Вас вопрос о тестах, то я приводил ссылки, как делать глобальный TearDown.

                                      … про которую я вам сказал, что это так не работает. Это типичный assembly teardown, он не вызывается после каждого теста.

                                      по скольку есть оболочки для тестов, где уже все настроено такие ситуации будут редкие.

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

                                      нету разницы

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

                                      Кстати, как на счет named, то есть когда Вам надо IocFactory.Instance.TryResolve(Provider.TwitterSdk)
                                      Во-первых, все вменяемые DI-контейнеры поддерживают атрибуты для указания нужных именованных зависимостей. А во-вторых именованные зависимости — это design smell, если они вам понадобились, то (за небольшими исключениями) вы делаете что-то странное.
                                      • 0
                                        Нет. Во-первых, оболочки для тестов — это признак того, что код плохо тестируется. Во-вторых, оболочки хорошо пишутся для однородных задач, однако сама природа нашей работы такова, что задачи у нас разнородны.

                                        Если все Command/Query однотипны, почему бы и нет. Как раз оболочка подчеркивает стандарт кода, а у Вас я так понимаю, каждый класс по своему уникальный?

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

                                        Ещё раз повторю в рамках incoding framework Command/Query имеет IRepositry, IUnitOfWork, по этому Вам не надо часто делать TryResolve. Конечно куда лучше, копировать один и тот же ctor с IRepository или у Вас мало command, которые работают с базой?

                                        P.S. Вы бы кстати привели пример бы своего кода, а то мы только мой разбираем.
                                        • +1
                                          Как раз оболочка подчеркивает стандарт кода, а у Вас я так понимаю, каждый класс по своему уникальный?

                                          Как показывает опыт, большая часть задач уникальна, да.

                                          Ещё раз повторю в рамках incoding framework Command/Query имеет IRepositry, IUnitOfWork, по этому Вам не надо часто делать TryResolve.

                                          Дело не в том, что надо часто делать, дело в том, что понятно/непонятно при чтении кода. Вы Макконнела читали?

                                          Конечно куда лучше, копировать один и тот же ctor с IRepository или у Вас мало command, которые работают с базой?

                                          (Если уж совсем точно, команды вообще ни с чем не должны работать, работать должны обработчики.)

                                          Да, лучше копировать конструктор с IRepository, потому что он дает явную видимость зависимостей.

                                          Вы бы кстати привели пример бы своего кода, а то мы только мой разбираем.

                                          Мы разбираем не абстрактно ваш код, а код фреймворка, о котором вы тут пишете статьи. Это очень логично.
                                        • 0
                                          … про которую я вам сказал, что это так не работает. Это типичный assembly teardown, он не вызывается после каждого теста.

                                          Хорошо, делаем базовый класс MyTestWithClean, в котором описываем TearDown и потом используем его, как базовый, НО как я и говорил, по большей части есть несколько элементов, которые надо подменять, а именно IRepository, IEventBroker и IDispatcher (если не использовать MVD) и они все имеют готовые Mock оболочки.
                                          • 0
                                            Хорошо, делаем базовый класс MyTestWithClean, в котором описываем TearDown и потом используем его, как базовый,

                                            Неудобно — слишком разветвленная цепочка наследования получится.
                                            • 0
                                              Неудобно — слишком разветвленная цепочка наследования получится.

                                              Один базовый класс затрудняет Вам работу?
                                              • 0
                                                Да. Любое инфраструктурное навязывание базовых классов затрудняет работу.
                                                • 0
                                                  Да. Любое инфраструктурное навязывание базовых классов затрудняет работу.

                                                  То есть Вы вместо наследования лучше выберите Copy&Paste, который прозрачный и явный? И опять же о слоях, Data facade разве не усложняет инфраструктуру?
                                                  • 0
                                                    То есть Вы вместо наследования лучше выберите Copy&Paste, который прозрачный и явный?

                                                    Зависит от ситуации. Для повторного использования композиция предпочтительнее наследования.

                                                    И опять же о слоях, Data facade разве не усложняет инфраструктуру?

                                                    Нет.
                                    • 0
                                      Уважаемый vkopachinsky, хотя lair несколько категоричен, но в целом я поддерживаю его мнение. Марк Симан в своей книге «Внедерение зависимостей в .NET» довольно подробно описал существующие паттерны и антипаттерны внедрения зависимостей.

                                      Вы используете в своей реализации антипаттерн «Локатор сервисов». Использование этого подхода не всегда является плохой практикой, но в большинстве случаев так и есть.
                                      • 0
                                        Вы используете в своей реализации антипаттерн «Локатор сервисов». Использование этого подхода не всегда является плохой практикой, но в большинстве случаев так и есть.

                                        Хорошо, вот пример кода для решения одной задачи
                                        Server Side
                                        public class AddAlbumCommand : CommandBase
                                            {
                                                public string Title { get; set; }
                                                public string ArtistId { get; set; }
                                                public string GenreId { get; set; }
                                        
                                                public override void Execute()
                                                {
                                                   var artist = Repository.GetById<Artist>(ArtistId);
                                                   var genre = Repository.GetById<Genre>(GenreId);
                                        
                                                   Repository.Save(new Album() { Title = Title, Artist = artist , Genre = genre });
                                                }
                                            }
                                        

                                        View
                                        @(Html.When(JqueryBind.Click)
                                              .AjaxPost(Url.Dispatcher().Push(new AddProductCommand()
                                                                                  {
                                                    Title = "Title",
                                                    GenreId = "70048B8A-5233-4AAB-AA9F-728A8D898323",
                                                    ArtistId = "6E8D11B7-D279-46C6-ADAC-443D455E35A0",
                                                                                  }))
                                              .OnSuccess(dsl => { })
                                              .AsHtmlAttributes()
                                              .ToButton("Save"))
                                        


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

                                        P.S. Если есть возможность упростить и сократить код, но для этого надо использовать «анти-патерн» я за, потому что я преследую продуктивность, а не лабораторные по паттернам пишу.
                                        • 0
                                          Я попробую объяснить, какие проблемы возможны с использованием вашего подхода:

                                          1. Ваш код трудно использовать повторно, так как неясно какие зависимости ему нужны.
                                          Например, если я захочу использовать ваш объект AddAlbumCommand в своем приложении, то из контракта класса мне будет не ясно, что мне еще нужно сделать кроме вызова конструктора.

                                          2. Из первого пункта вытекает второй: класс AddAlbumCommand трудно покрыть тестами, так как он зависит от объекта Repository, который не совсем ясно как подменить.

                                          P.S. Если есть возможность упростить и сократить код, но для этого надо использовать «анти-патерн» я за, потому что я преследую продуктивность, а не лабораторные по паттернам пишу.

                                          Безусловно, ваш код на пару сторочек короче, чем код использующий Constructor/Property Injection, однако у него есть две перечисленные проблемы.
                                          Если в контексте вашего приложения на них можно закрыть глаза, то ваш путь отлично подходит.
                                          Однако при росте приложения, как мне кажется, эти две проблемы дадут о себе знать.
                                          • 0
                                            Ваш код трудно использовать повторно, так как неясно какие зависимости ему нужны.

                                            Это готовое решение для конкретного проекта, то есть логика сохранения Product будет в каждом проекте своя.

                                            Например, если я захочу использовать ваш объект AddAlbumCommand в своем приложении

                                            Incoding framework это CommandBase и Dispatcher, вот что нужно повторно использовать.

                                            Из первого пункта вытекает второй: класс AddAlbumCommand трудно покрыть тестами, так как он зависит от объекта Repository, который не совсем ясно как подменить.

                                            1. Без wrapper
                                            IoCFactory.Instance.StubTryResolve(myRepository.Object)
                                            

                                            2. C wrapper (он покрывает все аспекты теста Command)
                                            MockMessage<YourCommandOrQuery>
                                                            .When(instance)
                                                            .StubGetById(instance.ArtistId,artist)
                                                            .StubGetById(instance.GenreId,genre) // establish
                                            
                                            should be save => mock.ShouldBeSave<Product>(r=>r.ShouldEqualWeak(mock.Original))
                                            

                                            примечание: mock.Original это instance Command/Query

                                            Все сценарии одинаковы, все равно будет Repository (с ним же Unit Of Work ), но конечно может быть дополнительно разные Send email, Get Amazon Product и т.д., но основной костяк, который повторяется из раз в раз это работа с БД и для этого у нас много чего проработано

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

                                            А от увеличения кол-во кода из-за ограничений на ctor? Для Command/Query нужно иметь публичный ctor без параметров, потому что MVC binding делает десераилизацию, так что ради DI создавать ещё промежуточный класс, который использовать, как транспорт из Web form?.. Я думаю не стоит.
                                            • 0
                                              Это готовое решение для конкретного проекта, то есть логика сохранения Product будет в каждом проекте своя.

                                              Правильно ли я вас понял, что в данном конкретном случае, вас не заботит re-usability класса AddAlbumCommand?

                                              Если это так, то всё ОК и я с вами согласен с вашим подходом.
                                              • 0
                                                вас не заботит re-usability класса AddAlbumCommand?

                                                В другом проект Вы все равно не сможете использовать Add Album, потому что это будет другая логика (поля, условия, workflow и т.д.). Во многих проектах есть добавление альбомов, но у каждого со своими особенностями!

                                                Повторно использовать (для целей одного проекта) Add Album в рамках Composite или Background service (инициализации тестовых данных при старте приложения или выполнение по расписанию), ни каких проблем.

                                                Ещё варианты:

                                                1.Если Вы о том, что бы класс Add Album использовать в рамках скажем мобильного приложения, то я думаю логичней будет использовать его через Controller ( в моем случаи MVD) в рамках API.

                                                2.Если о том, что бы в Console Application, то тоже ни каких проблема, потому что dispatcher не имеет зависимостей от Web, так что просто делаете (настроив IoCFactory всеми нужными зависимостями) Push любой Command или Query.
                                                примечание: на самом деле случай не частый и усложнять проект только опираясь на предугадывание расширения проекта, не самый лучший способ. Вы, как любитель паттернов, я думаю знаете YAGNI» ( You Ain't Gonna Need It )

                                                Бывает иногда надо передать значение, а иногда взять из Context, то
                                                Repository.GetById(UserId ?? App.Current.UserId)
                                                

                                                Я привел пример из Command/Query, если значение не указывать то берем из текущей сессии (для подмены IocFactory.Instance.StubTryResolve(mockSession.Object) или же передаем явно.

                                                • 0
                                                  Правильно ли я вас понял, что в данном конкретном случае, вас не заботит re-usability класса AddAlbumCommand?

                                                  Обратите внимание, что в CommandBase и Dispatcher, которые предназначены для повторного использования, тоже service locator.
                                                  • 0
                                                    тоже service locator.

                                                    А чем это плохо?

                                                    Вы при старте приложение конфигурируете IDispatcher, IRepository (Nhibernate, EF, RavenDb, MongoDb), а так же ITemplateSyntax, IDataBase и т.д., я к тому, что Вы это Service locator и видеть не будете, Вам надо dipsatcher.Push/Query и ни каких слоев ( Data ,Facade, Service )

                                                    • 0
                                                      А чем это плохо?

                                                      Вам уже объяснили, и ссылку на статью дали. Повторяться не буду.

                                                      Вы при старте приложение конфигурируете IDispatcher, IRepository

                                                      Откуда я узнаю, что именно надо конфигурировать?

                                                      Вы это Service locator и видеть не будете

                                                      Мы уже выяснили, что это не так — первая же практическая реализация мгновенно вылетает за рамки того, что вы предусмотрели.
                                                      • 0
                                                        Откуда я узнаю, что именно надо конфигурировать?

                                                        Есть Get Started. Сейчас через nuget все устанавливается, но можно посмотреть больше примеров ( обычный и multiple ORM )

                                                        P.S. Кол-во документации и статье (у мнея есть блог о framework ) увеличивается
                                                        • +1
                                                          Документация? Нет, спасибо. Я предпочитаю код, для которого нужен минимум документации (а лучше — вообще обойтись без нее).
                                                        • 0
                                                          Мы уже выяснили, что это не так — первая же практическая реализация мгновенно вылетает за рамки того, что вы предусмотрели.

                                                          Ок, если Вам надо получить сторонний сервис, то мы его выделяем в Interface и потом используем. Давай примере именованных Try Resolve.

                                                          Задача: отправлять сообщения в разные соц сети
                                                          Решение:
                                                          IoC Initiailize
                                                          for<ICommunicationMessage>().Use(()=>new TwitterCommunication(app,key))
                                                                                      .Named(ProviderOfType.Twitter) 
                                                          for<ICommunicationMessage>().Use(()=>new FacebookCommunication(app,key))
                                                                                      .Named(ProviderOfType.Facebook)
                                                          

                                                          Send Message Command
                                                          pubic class SendMessageCommand:CommandBase
                                                          {
                                                             public ProviderOfType Type {get;set;}
                                                             public String Message {get;set;}
                                                          
                                                             public override Execute()
                                                             {
                                                               IoCFactory.Instance.TryResolveByNamed(Type.ToString()).Send(Message)
                                                             }
                                                          }
                                                          


                                                          Разве плохо выглядит? Для теста есть IocFactory.Instance.StubTryResolveByNamed(type,mock.Object)

                                                          Как будет вариант на DI?
                                                          • 0
                                                            Сервис, отправляющий сообщения в произвольные соцсети, выглядит с использованием DI приблизительно так (обработка ошибок, естественно, опущена):

                                                            class SocialNetworkGateway
                                                            {
                                                                 private readonly IDictionary<Guid,ISocialNetwork> _networks;
                                                            
                                                                 public SocialNetworkGateway(ISocialNetwork[] networks)
                                                                 {
                                                                     _networks = networks.ToDictionary(n => n.Id);
                                                                 }
                                                            
                                                                 public void Send(Guid networkId, string message)
                                                                 {
                                                                     _networks[networkId].Send(message);
                                                                 }
                                                            }
                                                            


                                                            Регистрация ничем не отличается от вашей.

                                                            (на самом деле, технически ничего не мешает получать сразу словарь, просто используемый мной Unity из коробки это не поддерживает, а писать расширение или описывать, как это делается через регистрацию, мне лень)
                                                            • 0
                                                              Регистрация ничем не отличается от вашей.

                                                              Вам надо добавить ещё один class (аля Factory или Gateway), но мое решение обходится без написания дополнительного кода ( и теста к нему)

                                                              Мои выводы о DI
                                                              1. Все объекты с ctor — проблема серилизации ( fluent validation не поддерживает конструкторов, но это шикарное решение для валидации )
                                                              2. Дубляж однотипных ctor — пример тому IRepository, который есть почти в каждой Command/Query
                                                              3. Вместо named нужна промежуточная сущность Factory (Gateway)

                                                              Если бы Service Locator убирал бы типизацию или как то скрывал ошибки, то я может бы и понял проблему, НО из-за псевдо прозрачности (как будто IoCFactor.Instance.TryResovle/StubTryResolve не прозрачно) приходится всегда усложнять и увеличивать код!
                                                              • 0
                                                                Вам надо добавить ещё один class (аля Factory или Gateway)

                                                                Зачем? Класс, который я написал, заменяет функциональность вашей команды, а не дополняет ее.

                                                                Все объекты с ctor — проблема серилизации

                                                                Просто разделяйте DTO и Actors, и никаких проблем с сериализацией не будет. Вы думаете, сообщения на пустом месте придумали?

                                                                Вместо named нужна промежуточная сущность Factory (Gateway)

                                                                Этот вывод ошибочен.

                                                                Если бы Service Locator убирал бы типизацию или как то скрывал ошибки, то я может бы и понял проблему

                                                                Сервис-локатор скрывает (вплоть до рантайма) ошибку регистрации зависимости. Если нужная классу зависимость не зарегистрирована, то мы об этом не узнаем вплоть до обращения к сервис-локатору (когда уже поздно).
                                                                • 0
                                                                  Зачем? Класс, который я написал, заменяет функциональность вашей команды, а не дополняет ее.

                                                                  А вызывать Вы откуда будете Gateway, не из Command? Отправка сообщения это только часть Command, надо же записать историю в базу или это будет в Gateway?

                                                                  Просто разделяйте DTO и Actors, и никаких проблем с сериализацией не будет. Вы думаете, сообщения на пустом месте придумали?

                                                                  Вы делаете сайт, зачем Вам все эти усложнения? Сейчас вместо DTO используется Rest API, так что временна SOAP (proxy классы) прошли.

                                                                  Этот вывод ошибочен.

                                                                  Выше об этом

                                                                  Сервис-локатор скрывает (вплоть до рантайма) ошибку регистрации зависимости. Если нужная классу зависимость не зарегистрирована, то мы об этом не узнаем вплоть до обращения к сервис-локатору (когда уже поздно).

                                                                  А ctor? Какая разница если Вы забыли зарегистрировать зависимости, то ошибка тоже будет.
                                                                  • 0
                                                                    А вызывать Вы откуда будете Gateway, не из Command?

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

                                                                    Вы делаете сайт, зачем Вам все эти усложнения?

                                                                    Я не делаю сайтов. Я делаю прикладные решения, в состав которых входит много всего, в том числе и веб-приложения. И мне все эти «усложнения» очень пригождаются.

                                                                    Сейчас вместо DTO используется Rest API, так что временна SOAP (proxy классы) прошли.

                                                                    У вас путаница в голове. REST API не может использоваться вместо DTO, REST API основан на DTO (почитайте тот же Service Stack). Для SOAP никакие прокси-классы не нужны, прокси-классы — это атрибут удобной реализации сервисной RPC-модели. И, наконец, про то, что времена SOAP прошли, расскажите корпоративной интеграции, построенной на этом самом SOAP.

                                                                    А ctor? Какая разница если Вы забыли зарегистрировать зависимости, то ошибка тоже будет.

                                                                    Она будет в момент создания, а не в момент выполнения. Это позволяет весьма эффективно тестировать регистрацию.
                                                                    • 0
                                                                      Просто мысленно поместите этот класс вместо вашей команды (которая не делает ничего кроме отправки сообщения).

                                                                      Очень удобно плодить тысячи терминов, вместо использования логических имен для Command/Query.
                                                                      SocialNetworkGateway vs SendMessageCommand — Что понятней? CQRS позволяет уйти от выдумывания имен (Task, Service, Gateway, Factory, Runner и т.д.) в сторону «говорящих» названий по которым проще перемещаться через навигацию.

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

                                                                      У Вас есть Controller, вы добавили ему инъекцию в ctor IUserService, если не будет зарегистрирован IUserService, то будет exception в runtime!!!

                                                                      • 0
                                                                        CQRS позволяет уйти от выдумывания имен (Task, Service, Gateway, Factory, Runner и т.д.) в сторону «говорящих» названий по которым проще перемещаться через навигацию.

                                                                        Вы ушли от темы. Это никак не относится к вашему утверждению, что DI требует лишнего класса по сравнению с SL.

                                                                        У Вас есть Controller, вы добавили ему инъекцию в ctor IUserService, если не будет зарегистрирован IUserService, то будет exception в runtime!!!

                                                                        Эксепшн будет в момент создания конструктора, а не выполнения какой-то операции (причем при неизвестных условиях).

                                                                        А создание всех конструкторов (и вообще всех зависимостей) в приложении очень легко протестировать (в отличие от прохода по всем путям выполнения).
                                                                        • 0
                                                                          Вы ушли от темы. Это никак не относится к вашему утверждению, что DI требует лишнего класса по сравнению с SL.

                                                                          Ну а как нет, если мне в Command, нужно получить экземпляр Gateway (который знает о ICommunicationMessage), в который потом передать ProviderOfType? Ответ «работайте без command», то же самое что «не используйте CQRS». У вас 3 класса, а у меня один, но поведение одинаковое.

                                                                          Эксепшн будет в момент создания конструктора, а не выполнения какой-то операции (причем при неизвестных условиях).

                                                                          Вы LINQ тоже не используете (он же отложенный) ?))
                                                                          public MyController()
                                                                          {
                                                                          this.dispatcher = IoCFactory.Instance.TryResolve<T>() 
                                                                          }
                                                                          

                                                                          примечание: если сюда добавить DI, то придет ещё регистрировать ControllerFactory, а в этой реализации НЕ надо.

                                                                          А создание всех конструкторов (и вообще всех зависимостей) в приложении очень легко протестировать (в отличие от прохода по всем путям выполнения).

                                                                          Конечно будет сложно, если плодить Gateway, Facade Data и т.д.

                                                                          • 0
                                                                            Ну а как нет, если мне в Command, нужно получить экземпляр Gateway (который знает о ICommunicationMessage), в который потом передать ProviderOfType?

                                                                            Понятно, мысленный эксперимент не прошел. Жалко.

                                                                            pubic class SendMessageCommand:CommandBase
                                                                            {
                                                                               private readonly IDictionary<ProviderOfType,ISocialNetwork> _networks;
                                                                            
                                                                               public SendMessageCommand(ISocialNetwork[] networks)
                                                                               {
                                                                                   _networks = networks.ToDictionary(n => n.Id);
                                                                               }
                                                                            
                                                                               public ProviderOfType Type {get;set;}
                                                                               public String Message {get;set;}
                                                                            
                                                                               public override Execute()
                                                                               {
                                                                                 _networks[Type].Send(Message);
                                                                               }
                                                                            }
                                                                            


                                                                            Ответ «работайте без command», то же самое что «не используйте CQRS».

                                                                            Янга вы, похоже, не читали. Процитирую: «There is no need to use messaging patterns with CQRS.»

                                                                            Вы LINQ тоже не используете (он же отложенный) ?)

                                                                            Я, скажем так, знаю, что абстрации, выраженные в LINQ, очень хорошо текут, и их надо аккуратно тестировать.

                                                                            примечание: если сюда добавить DI, то придет ещё регистрировать ControllerFactory, а в этой реализации НЕ надо.

                                                                            Это давно реализовано за меня специализированный nuget-пакетом, так что это меня не волнует.

                                                                            А ваш код — наглядный пример того, что SL позволяет делать по-разному, как хуже, так и лучше. Здесь у вас лучше. В вашем же коде в фреймворке — хуже (там Lazy, причем реализованный вручную, а не через IoC-фреймворк, поэтому ошибка отложена). Нет никакого способа это статически проконтролировать.
                                                                            • 0
                                                                              В вашем же коде в фреймворке — хуже (там Lazy, причем реализованный вручную, а не через IoC-фреймворк, поэтому ошибка отложена)

                                                                              Вы прям знаток, а не думали, что это специально для того чтобы не открывать Session если Command не работает с Базой?

                                                                              • 0
                                                                                Я знаю, зачем это. А еще я знаю, к чему это приводит.

                                                                                По остальным пунктам возражений нет?
                                                                                • 0
                                                                                  Я знаю, зачем это. А еще я знаю, к чему это приводит.

                                                                                  И к чему?

                                                                                  По остальным пунктам возражений нет?

                                                                                  Я не вижу смысла в нашей дискусии, потому что я Вас не переубежу (Вы сами с усами) и я не собираюсь менять своей позиции, потому что то, о чем Вы мне рассказываете я уже пробовал…
                                                                                  • 0
                                                                                    И к чему?

                                                                                    К ошибкам периода выполнения, причем на непредсказуемом пути выполнения.

                                                                                    Я не вижу смысла в нашей дискусии, потому что я Вас не переубежу (Вы сами с усами) и я не собираюсь менять своей позиции, потому что то, о чем Вы мне рассказываете я уже пробовал…

                                                                                    Так возражения есть, или нет? А то вы сделали парочку громких заявлений, например, о DI или о CQRS, а вот подтвердить их вам пока не удалось.
                                                                                    • 0
                                                                                      К ошибкам периода выполнения, причем на непредсказуемом пути выполнения.

                                                                                      Вы боитесь когда код работает? Есть dispatcher, который отвечает за работу Command/Query и сколько бы Вы раз не вызывали dispatcher.Push(new Command()), то он может упасть ТОЛЬКО если ошибка будет из-за кода внутри Command и все!.. С чего вдруг будут ошибки при получении IRepository?

                                                                                      вот подтвердить их вам пока не удалось.

                                                                                      Ваше заявления о Service Locator меня тоже не убедили.
                                                                                      • 0
                                                                                        Есть dispatcher, который отвечает за работу Command/Query и сколько бы Вы раз не вызывали dispatcher.Push(new Command()), то он может упасть ТОЛЬКО если ошибка будет из-за кода внутри Command и все!..

                                                                                        Это утверждение может верно только для того диспетчера, который у вас в голове (да и то я готов, в принципе, доказать обратное для вашего DefaultDispatcher).

                                                                                        Ваше заявления о Service Locator меня тоже не убедили.

                                                                                        Понятно. Значит, аргументированных возражений не осталось.
                                                                                        • 0
                                                                                          Это утверждение может верно только для того диспетчера, который у вас в голове (да и то я готов, в принципе, доказать обратное для вашего DefaultDispatcher).

                                                                                          Удачи.

                                                                                          Понятно. Значит, аргументированных возражений не осталось

                                                                                          А Вы новые фразы из книг заготовили?
                                                                                      • 0
                                                                                        например, о DI

                                                                                        Ради реализации DI я должен жертвовать простой разработки, потому что придется все время вводить зависимости в ctor.

                                                                                        Ещё раз пример IRepository, который используется в практически во всех Command, то мне придется везде копировать код, public MyCommand(IRepository repository) вместо того, что бы использовать базовый класс.

                                                                                        • 0
                                                                                          У вас очень свое собственное понятие о простоте разработки, не очень совпадающее с моим.
                                                                                          • 0
                                                                                            У вас очень свое собственное понятие о простоте разработки, не очень совпадающее с моим.

                                                                                            Я думаю это не единственное в чем мы не совпадаем.
                                                    • 0
                                                      Для Command/Query нужно иметь публичный ctor без параметров, потому что MVC binding делает десераилизацию, так что ради DI создавать ещё промежуточный класс, который использовать, как транспорт из Web form?.. Я думаю не стоит.

                                                      А эта проблема у вас возникла из-за того, что вы нарушили структуру CQRS. В оригинале команды исполнялись не сами, а обработчиком; соответственно, команда, как простой DTO, функциональности не имела и легко сериализовалась куда угодно. Обработчики же создавались один раз в коде сервера, и там проблемы с DI не было никакой.

                                                      Не зря же у паттерна конкретная структура, совсем не зря.
                                                      • 0
                                                        А эта проблема у вас возникла из-за того, что вы нарушили структуру CQRS.

                                                        Когда я только начинал, то делал ICommandHanlder<AddUserCommand> и AddUserCommand, но получается, что AddUserCommand это просто набор полей, поэтому что бы не делать 2 класса, мы объединяем и это ни как, не сказывается на объеме, потому что поля не усложняют класс.
                                                        Плюсы в том, что упрощается навигация по проекту

                                                        P.S. О «нарушили структуру CQRS», то даже в книгах про патернны делают сноски о возможных вариациях, то есть шаблон это набросок, который надо уже дорабатывать под код. Вы опять ради DI усложняете проект, только потому что, у Вас был неудачный опыт с Service Locator
                                                        • 0
                                                          Когда я только начинал, то делал ICommandHanlder<AddUserCommand> и AddUserCommand, но получается, что AddUserCommand это просто набор полей, поэтому что бы не делать 2 класса, мы объединяем и это ни как, не сказывается на объеме, потому что поля не усложняют класс.
                                                          Плюсы в том, что упрощается навигация по проекту

                                                          Вы не пробовали задуматься о том, зачем именно команда в CQRS — это именно DTO, а не исполняемый класс? Что, может быть, у авторов изначального паттерна была какая-то идея под этим? И что вы теряете, сливая команду и обработчик в одно?

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

                                                          Не в данном случае. CQRS — это архитектурное решение, которое можно применять вполне целиком. Я не видел, чтобы где-то была описана вариация CQRS, в которой команда бы выполнялась самостоятельно. Или вы считаете, что понимаете CQRS лучше Грега Янга?

                                                          Вы опять ради DI усложняете проект, только потому что, у Вас был неудачный опыт с Service Locator

                                                          Я ничего не усложняю. CQRS «из коробки» прекрасно поддерживает DI; это ваша вариация, которую вы сделали из своего понимания простоты, конфликтует со многими изначально заложенными возможностями.

                                                          Вот как вы будете делать гарантированную доставку и обработку команд в распределенной системе? Когда UI живет на одном узле, а выполнять команды надо на пяти других в облаке?
                                                          • 0
                                                            Вы не пробовали задуматься о том, зачем именно команда в CQRS — это именно DTO

                                                            DTO должен поддерживать сеарилизацию и как раз в моей реализации с этим проблем нет, а вот Ваш DI усложняет.

                                                            Я не видел, чтобы где-то была описана вариация CQRS, в которой команда бы выполнялась самостоятельно.

                                                            А Вы сами принимает решения или только после разрешения из книги?

                                                            Вот как вы будете делать гарантированную доставку и обработку команд в распределенной системе? Когда UI живет на одном узле, а выполнять команды надо на пяти других в облаке?

                                                            А что меняется, Вы отправили Ajax post из UI, а кто там его обработает не так важно.

                                                            P.S. Ничего личного, но по мне Вы просто сноб, который считает «ересью» отступление от шаблонов или реализацию их не так, как об этом написано в книги.
                                                            • 0
                                                              как раз в моей реализации с этим проблем нет,

                                                              Правда? А что случится, если в вашей реализации обратиться к репозиторию сначала до сериализации, а затем после?

                                                              А что меняется, Вы отправили Ajax post из UI, а кто там его обработает не так важно.

                                                              Я говорил о команде, которая создана как CLR-объект на узле веб-фермы, а потом должна быть обработана на одном из узлов-воркеров.

                                                              Ничего личного, но по мне просто надо сначала понять, почему и зачем в книге предлагается конкретное решение, а только потом думать о том, какие его части можно безболезненно выбросить.
                                                              • 0
                                                                Правда? А что случится, если в вашей реализации обратиться к репозиторию сначала до сериализации, а затем после?

                                                                Сериализация — это сбор всех публичных полей, которые имеют Get и Set, что бы потом превратить в Json/xml/binary.
                                                                Поле Repository — это protected, который не попадает под сериализацию или десериализацию

                                                                Я говорил о команде, которая создана как CLR-объект на узле веб-фермы, а потом должна быть обработана на одном из узлов-воркеров.

                                                                Может проще сериализовать Command в базу и потом уже в backgroud выполнить? В чем смысл сценария, что он дает? Если Вы о web balance, то там на уровне железа и софта, а не на уровне кода.
                                                                Dipsatcher потоко-безопасный, так что его можно вызывать, как удобно и где угодно )

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

                                                                Мы проходили N-layer, не обобщенные IRepository (ProductRepositoru, GenreRepository ), ICommandHanlder и класс для хранения полей T, using(new UnitOfWork()) { code }, инъекции в ctor и эти решения работали, НО теперь мы пишем меньше кода.
                                                                • +1
                                                                  Сериализация — это сбор всех публичных полей, которые имеют Get и Set, что бы потом превратить в Json/xml/binary. Поле Repository — это protected, который не попадает под сериализацию или десериализацию

                                                                  Прекрасно! Так что случится-то? К каким репозиториям будет обращение? Будут ли в них одни и те же объекты?

                                                                  Может проще сериализовать Command в базу и потом уже в backgroud выполнить?

                                                                  Не проще. База не заточена на такие операции (в отличие от очередей).

                                                                  В чем смысл сценария, что он дает?

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

                                                                  НО теперь мы пишем меньше кода.

                                                                  Вопрос в том, ценой чего. Спагетти требует еще меньше кода, только вот поддерживать и развивать его дорого.
                                                                  • 0
                                                                    Прекрасно! Так что случится-то? К каким репозиториям будет обращение? Будут ли в них одни и те же объекты?

                                                                    НЕЛЬЗЯ использовать Command/Query без Dispatcher и именно он открывает Session (context) и поддерживает tranasaction (UoW).
                                                                    Пример из моей статьи про CQRS

                                                                    new DeactivateEntityCommand().Execute(); // without transaction and connection
                                                                    dispatcher.Push(new DeactivateEntityCommand()); // open transaction and connection
                                                                    


                                                                    Не проще. База не заточена на такие операции (в отличие от очередей).

                                                                    Смотря какая, mongo очень шустро работает.

                                                                    • 0
                                                                      НЕЛЬЗЯ использовать Command/Query без Dispatcher и именно он открывает Session (context) и поддерживает tranasaction (UoW).

                                                                      Вы на вопрос не ответили. Вопрос был простой: будут ли в репозитории до и после сериализации команды одни и те же объекты, или нет?

                                                                      Смотря какая, mongo очень шустро работает.

                                                                      Вы сравнивали со специализированными очередями? А по удобству работы?
                                                                      • 0
                                                                        Вы на вопрос не ответили. Вопрос был простой: будут ли в репозитории до и после сериализации команды одни и те же объекты, или нет?

                                                                        Конечно нет, потому что Dispatcher закроет Unit of work и Session, что приведет к Disposable. Если бы Вы до конца досмотрели реализацию (или дочитали статью про CQRS), а не поднимали тревогу из-за Service Locator, то Вам все было понятно.

                                                                        Вы сравнивали со специализированными очередями? А по удобству работы?

                                                                        Disaptcher имеет параметры для настройки того, как будет выполнена Command/Query и там предусмотрено Delay, что заставляет вместо выполнения поместить Command в Scheduler (планировщик)
                                                                        • 0
                                                                          Конечно нет, потому что Dispatcher закроет Unit of work и Session, что приведет к Disposable. Если бы Вы до конца досмотрели реализацию (или дочитали статью про CQRS), а не поднимали тревогу из-за Service Locator, то Вам все было понятно.

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

                                                                          Disaptcher имеет параметры для настройки того, как будет выполнена Command/Query и там предусмотрено Delay, что заставляет вместо выполнения поместить Command в Scheduler (планировщик)

                                                                          Это тоже не имеет отношения к обсуждению. Вы когда-нибудь работали со специализированными очередями/шинами сообщений (MSMQ, Azure Service Bus/Azure Queues, RabbitMQ и так далее)?
                                                                          • 0
                                                                            что в некий неизвестный момент ее работы ее сериализуют

                                                                            Ещё раз говорю, Disaptcher открывает Unit of work и закрывает его после завершения кода Command/Query и ни каких «неизвестных моментов» быть не может!!! Вы опишите ситуацию, а то выдумки какие то.
                                                                            Какой такой момент? Код вдруг с того не всего начал проводить сереализацию или что?

                                                                            Это тоже не имеет отношения к обсуждению. Вы когда-нибудь работали со специализированными очередями/шинами сообщений (MSMQ, Azure Service Bus/Azure Queues, RabbitMQ и так далее)?

                                                                            Если нужно, то можно подменить реализацию IDisaptcher, которая будет работать с очередями, НО меня устраивает и так.
                                                                            • 0
                                                                              Какой такой момент? Код вдруг с того не всего начал проводить сереализацию или что?

                                                                              Ну как же. Команду создали. В момент создания она для своей инициализации взяла какие-то данные (может, из контекста, может, из репозитория). Это был конструктор. Потом ее сериализовали и отправили на другую ноду, где запустили. Это Execute. В этот момент она взяла и попробовала связать данные, которые она взяла, с тем, откуда она их взяла — она-то не знает, что она уже на другой ноде. Упс, клинч.

                                                                              Если нужно, то можно подменить реализацию IDisaptcher, которая будет работать с очередями, НО меня устраивает и так.

                                                                              Вы снова не ответили на прямой вопрос.

                                                                              У меня создается ощущение, что вы применяли свой фреймворк ровно в одном сценарии — веб-приложение, развернутое на одной ноде (или с горизонтальным масштабированием, где все ноды равноправны). Да? Если нет, то опишите, какие еще варианты компоновки решения вы успешно использовали, и как была устроена коммуникация между нодами.
                                                                              • 0
                                                                                Ну как же. Команду создали. В момент создания она для своей инициализации взяла какие-то данные (может, из контекста, может, из репозитория). Это был конструктор. Потом ее сериализовали и отправили на другую ноду, где запустили. Это Execute. В этот момент она взяла и попробовала связать данные, которые она взяла, с тем, откуда она их взяла — она-то не знает, что она уже на другой ноде. Упс, клинч.

                                                                                если вы делаете disaptcher.Push(new Command()) то это синхронный код и пока тело Execute не завершится, то ничего не будет.

                                                                                Вы снова не ответили на прямой вопрос.

                                                                                Зачем, если я решаю свои задачи и без них?

                                                                                У меня создается ощущение, что вы применяли свой фреймворк ровно в одном сценарии — веб-приложение, развернутое на одной ноде (или с горизонтальным масштабированием, где все ноды равноправны). Да? Если нет, то опишите, какие еще варианты компоновки решения вы успешно использовали, и как была устроена коммуникация между нодами.

                                                                                Я так смотрю решили «померится», кто что сделал?
                                                                                • 0
                                                                                  если вы делаете disaptcher.Push(new Command()) то это синхронный код и пока тело Execute не завершится, то ничего не будет.

                                                                                  Про недостатки синхронного кода в веб-приложениях я вам даже не буду рассказывать, это уже совсем за рамками дискуссии. А вот «ничего не будет» — это интересно.

                                                                                  Скажите, а как же в рамках вашего фреймворка все-таки реализовать обработку команд на нескольких узлах, учитывая, что команды формируются в .net-коде на уровне фронт-ноды? Или это невозможно?

                                                                                  Зачем, если я решаю свои задачи и без них?

                                                                                  То есть не работали. Ок.

                                                                                  Я так смотрю решили «померится», кто что сделал?

                                                                                  Нет, я хочу понять границы применимости предлагаемого вами решения; только не гипотетические «а вот можно было бы», а конкретные — в каких оно уже точно работает.
                                                                                  • 0
                                                                                    Нет, я хочу понять границы применимости предлагаемого вами решения; только не гипотетические «а вот можно было бы», а конкретные — в каких оно уже точно работает.

                                                                                    Pazar трафик 100000 пользователей в день и больше 1000 запросов в секунду.

                                                                                    в каких оно уже точно работает

                                                                                    Мы сделали порядка 30 проектов на Incoding Framework

                                                                                    Про недостатки синхронного кода в веб-приложениях я вам даже не буду рассказывать, это уже совсем за рамками дискуссии. А вот «ничего не будет» — это интересно.

                                                                                    На клиенте будет ajax ( который из названия асинхронный), а на сервере чаще (хотя без проблем asyn + avoid ) исполняется синхронный код, потому что нужно дождаться завершения транзакции.

                                                                                    Или это невозможно?

                                                                                    Заменить DefaultDispatcher другой реализацией.

                                                                                    • 0
                                                                                      Pazar трафик 100000 пользователей в день и больше 1000 запросов в секунду.

                                                                                      Архитектура развертывания? Я же, в общем-то, задал конкретный вопрос: в каких именно вариантах развертывания использовался ваш фреймворк. Меня не интересуют проекты сами по себе и их нагрузка — только развертывание.

                                                                                      на сервере чаще (хотя без проблем asyn + avoid ) исполняется синхронный код, потому что нужно дождаться завершения транзакции.

                                                                                      Т.е. про IOCP и неблокирующее ожидание ресурсов вы тоже не слышали?

                                                                                      (перепутать avoid и await — это сильно...)

                                                                                      Заменить DefaultDispatcher другой реализацией.

                                                                                      Как именно должна работать эта реализация для описанного мной сценария? Или вы не продумывали такой сценарий?
                                                                                      • 0
                                                                                        Архитектура развертывания?

                                                                                        Клиент — Сервер, все же framewok в первую очередь по Web

                                                                                        (перепутать avoid и await — это сильно...)

                                                                                        Сейчас час ночи и уже глаза закрываются, НО конечно из всей нашей дискуссии это самое важное.

                                                                                        Т.е. про IOCP и неблокирующее ожидание ресурсов вы тоже не слышали?

                                                                                        Вы лучше объясните, почему вдруг в процессе выполнения Command Вы её начинаете сериализовать.

                                                                                        Как именно должна работать эта реализация для описанного мной сценария? Или вы не продумывали такой сценарий?

                                                                                        Нет.
                                                                                        • 0
                                                                                          Клиент — Сервер, все же framewok в первую очередь по Web

                                                                                          Значит, мои оценки были правильными.

                                                                                          Вы лучше объясните, почему вдруг в процессе выполнения Command Вы её начинаете сериализовать.

                                                                                          А никто не говорит о «в процессе выполнения». Диспетчер получил готовую (но еще не выполненную) команду, а теперь ее надо выполнить на другой ноде — он ее сериализует и кладет в очередь; на ноде выполнения ее десериализуют достают из очереди и выполняют. Это как раз тот сценарий, который вы не продумывали (и который в message-based CQRS является базовым). Между созданием команды и ее выполнением есть разрыв, в котором может происходить что угодно.
                                                                                          • 0
                                                                                            Значит, мои оценки были правильными.

                                                                                            Польщен, сам LAIR оценил меня!!!

                                                                                            Это как раз тот сценарий, который вы не продумывали (и который в message-based CQRS является базовым).

                                                                                            Я же говорю, что можно сохранять Command в базу или ещё куда ( провайдер может быть любой )

                                                                                            Между созданием команды и ее выполнением есть разрыв, в котором может происходить что угодно.

                                                                                            Command будет выполнятся тогда когда она будет создана, но не иначе, потому что тогда, нарушается целостность.
                                                                                            Вы до new Command() уже будете выполнять?
                                                                                            • 0
                                                                                              Я же говорю, что можно сохранять Command в базу или ещё куда ( провайдер может быть любой )

                                                                                              Можно. И получить все проблемы, связанные с сериализацией исполняемых объектов (vs сериализация dto).

                                                                                              Command будет выполнятся тогда когда она будет создана, но не иначе, потому что тогда, нарушается целостность. Вы до new Command() уже будете выполнять?

                                                                                              Вы путаете. Команда будет выполняться после ее создания, это очевидно. Но между созданием и выполнением диспетчер может сделать с командой что угодно, и сама команда об этом ничего не знает.
                                                                                              • 0
                                                                                                Вы путаете. Команда будет выполняться после ее создания, это очевидно. Но между созданием и выполнением диспетчер может сделать с командой что угодно, и сама команда об этом ничего не знает.

                                                                                                «диспетчер может сделать с командой что угодно» — Вы сейчас о Искусственном интеллекте? Есть исходники и можно посмотреть, что делает Dispatcher.Push/Query, а Вы строите какие то гипотезы, о «неведомых» ситуациях.!!!

                                                                                                сделать с командой что угодно, и сама команда об этом ничего не знает

                                                                                                Что сделать? ЕЩЁ РАЗ, код Dispatcher один и он известный, там нету ни каких рандомных ветвлений, который могут или не могут быт.
                                                                                                • 0
                                                                                                  код Dispatcher один и он известный

                                                                                                  Я правильно понимаю, что вы только что признали, что система не расчитана на работу с любыми другими диспетчерами кроме написанного вами дефолтного? И все ваши фразы «можно написать другой диспетчер» на самом деле не имеют отношения к реальности?
                                                                                • 0
                                                                                  В момент создания она для своей инициализации взяла какие-то данные (может, из контекста, может, из репозитория). Это был конструктор.

                                                                                  Весь код должен быть в методе Execute, а на в ctor, потому что Repository будет доступен только там. Проектировалось все так, что бы ограничить контекст работы с базой данных, а не вызывать где попало Repository.
                                                                                  • 0
                                                                                    Весь код должен быть в методе Execute, а на в ctor, потому что Repository будет доступен только там.

                                                                                    Только вы это тоже никак не контролируете (хотя могли бы). И поэтому разработчик будет писать так, как ему удобно.
                                                                                    • 0
                                                                                      Только вы это тоже никак не контролируете (хотя могли бы). И поэтому разработчик будет писать так, как ему удобно.

                                                                                      Вы в asp.net mvc, тоже пишите как хотите или делаете по руководству? Я четко написал «НЕЛЬЗЯ» вызывать Command/Query без Dispatcher, потому что код не будет работать.
                                                                                      • 0
                                                                                        Вы в asp.net mvc, тоже пишите как хотите или делаете по руководству?

                                                                                        Я стараюсь писать так, как мне удобно. Но я всегда понимаю, что разработчик, который пользуется предоставляемым мной кодом, будет пользоваться им так, как удобно ему, поэтому я максимально защищаю свой код от (по крайней мере, случайного) некорректного использования.
                                                                    • 0
                                                                      Сериализация — это сбор всех публичных полей, которые имеют Get и Set, что бы потом превратить в Json/xml/binary. Поле Repository — это protected, который не попадает под сериализацию или десериализацию

                                                                      Что-то я невнимателен.

                                                                      Описанное вами поведение верно для дефолтных реализаций xml- и json-сериализаторов в .net. При этом дефолтный же бинарный сериализатор в .net ведет себя строго обратно: он сериализует все поля вне зависимости от их видимости. Так что если вы его внедрите (для того же распределенного сценария), вы можете получить множество занимательных побочных эффектов.

                                                                      (поля, кстати, не могут иметь get и set)
                                                                      • 0
                                                                        Описанное вами поведение верно для дефолтных реализаций xml- и json-сериализаторов в .net.

                                                                        asp.net mvc, WCF они используют именно такие подходы.

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

                                                                        Всегда можно настроить, то какие поля сериализовать, а как нет.

                                                                        Если Вы волнуетесь, о том, что после Deserialize у Вас будет тот же самый Repository, то Вы не правы:
                                                                        1. Если Вы сериализуете Command ДО выполнения, то Repository ещё не создан и по этому сохранять и нечего
                                                                        2. Если ПОСЛЕ выполнения, то Repository уже закрыт
                                                                        3.Все, больше вариантов
                                                                        • 0
                                                                          asp.net mvc, WCF они используют именно такие подходы.

                                                                          На внешней границе. При сериализации данных в очередь обычно используют бинарник, потому что быстрее и компактнее.

                                                                          Если Вы волнуетесь, о том, что после Deserialize у Вас будет тот же самый Repository,

                                                                          Я волнуюсь о том, что поведение недетерминировано.

                                                                          Если Вы сериализуете Command ДО выполнения, то Repository ещё не создан и по этому сохранять и нечего

                                                                          Это не так. Даже отбрасывая сценарий, при котором обращение к репозиторию произошло до Execute (а это валидное поведение, пусть вами и не предусмотренное), все равно остается сугубо инфраструктурная деталь: ваше поле с репозиторием — это lazy (задаваемый в конструкторе). При его сериализации (по крайней мере, насколько я понимаю код) заданная фабрика теряется, поэтому после десериализации любые обращения к репозиторию просто упадут с ошибкой инициализации lazy.
                                                                          • 0
                                                                            На внешней границе. При сериализации данных в очередь обычно используют бинарник, потому что быстрее и компактнее.

                                                                            Статья про MVD, который используется в рамках asp.net mvc, а Вы уже все подряд.

                                                                            обычно используют бинарник, потому что быстрее и компактнее.

                                                                            ТО есть, JSON уже не удобен? RavenDB, MongoDb все они на JSON и мне кажется шустро работают.

                                                                            При его сериализации (по крайней мере, насколько я понимаю код) заданная фабрика теряется, поэтому после десериализации любые обращения к репозиторию просто упадут с ошибкой инициализации lazy.

                                                                            Вы несете бред!!! Если объект десериализовать, то все поля заново будут вычислены, это не теория, потому что у нас есть Scheduler, который так и работает.

                                                                            Repository это только набор методов для работы с бд, есть ещё Unit Of Work и Session Factory.
                                                                            • 0
                                                                              Статья про MVD, который используется в рамках asp.net mvc, а Вы уже все подряд.

                                                                              Вы все время забываете, что asp.net mvc — это всего лишь фронтенд, а CQRS — это сквозное решение; и для взаимодействия фронт-нод с воркерами можно использовать разные способы. Впрочем, мы уже выяснили, что вы такой сценарий не рассматривали и систему в нем не применяли.

                                                                              ТО есть, JSON уже не удобен? RavenDB, MongoDb все они на JSON и мне кажется шустро работают.

                                                                              Зависит от задачи. Бинарная сериализация компактнее json, это такая данность.

                                                                              Вы несете бред!!! Если объект десериализовать, то все поля заново будут вычислены, это не теория, потому что у нас есть Scheduler, который так и работает.

                                                                              У вас в шедулере используется сериализация в json, которая работает иначе. Я говорил про бинарную.
                                                                              • 0
                                                                                Вы все время забываете, что asp.net mvc — это всего лишь фронтенд

                                                                                Зачем так усложнять, если asp.net как backend прекрасно работает? В чем смысл, разве мало сценариев, как оптимизировать веб сервер?

                                                                                У вас в шедулере используется сериализация в json, которая работает иначе. Я говорил про бинарную.

                                                                                Я не использовал бинарную уже лет 7, потому что все мобильные приложения, веб сайты, js framework и т.д. это JSON.

                                                                                НО все же повторю, сериализовать Command можно, во что угодно.
                                                                                • 0
                                                                                  asp.net как backend прекрасно работает?

                                                                                  Не так прекрасно, как хотелось бы. Любая тяжелая обработка — и начинают страдать другие пользователи. Более того, любая асинхронная обработка с паралеллизмом — и выполнение кода становится негарантированным.

                                                                                  Собственно, это типовой сценарий в CQRS w/messaging — мы приняли команду пользователя, она породила десяток событий, все их надо обработать. Но пользователя-то это не волнует, его сценарий использования завязан на ровно одно событие, он его дождался и ушел домой. А кто будет обрабатывать все остальные? В каком процессе?

                                                                                  В чем смысл, разве мало сценариев, как оптимизировать веб сервер?

                                                                                  Горизонтальное масштабирование воркеров намного дешевле.

                                                                                  НО все же повторю, сериализовать Command можно, во что угодно.

                                                                                  «Мы думаем, что можно, но не пробовали». А в реальности получите описанные выше проблемы.
                                                                                  • 0
                                                                                    Собственно, это типовой сценарий в CQRS w/messaging — мы приняли команду пользователя, она породила десяток событий, все их надо обработать. Но пользователя-то это не волнует, его сценарий использования завязан на ровно одно событие, он его дождался и ушел домой. А кто будет обрабатывать все остальные? В каком процессе?

                                                                                    Кладем 10 Command в Scheduler (планировщик), который обрабатывается в backgroud.

                                                                                    Не так прекрасно, как хотелось бы. Любая тяжелая обработка — и начинают страдать другие пользователи. Более того, любая асинхронная обработка с паралеллизмом — и выполнение кода становится негарантированным.

                                                                                    То есть, Stackoverflow нормально справился, а у Вас проблемы?

                                                                                    «Мы думаем, что можно, но не пробовали». А в реальности получите описанные выше проблемы.

                                                                                    Судя по Вашему профилю, где за 4000 комментов (и 0 постов), которые почти все направленны на «провакацию», Вы просто тролль.

                                                                                    P.S. Вы бы показали Ваши (да, я хочу знать, кто меня осуждает) проекты, а то прямо все знаете, все видели, да ещё и оцениваете
                                                                                    • 0
                                                                                      Кладем 10 Command в Scheduler (планировщик), который обрабатывается в backgroud.

                                                                                      Обрабатываются каким процессом?

                                                                                      То есть, Stackoverflow нормально справился, а у Вас проблемы?

                                                                                      У меня другие задачи. Но вообще, у SO архитектура малость посложнее, чем вы говорите — там и редис для кэширования, и сообщения для управления кэшированием, и люцен для поиска (я уж молчу про балансировщики).

                                                                                      Судя по Вашему профилю, где за 4000 комментов (и 0 постов), которые почти все направленны на «провакацию», Вы просто тролль.

                                                                                      Судя по переходу на личность, аргументы кончились.

                                                                                      Мои оценки основаны на вашем коде и вещах, которые вы же и озвучиваете. Чтобы их обсуждать, не нужно знать о том, какие проекты и для кого я делал (хотя в принципе эта информация публично доступна).
                                                                                      • 0
                                                                                        Судя по переходу на личность, аргументы кончились.

                                                                                        Да, Вам что не пиши Вы все равно скажите, что это не так. Вся наша переписка это просто отрицание, на которое больше не вижу смысла тратить время, потому что Вы не планируете использовать framework, а значит зачем Вам мои ответы…?

                                                                                        Обрабатываются каким процессом?

                                                                                        Где угодно, есть код, который отвечает за выполнение отложенных Command, откуда его вызывать не важно ( windows service, console, TaskFactory.NewStart при старте Global.asax )

                                                                                        • 0
                                                                                          Где угодно, есть код, который отвечает за выполнение отложенных Command, откуда его вызывать не важно ( windows service, console, TaskFactory.NewStart при старте Global.asax )

                                                                                          Как раз очень важно.

                                                                                          Если вызывать в global.asax, то вы нагружаете процесс самого веб-сервера (IIS), который не очень дешевый. Что веселее, вы можете никогда не узнать, что этот процесс остановили/перегрузили (при перезагрузке апп-пула, например), а вместе с ним без каких-либо объяснений умерли и все задачи, которые вы запускали.

                                                                                          А если вызывать в отдельном сервисе, то это уже не asp.net, и это как раз тот сценарий, про который я говорил, когда бэкэнд из asp.net выносится по тем или иным причинам.

                                                                                          Как реализовано конкретно у вас?
                                                                                          • 0
                                                                                            Если вызывать в global.asax, то вы нагружаете процесс самого веб-сервера (IIS), который не очень дешевый.

                                                                                            Я привел разные примеры, но не спрашивал Вашего мнения о них. Не Вы один знаете, как работает App pool.

                                                                                            А если вызывать в отдельном сервисе, то это уже не asp.net, и это как раз тот сценарий, про который я говорил, когда бэкэнд из asp.net выносится по тем или иным причинам

                                                                                            Код один, просто пишется ещё площадка для размещения, какую хотите.
                                                                                            • 0
                                                                                              Собственно, почему asp.net в качестве бэкэнда на определенных задачах недостаточен — тоже считаем продемонстрированным.

                                                                                              PS Thread.Sleep в серверном коде? Вы серьезно?
                                                                                              • 0
                                                                                                PS Thread.Sleep в серверном коде? Вы серьезно?

                                                                                                А как Вы считаете реализовывать задержки между сеансами?

                                                                                                Скажите раз Вам так все не нравится мой framework (нету DI, отсутствует DI и другие проблемы в том числе нехватка DI), зачем наш диалог? Моя цель это помочь тем, кто решит попробовать framework или думает его использовать, но Вы просто тратите время на комменты.
                                                                                                • 0
                                                                                                  А как Вы считаете реализовывать задержки между сеансами?

                                                                                                  (1) есть более одного шедулинг-фреймворка с работой по расписанию, начиная от quartz.net, которые просто абстрагируют это от вас
                                                                                                  (2) если вы настолько любите писать все сами, есть System.Threading.Timer и System.Timers.Timer, которые хорошо подходят для периодических задач
                                                                                                  (3) наконец, если вам так хочется делать именно циклический опрос/обработку, есть Task.Delay, который, по крайней мере, позволяет отпустить поток и не потреблять ресурсы во время ожидания (понятное дело, использовать его с Wait — бессмысленно, там нужен честный async).

                                                                                                  Скажите раз Вам так все не нравится мой framework (нету DI, отсутствует DI и другие проблемы в том числе нехватка DI), зачем наш диалог?

                                                                                                  Чтобы прочие читатели этого поста, которые решат попробовать фреймворк или думают его использовать, заранее видели его (фреймворка) ограничения и недостатки.
                                                                                                  • 0
                                                                                                    есть более одного шедулинг-фреймворка с работой по расписанию, начиная от quartz.net, которые просто абстрагируют это от вас

                                                                                                    Очень много специфики, так что решили свой.

                                                                                                    если вы настолько любите писать все сами, есть System.Threading.Timer и System.Timers.Timer, которые хорошо подходят для периодических задач

                                                                                                    Это другие задачи, мне надо все время выполнять, потому что расписание будет через recurrence schedule.

                                                                                                    по крайней мере, позволяет отпустить поток и не потреблять ресурсы во время ожидания

                                                                                                    Не вижу проблемы, поток один и постоянно крутится, чаще ставится минимальный Sleep (можно 0). Scheduler пока в beta версии, много ещё не доделано (будет UI для управления задачами и т.д.), по этому могут быть нюансы, НО про него я пока ещё и не писал в статьях. Надо учитывать что Scheduler работает с провайдерами framework, то есть там тоже Repository, что позволяет легко менять место хранения задач.

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


                                                                                                    Вам не жалко тратить время ?, что бы доказывать другим, что мой инструмент плохой, особо то и не зная ( с Вас кстати пример, когда Disapatcher.Push выкинет ошибку), как он работает.

                                                                                                    • 0
                                                                                                      Это другие задачи, мне надо все время выполнять, потому что расписание будет через recurrence schedule.

                                                                                                      Таймеры тоже постоянно работают.

                                                                                                      Не вижу проблемы, поток один и постоянно крутится

                                                                                                      То есть постоянно ест ресурсы. Ну да, нет никаких проблем.

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

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

                                                                                                      с Вас кстати пример, когда Disapatcher.Push выкинет ошибку

                                                                                                      Очень просто.
                                                                                                      1. Не регистрируем IEventBroker
                                                                                                      2. IoCFactory.Instance.TryResolve<IEventBroker>() возвращает null
                                                                                                      3. (при определенных условиях) вызывается eventBroker.Publish()
                                                                                                      4. получаем NullReferenceException
                                                                                                      5. в блоке catch (при определенных условиях) идет повторное обращение к eventBroker, что дает новый эксепшн (привет отладке), уже внутри catch
                                                                                                      6. то же самое повторяется в finally, после чего мы получаем голый необработанный эксепшн


                                                                                                      PS Интересно, если «примечание: если сюда добавить DI, то придет ещё регистрировать ControllerFactory, а в этой реализации НЕ надо», то зачем в вашем фреймворке есть IncControllerFactory?
                                                                                                      • 0
                                                                                                        Таймеры тоже постоянно работают.

                                                                                                        Таймеры живут в памяти, а если скажем у Вас сотни Command в планировщики? Мой подход просто делает запрос в бд (через Query) и достает все (можно с pagianated) актуальные на данный момент и выполняет (можно параллельно сразу несколько).

                                                                                                        То есть постоянно ест ресурсы. Ну да, нет никаких проблем.

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

                                                                                                        Не регистрируем IEventBroker

                                                                                                        Это называется не «не читаете документации». Вы для asp.net mvc можете удалить global.asax?
                                                                                                        примечание: при установки через nuget у Вас все настроится

                                                                                                        PS Интересно, если «примечание: если сюда добавить DI, то придет ещё регистрировать ControllerFactory, а в этой реализации НЕ надо», то зачем в вашем фреймворке есть IncControllerFactory?

                                                                                                        Потому что раньше был IDispatcher в IncControllerBase, но потом отказались в пользу менее зависимого решения.
                                                                                                        • 0
                                                                                                          Таймеры живут в памяти, а если скажем у Вас сотни Command в планировщики? Мой подход просто делает запрос в бд (через Query) и достает все (можно с pagianated) актуальные на данный момент и выполняет (можно параллельно сразу несколько).

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

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

                                                                                                          Т.е. вы не в курсе, что сам по себе поток — это накладные расходы, а Thread.Sleep ест процессорное время?

                                                                                                          Это называется не «не читаете документации».

                                                                                                          А это уже не важно. Важно, что диспетчер после этого упадет, а значит, ваше утверждение «он может упасть ТОЛЬКО если ошибка будет из-за кода внутри Command и все!.» — ошибочно.

                                                                                                          (Я, при этом, не говорю, что он не должен падать, это отдельный интересный вопрос. Но вот то, что вы опять не учитываете то, что ваша система — предположительно — будет использоваться не только вами и не только так, как вы предполагаете — это показательно. И то, как именно он падает — тоже показательно, потому что отследить, что именно произошло, будет очень сложно — хотя казалось бы, достаточно использовать Resolve вместо TryResolve, и была бы типизованная ошибка сразу в нужном месте.)

                                                                                                          И это, заметим, только первый уровень кода. Ваш event broker обрабатывает события синхронно (да, я в курсе наличия атрибута HandlerAsyncAttribute, только это неправильная политика — все события должны быть асинхронны, а не только избранные; более того, асинхронность у вас сделана через BeginInvoke, а EndInvoke вы вызываете? я не нашел, а это, заметим, обязательно), и ошибки в них вы тоже не блокируете — а это значит, что во время выполнения команды ошибка в любом синхронном обработчике событий тоже вылетит из метода Push, хотя он, казалось бы, ни в чем не виноват.

                                                                                                          (а вот сделали бы вы очереди, сразу бы все упростилось и стало надежнее… не надо реализовывать messaging наполовину; либо все, либо ничего)
                                                                                                          • 0
                                                                                                            А это уже не важно. Важно, что диспетчер после этого упадет, а значит, ваше утверждение «он может упасть ТОЛЬКО если ошибка будет из-за кода внутри Command и все!.» — ошибочно.

                                                                                                            Если не подключать incoding.dll, то даже компилироваться не будет.

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

                                                                                                            Nuget все настраивает, так что я бы не сказал, что прямо все плохо и запутанно. Есть Get started, а забыл Вы же не читаете доки…

                                                                                                            Т.е. вы не в курсе, что сам по себе поток — это накладные расходы, а Thread.Sleep ест процессорное время?

                                                                                                            Мы говорим о одном потоке, а тот же asp.net mvc создает для каждого Action свой, так что критичного ничего в этом ничего нет.

                                                                                                            все события должны быть асинхронны, а не только избранные; все события должны быть асинхронны, а не только избранные;

                                                                                                            Это Ваш подход.

                                                                                                            то во время выполнения команды ошибка в любом синхронном обработчике событий тоже вылетит из метода Push, хотя он, казалось бы, ни в чем не виноват

                                                                                                            В этом идея транзакционности, потому что нужно что бы все закончили без ошибок. Event в incoding framework больше, как способ разгрузить Command и кстати в новой версии его заменяет внутренний Disaptcher, который имеет метод Delay, что близко к async, но безопасней (Command будет в планировщики и мы всегда будем видеть на каком он этапе)

                                                                                                            • 0
                                                                                                              Nuget все настраивает, так что я бы не сказал, что прямо все плохо и запутанно.

                                                                                                              Nuget настраивает, а люди переделывают. У вас нет никакого контроля над этим, и поэтому вы должны учитывать эту возможность в своем коде. А вы не учитываете — и ваш код становится хрупким.

                                                                                                              Мы говорим о одном потоке, а тот же asp.net mvc создает для каждого Action свой, так что критичного ничего в этом ничего нет.

                                                                                                              В потере ресурсов нет ничего критичного? Завидую вам.

                                                                                                              Это Ваш подход.

                                                                                                              Это messaging.

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

                                                                                                              Вам не приходило в голову, что обработчики событий не должны влиять на команду? Создав эту зависимость вы окончательно потеряли идею CQRS, который как раз направлен на то, чтобы ее разорвать.
                                                                                                              • 0
                                                                                                                Nuget настраивает, а люди переделывают. У вас нет никакого контроля над этим, и поэтому вы должны учитывать эту возможность в своем коде. А вы не учитываете — и ваш код становится хрупким.

                                                                                                                Хорошо пример с Controller Factory для вызова TryResolve, которую Вам устанавливает nuget, Вы же тоже можете удалить? Поймите, там всего 5 зависимостей, которые обязательно должны быть.
                                                                                                                Я могу в коде Dispatcher сделать TryResolve<IEventBroker>() ?? new DefaultEventBroker(), но IRepository, IUnitOfWorkFactory так уже не получится.

                                                                                                                В потере ресурсов нет ничего критичного? Завидую вам.

                                                                                                                Поток работает постоянно и ни когда не останавливает. Оптимизировать надо уже Command/Query, а 1 Thread не погубит систему. Всегда можно вынести Scheduler на другой сервер ( для серьезных проектов, добавить сервер не такая проблема )
                                                                                                                • 0
                                                                                                                  Хорошо пример с Controller Factory для вызова TryResolve, которую Вам устанавливает nuget, Вы же тоже можете удалить?

                                                                                                                  Могу, конечно.

                                                                                                                  Я могу в коде Dispatcher сделать TryResolve<IEventBroker>() ?? new DefaultEventBroker(), но IRepository, IUnitOfWorkFactory так уже не получится.

                                                                                                                  И что? Обрабатывайте ошибки корректно, это еще Макконнел писал.

                                                                                                                  Всегда можно вынести Scheduler на другой сервер

                                                                                                                  … и еще одна иллюстрация.
                                                                                                                  • 0
                                                                                                                    Могу, конечно.

                                                                                                                    Значит кругом не безопасный код

                                                                                                                    И что? Обрабатывайте ошибки корректно, это еще Макконнел писал.

                                                                                                                    Почему это ошибка? Поймите этот код настраивается (а точнее это делает NuGet) один раз и далее он не меняется. Если ничего не удалять и не саботировать код, то все будет работать. Прогнозирование ВСЕХ ошибок и обработка их, только увеличит код.

                                                                                                                    P.S.прочитать (и ничего не удалять из NuGet) Get started и понять, как пользоваться инструментом вот, что нужно, что бы избежать ошибок.
                                                                                                                    • 0
                                                                                                                      Значит кругом не безопасный код

                                                                                                                      Это для вас новость?

                                                                                                                      Почему это ошибка?

                                                                                                                      Потому что вылетел exception, который кому-то теперь надо обработать.

                                                                                                                      Прогнозирование ВСЕХ ошибок и обработка их, только увеличит код.

                                                                                                                      Похоже, Макконнела вы тоже не читали. Defensive programming, все такое.

                                                                                                                      Впрочем, вы снова невнимательны. Дело ведь не в том, что Push бросает эксепшны — это как раз нормально (хотя, конечно, то, как вы их обрабатываете — это печаль). Ненормально то, что вы утверждаете, что он их бросает только в одном случае, хотя это не так.
                                                                                                                      • 0
                                                                                                                        Потому что вылетел exception, который кому-то теперь надо обработать.

                                                                                                                        Ничего не надо обрабатывать, надо просто спросить (Stackoverflow к примеру) или почитать документацию, где будет решение проблемы связанной с не правильной настройкой.

                                                                                                                        Ненормально то, что вы утверждаете, что он их бросает только в одном случае, хотя это не так.

                                                                                                                        ЕСЛИ правильно настроить IoC ОДИН раз, то ошибок в Push быть не должно, иначе они считаются багами framework и о них надо написать в issue. К примеру Nhibernate, если где то внутри будет Exception, Вы что будете делать?

                                                                                                                        Похоже, Макконнела вы тоже не читали. Defensive programming, все такое.

                                                                                                                        Вы понимаете, что есть разные подходы, к примеру я не использую Data Contract и это значит, что надо брать цитаты из книги про него (Data contract) в качестве аргумента, что мой код плохой?

                                                                                                                        хотя, конечно, то, как вы их обрабатываете — это печаль

                                                                                                                        У Вас свой catch?

                                                                                                                        Вывод: если после установки framework через Nuget, ничего не удалять, то можно приступать писать Command/Query
                                                                                                                        • 0
                                                                                                                          Ничего не надо обрабатывать, надо просто спросить (Stackoverflow к примеру) или почитать документацию, где будет решение проблемы связанной с не правильной настройкой.

                                                                                                                          Ошибки вообще надо обрабатывать, это полезно.

                                                                                                                          ошибок в Push быть не должно

                                                                                                                          А будут. Ошибка настройки IoC — это всего лишь один пример возможной ошибки.

                                                                                                                          Вы понимаете, что есть разные подходы

                                                                                                                          Понимаю. Например, в обработке ошибок есть (условно говоря) ровно два подхода: fail early и fail never. У вас ни то, и ни другое, и это от банального недосмотра.

                                                                                                                          надо брать цитаты из книги про него (Data contract)

                                                                                                                          Есть маленький такой нюанс. Книга Макконнела — про совершенный код. Вы не используете практики, направленные на чистоту кода? Ок.

                                                                                                                          У Вас свой catch?

                                                                                                                          Я (например) слежу за тем, чтобы в catch и finally не было (предусмотримых) ошибок.

                                                                                                                          если после установки framework через Nuget, ничего не удалять, то можно приступать писать Command/Query

                                                                                                                          Да понятно, что можно. Надежнее это ваш код не сделает, к сожалению.