Pull to refresh

Comments 172

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

Сравнивать 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 к одному.
Сравнивать Controller с Dispatcher не верно, потому что они выполняются разные задачи, далее я приведу аргументы.

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

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

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

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

Так что что-то совершенно не понятны бонусы от того, что вы предлагаете, зато видно, что связность растет.
Ага, так это внезапно оказался сервисный слой (в терминах Эспозито). Тогда к нему нельзя обращаться напрямую из 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) для документации ( в статьей есть пример )

(я в этом комменте буду исходить из того, что 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).

На всякий случай, чтобы не было аргумента «можно же ввести контроллеры там, где они нужны» — это нарушает единообразность архитектуры, что резко усложняет ее анализ и валидацию (в том числе и статическую).
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();
     }

   } 


В 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?
Что плохого в необходимости создать экшн и контроллер? Чем это хуже необходимости создать команду?

Тем, что код 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)
Тем, что код 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.
Только в вашей конкретной реализации 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() ??
По этому, мы делаем ISessionContext, который потом подменяем для тестов.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вы можете аргументировать эту максиму?
Я не знаю ни одного фреймворка, который бы поддерживал глобальный 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 и ещё уменьшить кол-во кода.

К примеру MSpec

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

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

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

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

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

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

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

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

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

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

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

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

Я это и имел ввиду, что надо написать 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 и не думать о какой слой и зачем?
Я это и имел ввиду, что надо написать 10 тестов, что бы покрыть один сценарий из-за того, что надо все куда то пробрасывать.

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

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

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

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

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

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

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

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

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

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

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

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

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

Вот, за это отвечает ТОЛЬКО 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)
я все время старался минимизировать количество действий, что бы выполнить задачу, поэтапно убирая слои (или объединяя)

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

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

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

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

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

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

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

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

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

Значит, у вас неправильно декомпонована задача.
Быстрый 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 не важно, потому что он один для всех.
go to delcaration попадет в ACTION Add, который просто делает Push AddUserCommand

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

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

Все, что может 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();
        }
    }


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 смотрели?
Более того, сервис-локатор без возможности подмены:

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


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

Может больше конкретики?
Подменять можно 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), что затрудняет и рефакторинг (попробуйте найти все команды, использующие конкретный бизнес-сервис), и тестирование (попробуйте угадать, какие зависимости нужны конкретной команде для тестирования). Впрочем, я повторяюсь, у Симана это все описано.
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, но отражает общую картину.
Это используется только в рамках метода Push, который находится в DefaultDispatcher,

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

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

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

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

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

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

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

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

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

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

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


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

Вот пример 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();
    }
 }
Угу.

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

IoCFactory.Instance.TryResolve()

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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?
Зачем передавать locator в ctor, если есть Singleton?

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

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

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

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

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

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

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

нету разницы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Хорошо, вот пример кода для решения одной задачи
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. Если есть возможность упростить и сократить код, но для этого надо использовать «анти-патерн» я за, потому что я преследую продуктивность, а не лабораторные по паттернам пишу.
Я попробую объяснить, какие проблемы возможны с использованием вашего подхода:

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

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

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

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

Это готовое решение для конкретного проекта, то есть логика сохранения 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?.. Я думаю не стоит.
Это готовое решение для конкретного проекта, то есть логика сохранения Product будет в каждом проекте своя.

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

Если это так, то всё ОК и я с вами согласен с вашим подходом.
вас не заботит 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) или же передаем явно.

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

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

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

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

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

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

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

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

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

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

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

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

Ок, если Вам надо получить сторонний сервис, то мы его выделяем в 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?
Сервис, отправляющий сообщения в произвольные соцсети, выглядит с использованием 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 из коробки это не поддерживает, а писать расширение или описывать, как это делается через регистрацию, мне лень)
Регистрация ничем не отличается от вашей.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Выше об этом

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ну а как нет, если мне в 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-фреймворк, поэтому ошибка отложена). Нет никакого способа это статически проконтролировать.
В вашем же коде в фреймворке — хуже (там Lazy, причем реализованный вручную, а не через IoC-фреймворк, поэтому ошибка отложена)

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

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

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

И к чему?

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

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

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

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

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

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

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

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

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

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

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

Удачи.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Сериализация — это сбор всех публичных полей, которые имеют 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 и эти решения работали, НО теперь мы пишем меньше кода.
Сериализация — это сбор всех публичных полей, которые имеют Get и Set, что бы потом превратить в Json/xml/binary. Поле Repository — это protected, который не попадает под сериализацию или десериализацию

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Это другие задачи, мне надо все время выполнять, потому что расписание будет через 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?
Таймеры тоже постоянно работают.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это messaging.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

У Вас свой catch?

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

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

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

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

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

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

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

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

У Вас свой catch?

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

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

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

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

Не надо мешать все в кучу. Пример, у Вас есть запрос session.GetById(Id).Name, Вы будете проверять объект после GetById на null?

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

Понятно, что баги бывают, но о них сообщают на bugtracker и разработчики их фиксят (покрывают тестами), НО не ставят try {} catch.

fail never.

Это называет скрытые ошибки, то есть Вы предполагаете пустой Catch

fail early

Код dipsatcher Push использовался тысячи раз и при правильной настройки все ок, но если все же найдут ошибки, то их надо просто пофиксить.

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

В рамках Command/Query пишите логику обработки Ваших ошибок, но зачем Вам думать о ошибках в dispatcher.Push/Query, где код протестирован как Unit test, так и в проектах.

Книга Макконнела — про совершенный код

Вы считаете кого то удивите, называя и так известные книги, о которых знает каждый разработчик?

P.S. На все отвечать смысла не вижу, потому что остальное все в духе «Я прочитал кучу умных книг и использую все паттерны и пишу самый безопасный код» (Но кроме пары строк Вы так и ничего не показали своего)
Пример, у Вас есть запрос session.GetById(Id).Name, Вы будете проверять объект после GetById на null?

Зависит от контракта и владельца GetById. Если контракт явно гласит, что всегда возвращается не-null и владелец кода — я или моя команда, то нет. Если контракт явно гласит, что всегда возвращается не-null, и владелец кода — кто-то другой, то будет Debug.Assert (хотя последнее время я ленюсь и ставлю CheckNull все равно). Наконец, если контракт ничего не говорит (или тем более явно говорит, что может быть null) — будет CheckNull.

Понятно, что баги бывают, но о них сообщают на bugtracker и разработчики их фиксят (покрывают тестами), НО не ставят try {} catch.

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

Это называет скрытые ошибки, то есть Вы предполагаете пустой Catch

О нет, пустым catch там не отделаешься. Fail never/robust programming — это весьма сложный набор подходов, и, будем честными, я половину из них не знаю и не применяю. По счастью, мне не нужно писать такого кода.

Код dipsatcher Push использовался тысячи раз и при правильной настройки все ок, но если все же найдут ошибки, то их надо просто пофиксить.

Ну вот я нашел как минимум две.

В рамках Command/Query пишите логику обработки Ваших ошибок, но зачем Вам думать о ошибках в dispatcher.Push/Query, где код протестирован как Unit test, так и в проектах.

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

Вы считаете кого то удивите, называя и так известные книги, о которых знает каждый разработчик?

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

Вот это поведение, Вы проверили на null и там null, что дальше? Поймите и так вылетит NullRefrenceException, который будет сигнализировать о НЕ рабочем коде и тут нечего перехватывать, потому что это фатальная ошибка (Вы же не подмените ID? ), так что надо просто показать Sorry используя глобальный (global.asax) перехватчик.

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

Объясните, зачем мне усложнять код ради того, что бы обработать ошибку (скорей не желания правильно использовать framework) отсутствия Event Broker, которая и так всплывет?

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

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

Ну вот я нашел как минимум две.

Вы что издевайтесь, я же Вам сказал не удаляйте файл Bootstrapp.cs и все будет работать, ни каких ошибок не будет, потому что IEventBroker будет не Null.
1.Если я добавлю if( eventBroker == null) throw new NullReferenceException() что поменяется?
2.Если я добавлю if(eventBroker == null) { eventBroker = new DefaultEventBroker(), то будет скрытие ошибки, но в рамках Event Broker это «прокатит», а вот IRepositoru, IUnitOfWork я уже не смогу подменить.

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

Я считаю у меня хороший код, он рабочий, он тестируется и т.д., а то что я не страдаю «параноей» обвешивать каждую строчку лишним IF, который будет предугадывать ВОЗМОЖНЫЕ ошибки, это мое дело.

P.S В framework десятки тысяч строк кода и они писались много лет, поэтому надо понимать для каких целей, что делалось и в каком контексте.
Откройте Nhibernate, я не думаю, что они все ошибки перехватывают, но это рабочий (хорошо) инструмент и ни кто не будет от него отказываться если авторы пропустили ВОЗМОЖНЫЙ баг.
Вот это поведение, Вы проверили на null и там null, что дальше? Поймите и так вылетит NullRefrenceException, который будет сигнализировать о НЕ рабочем коде и тут нечего перехватывать, потому что это фатальная ошибка (Вы же не подмените ID? ), так что надо просто показать Sorry используя глобальный (global.asax) перехватчик.

Понятно. Вам, похоже, никогда не приходилось пытаться понять, что произошло на удаленном сервере, где нет дебаггера, по сообщению «NullReferenceException in line 86», когда строка 86 имеет вид
return GetEmployee(code.Trim()).Address.City.ToUpper().


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

Более того, для конкретно взятого GetXById с документированным поведением «вернуть null, если сущность не найдена» проверка нужна тем более, чтобы вместо нуллрефа показать сообщение «Сущность X с идентификатором Y не найдена».

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

Объясните, зачем мне усложнять код ради того, что бы обработать ошибку (скорей не желания правильно использовать framework) отсутствия Event Broker, которая и так всплывет?

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

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

Заказчику вы мне тоже предлагаете сказать «это у сторонних разработчиков ошибка»? Не, так не работает, заказчику все равно, у кого ошибка. То, что ошибка у сторонних разработчиков, может дать мне немного больше времени на ее починку, вот и все; но чинить-то все равно придется.

Вы что издевайтесь, я же Вам сказал не удаляйте файл Bootstrapp.cs и все будет работать, ни каких ошибок не будет, потому что IEventBroker будет не Null.

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

1.Если я добавлю if( eventBroker == null) throw new NullReferenceException() что поменяется?
2.Если я добавлю if(eventBroker == null) { eventBroker = new DefaultEventBroker(), то будет скрытие ошибки,

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

Снова вежливость.
Понятно. Вам, похоже, никогда не приходилось пытаться понять, что произошло на удаленном сервере, где нет дебаггера, по сообщению «NullReferenceException in line 86», когда строка 86 имеет вид

Если null это допустипые значения, то можно
GetEmployee(code.With(r=>r.Trim())).With(r=>r.Address).With(r=>r.City).Recovery(string.Empty).ToLower()

Но, конечно куда лучше обвешать, каждое обращение через IF и выбрасывать exception. Единственно, где стоит выкинуть exception и то для отображения его на UI это Not found employee by Code, но остальные обращения можно через безопасный контекст.

Заказчику вы мне тоже предлагаете сказать «это у сторонних разработчиков ошибка»? Не, так не работает, заказчику все равно, у кого ошибка. То, что ошибка у сторонних разработчиков, может дать мне немного больше времени на ее починку, вот и все; но чинить-то все равно придется.

Вы не будете фиксить Nhibernate если там ошибка, а найдете просто альтернативный сценарий, скажем использовать ExecuteSql.

Более того, для конкретно взятого GetXById с документированным поведением «вернуть null, если сущность не найдена» проверка нужна тем более, чтобы вместо нуллрефа показать сообщение «Сущность X с идентификатором Y не найдена».

Зачем выкидывать exception, если разработчик сам при НЕОБХОДИМОСТИ может проверить на Null и если НАДО выкинуть такой exception. Вы рассуждаете с точки зрения готового приложения, потому что я не думаю, что будет очень красиво
try
{ session.GetById(id)}
catch(NotFoundEntiityException ex)
{
throw new MyBussinesExceptioon(«Your custom message»)
}
1. это ресурсы
2. Это длиннее
Вы посмотрите на любой ORM и его методы GetById НИ кто не выбрасывает exception.

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

Те ошибки которые не видны считаются багами и их находят в процессе тестирования.

бросит ошибку некорректной регистрации сервиса

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

P.S. Из Ваших примеров я могу судить о постоянно избыточных конструкция, которые направленны на предугадывания ошибок и Вы советуете мне, как делать повторно используемые библиотеки, НО сами ничего такого не делали. Если Вы считаете свой код хорошим это ничего не значит, например по мне GetEmployee(code) говорит о многом, например то, что бы читать Ваш код нужно постоянно ходить через Go to declaration.
Если null это допустипые значения, то можно

А если нет?

Единственно, где стоит выкинуть exception и то для отображения его на UI это Not found employee by Code,

Вот внезапно и выяснилось, что проверять результат GetById надо.

Вы не будете фиксить Nhibernate если там ошибка, а найдете просто альтернативный сценарий, скажем использовать ExecuteSql.

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

Зачем выкидывать exception, если разработчик сам при НЕОБХОДИМОСТИ может проверить на Null и если НАДО выкинуть такой exception.

Я про разработчика и говорю.

Вы посмотрите на любой ORM и его методы GetById НИ кто не выбрасывает exception.

Потому что для них определено поведение «вернуть null, если не найдено». А в других случаях (например, сущность не того типа) — вполне себе летят exception.

Те ошибки которые не видны считаются багами и их находят в процессе тестирования.

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

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

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

НО сами ничего такого не делали.

Это утверждение неверно.

по мне GetEmployee(code) говорит о многом, например то, что бы читать Ваш код нужно постоянно ходить через Go to declaration.

Эээ… Вам из этого названия не понятно, что этот метод возвращает сотрудника по коду? Ровно одна вещь, которая непонятна из этой строки — это может ли быть null в результате, но это фундаментальная проблема .net вообще (и она решается конвенциями и аннотациями).
То, что и как я буду делать, будет зависеть от причины ошибки. Соответственно, чем быстрее я ее определю, тем быстрее я решу проблему.

Вы будете гуглить и выйдете на Stackoverflow, а если нет, то плюнете и напишите обходной вариант.

Это утверждение неверно.

Покажите

Вот внезапно и выяснилось, что проверять результат GetById надо.

Ключевое слово внезапно, потому что где нужно там и проверяйте, а выбрасывать Exception это не верно, потому что это более узкий вариант, нежели return null

А в других случаях (например, сущность не того типа) — вполне себе летят exception.

Вы тип сами указываете, так что давайте реальный пример, когда там возникнет Exception,

Эээ… Вам из этого названия не понятно, что этот метод возвращает сотрудника по коду? Ровно одна вещь, которая непонятна из этой строки — это может ли быть null в результате, но это фундаментальная проблема .net вообще (и она решается конвенциями и аннотациями).

Мой вариант
Repository.Query(whereSpecification:new EmployeeByCode(code)).FirstOrDefault()
Зачем ещё выделять в метод и выдумывать ему название? Если у Вас будут разные варианты запросово Employee, то Вам надо имена давать разные (GetEmployeeByActive, GetEmployeeBySearch ), а тут на основе Specification видно все.

Во-вторых, есть ошибки, которые не являются багами, а являются ожидаемым поведением системы, и их тоже необходимо обрабатывать.

Отсутствие Event Broker это не ожидаемое поведение.

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

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

Вот именно. И поверьте, что гуглить по NHibernate EntityNotFoundException проще, чем по NHibernate NullReferenceException.

Покажите

NDA.

Ключевое слово внезапно, потому что где нужно там и проверяйте, а выбрасывать Exception это не верно, потому что это более узкий вариант, нежели return null

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

Вы тип сами указываете, так что давайте реальный пример, когда там возникнет Exception,

А это и есть реальный пример, из внутренностей EntityFramework:
if (obj != null && !(obj is TEntity))
        throw Error.DbSet_WrongEntityTypeFound((object) obj.GetType().Name, (object) typeof (TEntity).Name);


Зачем ещё выделять в метод и выдумывать ему название?

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

Отсутствие Event Broker это не ожидаемое поведение.

А закончившаяся память? А таймаут БД? Оборвавшееся соединение с БД? Ошибка валидации внутри события? Ошибка отправки email?

А то Вы прямо уже заранее говорите, что он не рабочий.

Я не говорю, что фреймворк не рабочий. Я указываю на те места его реализации, которые я по тем или иным причинам считаю неудачными.
Вот именно. И поверьте, что гуглить по NHibernate EntityNotFoundException проще, чем по NHibernate NullReferenceException.

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

А это и есть реальный пример, из внутренностей EntityFramework:

А как может быть, что Вы делаете запрос на TEntity по Id, а Вам придет другой тип? Над EF работала команда и у них есть время писать комменты, кучу кода и тд., но у меня нет.

более узкие варианты оказываются эффективнее.

Если Вы пишите код под один проект

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

Я считаю его избыточным, Вот видите, теперь у Вас появился аргумент, что ВАМ так нравится и комфортно, а то что мне удобно с Service Locator и без «паранойи» к ошибкам.

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

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

А закончившаяся память? А таймаут БД? Оборвавшееся соединение с БД? Ошибка валидации внутри события? Ошибка отправки email?

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

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

Вы, похоже, не поняли. Я не говорю о коде без ошибок (такого не бывает), я говорю о том, как обрабатывать ошибки (которые неизбежно будут).

А как может быть, что Вы делаете запрос на TEntity по Id, а Вам придет другой тип?

Мне не придет — именно благодаря этой проверке.

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

А как же «много лет», которые писался ваш фреймворк? Ну и да, экономия от точной локализации ошибок, поверьте мне, выше, чем потери на расстановку маркеров.

Если Вы пишите код под один проект

Нет. Если я точно знаю сценарий использования.

Я считаю его избыточным

Это не имеет отношения к непонятности. Вам что-то непонятно в том, что делает этот метод?

Это ошибки которые будут в методах Execute и каждый сам решает, как их обработать.

Угу. Обработчик события пишет один разработчик, который решил, что ошибку обрабатывать не надо, а получит ее другой ничего не подозревающий разработчик во время Push. Как ему угадать, что произошло?

Frameowkr не отправляет письма. не отвечает за таймаут и т.д.

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

Давайте так, то что Вы говорите о проблеме с «возможным» отсутствие Event Broker, то я могу добить проверку на null и выкидывать логическую ошибку, но это не так важно, а вот если у Вас есть сценарии, которые не может выполнить framework, то это проблема.

А как же «много лет», которые писался ваш фреймворк? Ну и да, экономия от точной локализации ошибок, поверьте мне, выше, чем потери на расстановку маркеров.

Вы предлогаете поверить Вашему NDA? Я тоже могу травить байки о безопасном коде и о том, что все ошибки обработаны, но смысла не вижу. Вы знаете, что такое Deadline? Вы понимаете, что frameowrk это open source, а значит и делался бесплатно.

Это не имеет отношения к непонятности. Вам что-то непонятно в том, что делает этот метод?

В том, что надо переходить во внутрь, что бы понять что он на самом деле делает (критериев может быть много)
примечание: GetEmployee(code,name,email.isActive) — как Вам это ?

Чем дальше, тем меньше я вижу вещей, за которые отвечает ваш фреймворк.

framework это каркас, так что Вы не правильно понимает смысл.

Угу. Обработчик события пишет один разработчик, который решил, что ошибку обрабатывать не надо, а получит ее другой ничего не подозревающий разработчик во время Push. Как ему угадать, что произошло?

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

то что Вы говорите о проблеме с «возможным» отсутствие Event Broker, то я могу добить проверку на null и выкидывать логическую ошибку,

Вы, похоже, так и не прочитали, что там надо было делать на самом деле. Удивительно.

если у Вас есть сценарии, которые не может выполнить framework, то это проблема.

Например, ошибка в любом обработчике события.

Вы знаете, что такое Deadline?

Знаю.

Вы понимаете, что frameowrk это open source, а значит и делался бесплатно.

Во-первых, open source не означает, что делалось бесплатно. Во-вторых, если бесплатно — то откуда дедлайны? Что-то у вас не сходится.

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

А вот это уже передергивание. Критерий ровно один, и он показан в примере кода. GetEmployee(code,name,email.isActive) придумали вы, я такого примера не приводил.

framework это каркас, так что Вы не правильно понимает смысл.

Каркас ни за что не отвечает?

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

Конечно, не влияет. Зато то, как Push обрабатывает ошибки внутри себя — сильно влияет на команду.
Например, ошибка в любом обработчике события.

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

Во-первых, open source не означает, что делалось бесплатно. Во-вторых, если бесплатно — то откуда дедлайны? Что-то у вас не сходится.

Мы используем frameowork в своих проектах и как раз там и есть дедлайны (а они давят на Framework, что бы быстрее появились новые фичи или фиксы)

А вот это уже передергивание. Критерий ровно один, и он показан в примере кода. GetEmployee(code,name,email.isActive) придумали вы, я такого примера не приводил.

но а как тогда будет? или Вы для одного параметра будете метод делать, а для двух нет?

Вы, похоже, так и не прочитали, что там надо было делать на самом деле. Удивительно.

Если Вы про TryResolve заменить на Resolve, то я уже написал, что разницы особо нету, ДА и в тысячный раз повторю, НИКОГДА ПРИ ПРАВИЛЬНОМ использование не будет Event Broker null, а те кто специально не зарегистрируют Event Broker меня не волнуют.

Конечно, не влияет. Зато то, как Push обрабатывает ошибки внутри себя — сильно влияет на команду.

Если Вы делаете в Command throw exception, то он пойдет вверх и остановит обработку, НО если вы перехватите exception и поставите пустой catch, то Push продолжит работу. Вопрос, чем мешает PUSH?

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

Вот мы и окончательно выяснили, что ошибки в диспетчере бросаются как вам удобно и комфортно, а не «только если их кинет команда». Q.E.D.

Мы используем frameowork в своих проектах и как раз там и есть дедлайны (а они давят на Framework, что бы быстрее появились новые фичи или фиксы)

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

но а как тогда будет? или Вы для одного параметра будете метод делать, а для двух нет?

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

Если Вы про TryResolve заменить на Resolve, то я уже написал, что разницы особо нету

Поведение только разное фнудаментально.

Если Вы делаете в Command throw exception, то он пойдет вверх и остановит обработку, НО если вы перехватите exception и поставите пустой catch, то Push продолжит работу. Вопрос, чем мешает PUSH?

Вы не понимаете. Проблема в том, что я, как человек, который вызывает диспетчер (и кладет туда команду на выполнение), не знаю, какой код в реальности будет выполнен (из-за того, как вы обрабатываете события); как следствие, я вообще никак не могу предсказать, какие ошибки я получу из Push.
А если вы используете фреймворк для коммерческой разработки, то неплохо бы писать его за деньги, не?

Это не Ваше наверно дело.

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

а как же тестирование сложных запросов? Вам не нравится идея использовать Specification, которые можно тестировать отдельно и упаковывать в них логику? И кстати опять у Вас слои, то есть как я и думал, что бы понять код надо пройти 5 Go to declaration

Вы не понимаете. Проблема в том, что я, как человек, который вызывает диспетчер (и кладет туда команду на выполнение), не знаю, какой код в реальности будет выполнен (из-за того, как вы обрабатываете события); как следствие, я вообще никак не могу предсказать, какие ошибки я получу из Push.

Выполнится только код в рамках Execute, остальной просто сопровождает (отвечает за Unit Of work, Repository ), но ни как не влияет. на его работу.

P.S. Спасибо за дискуссию, но время это ценная вещь, а игра в «придумай» аргумент уже надоела. Я так и не понял, что Вы хотели в итоге получить, потому что я все равно останусь при своем мнение (Вы для меня не авторитет) и Вас не переубедить (причина та же)
а как же тестирование сложных запросов?

Тестируются на массивах совершенно замечательно.

И кстати опять у Вас слои, то есть как я и думал, что бы понять код надо пройти 5 Go to declaration

Пример выше все демонстрирует: по названию метода (должно быть) понятно, что он делает. Ходить в код не нужно (и вредно).

Выполнится только код в рамках Execute, остальной просто сопровождает (отвечает за Unit Of work, Repository ), но ни как не влияет. на его работу.

Т.е. и события тоже выполнены не будут?
Т.е. и события тоже выполнены не будут?

Будут синхронно, я же говорю основная задач Event Broker разгрузка Command, но в последний версии Event Broker obsolete, потому что удобней использовать внутренний Dispatcher.

Тестируются на массивах совершенно замечательно.

У Вас поиск и там 20 критериев? Where(r=>r.IsActive && r.User.Id == userId && ( r.Date >= startDt || r.Date <= endDT). Вы наверно сложных условий не видели .,
примечание: надо так же заметить, что я не учитывал опциональные параметры, которые будут в If(startDt.HasValue) и т.д.

по названию метода (должно быть) понятно).

Вы придумайте название для метода с 10 критериями и тогда утверждайте. (для примера Выше дайте название).

Будут синхронно,

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

У Вас поиск и там 20 критериев?

Когда поиск и 20 критериев — надо тестировать сборщик критерия в первую очередь. И все равно результат сборки прекрасно тестируется на массиве.

Вы придумайте название для метода с 10 критериями и тогда утверждайте

Я не выставляю такие методы, считая это бессмысленным. Я не знаю задачи, которая бы этого требовала в такой формулировке.
Вам, похоже, никогда не приходилось пытаться понять, что произошло на удаленном сервере, где нет дебаггера, по сообщению «NullReferenceException in line 86»,

Когда пишутся данные для логирования ошибок в глобальном обработчик, то можно получить текущий instance Command/Query и в отчет включить все значения свойств, НО поскольку у Вас слои и нету четких ответственных за код, то да, Вам придется повозится.
примечание: зная Code можно найти (или наоборот понять, что его нет) Employee и проверить все Address.City и т.д. на наличие в базе, что сразу скажет в чем дело.
Когда пишутся данные для логирования ошибок в глобальном обработчик, то можно получить текущий instance Command/Query


Мне прямо интересно — как же вы в глобальном обработчике ошибок (при вашей нынешней стратегии обработки ошибок) получите текущую команду? (я уж молчу про то, что ошибки могут быть в событиях, а могут быть вообще в инфраструктурном коде)

в отчет включить все значения свойств

На какую глубину будете дерево обходить? А локальные переменные?

зная Code можно найти (или наоборот понять, что его нет) Employee и проверить все Address.City и т.д. на наличие в базе, что сразу скажет в чем дело.

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

Вообще, конечно, любопытно.

Вы все хвалитесь тем, что dependency injection требует больше кода, но почему-то умалчиваете тот факт, что для реализации service locator вам понадобилось добавить в свой фреймворк пару лишних классов и интерфейсов (и это не считая провайдеров под каждый используемый IoC-фреймворк), в то время как (для практически всех сценариев) Dependency Injection подключается прозрачно для фреймворка (т.е. не требуя ни одной строчки кода вообще).

Впрочем, даже если вы так привязаны к ServiceLocator, что ж вам мешало взять готвый Nuget-пакет? И с монадами то же самое… неудивительно, что у вас в проекте «десятки тысяч строк кода».
Впрочем, по поводу Грега Янга все-таки поправлюсь.

Сам Янг считает, что CQRS — это только и исключительно разделение потоков данных на запись и на чтение. Сообщения, события и прочие детали реализации не являются обязательной частью CQRS — это просто некие типовые решения, которые CQRS поддерживают. Более того, с точки зрения Янга CQRS — это вообще не архитектура и не паттерн, это всего лишь концепция; и очень простая концепция.
ИМХО, но Паттерны это просто удачное решение, которое нашел другой программист и потом задокументировал, НО всегда есть вероятность найти ещё более удачное )

P.S. Конечно не стоит идти от противного и выдумывать, что угодно лишь бы не как у всех.
Для того, чтобы утверждаеть, что найденное решение — более удачное, нужно знать достоинства и недостатки существующих.
Давайте я еще раз попробую объяснить, чем плоха идея «давайте откажемся от контроллеров». Возьмем для примера команду, которая опирается на какие-то данные текущего пользователя (например, команду AddDocument, которая автоматически проставляет текущего пользователя автором документа).

«Очевидно, что» эта команда зависит от UI-контекста. В обобщенном (применимом для всех) контексте такой команды быть не может, потому что в обобщенном контексте не бывает текущего пользователя, поэтому в обобщенном контексте может быть только команда AddDocument(authorId). Соответственно, обработчик команды AddDocument просто инициирует вызов AddDocument(userId), передавая туда в качестве параметра информацию из контекста.

Возникает вопрос: а где разместить первую команду и ее обработчик? Вопрос не праздный, потому что с точки зрения поддерживаемости все команды и обработчики общей применимости должны быть в отдельной сборке; а с точки зрения масштабируемости они все должны быть развертываемы на отдельной ноде. Если вы внесете вашу контекстно-зависимую команду в эту сборку — то вы нарушите SRP (а все потребители этого кода буду вынуждены тащить лишние ненужные им статические зависимости навроде System.Web). А если вы внесете эту команду на ноду общего пользования — то вы будете вынуждены добавить ноде состояние, что отрицательно сказывается на производительности.

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

Ну и да, это у вас еще команда может возвращать результат. На самом деле, в CQRS такого быть не должно (это нарушает, собственно, разделение чтения и записи), а все последствия выполнения команды вы получаете последующими событиями, и вот эту корреляцию (вместе с обработкой разных событий) вам тоже нужно где-то держать. И конечно это все еще сильно отличается для веб-приложений с доминирующим js (там вообще можно на SignalR события передавать), и приложений с равномерным распределением ответственностей.
Я уже описывал вариант с ISessionContext, который при использование Disaptcher в другом context можно подменить на WinSessionContext, опят же если это будет нужно, потому что Winform приложение скорей всего будет вызывать Ваш Controller (в моем случаи MVD).

ISessionContext, который при использование Disaptcher в другом context можно подменить на WinSessionContext,

А какой «SessionContext» вы будете использовать в сервисе, который добавляет документы для абстрактных пользователей?

inform приложение скорей всего будет вызывать Ваш Controller

Это почему это вдруг?
А какой «SessionContext» вы будете использовать в сервисе, который добавляет документы для абстрактных пользователей?


Мы говорим не о типе пользователей, а о местах где идет вызов dispatcher. К примеру если Вы вызываете из Web то у Вас будет WebSessionContext, но если из Windows Service, то там WinSessionContext и т.д.

Это почему это вдруг?

Затем, что бы использовать уже существующие методы, как API для сторонних приложений.
К примеру если Вы вызываете из Web то у Вас будет WebSessionContext, но если из Windows Service, то там WinSessionContext и т.д.

В windows service не должно быть контекста, потому что она не работает из-под пользователя. Грубо говоря, агент, который создает документы разным пользователям, не может иметь текущего пользователя.

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

В этом случае я буду использовать иначе спроектированную систему с другим API.
Sign up to leave a comment.

Articles