Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
Но это так только в том случае, если вы так решили; и именно вы породили лишнюю абстракцию в виде «диспетчера» (к которому обращается контроллер)
Не видя кода этого «диспетчера», сложно судить, но почему просто не внести его в контроллер?
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.")
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 });
}
}
Сравнивать Controller с Dispatcher не верно, потому что они выполняются разные задачи, далее я приведу аргументы.
Dispatcher можно использовать в background service, console application, Win/Wpf form, потому что он не завязан на особенности работы Web.
Ага, так это внезапно оказался сервисный слой (в терминах Эспозито). Тогда к нему нельзя обращаться напрямую из view, потому что вы нарушаете изоляцию. Да, бывает так, что контроллеры при работе с сервисным слоем выглядят избыточными, но их задача — изоляция одного уровня (UI) от другого (бизнес)
Так что что-то совершенно не понятны бонусы от того, что вы предлагаете, зато видно, что связность растет.
Url.Dispatcher().Query(new GetMyEntity<T>(){ Status = EntityOfStatus.New}).AsJson()
Url dispatcher строит адрес, по этому Url.Dispatcher.Push(new AddUserCommand()) это тоже самое, что Url.Action(«Add»,User"), но с тем отличием, что не надо создавать Action.
Убираем дубляж в коде, потому что заменяем Action, на Url.Dispatcher
Типизированный синтаксис для построения адреса
Url.Action<TicketController>(t => t.View(ticketId))
Быстрый go to declaration сразу из view
Каждое действие на странице (view) проецируется на Command/Query, что позволяет общаться терминами предметной области проекта
Возможность построить карту сайта на основе CQRS ( Command/Query) для документации ( в статьей есть пример )
IEnumerable<Order> GetPlacedOrders(). Проходит время, бизнес становится территориально распределенным, и теперь каждый менеджер должен видеть только те заказы, которые относятся к курируемому им подразделению. Тут мы сразу скажем, что мы умные, и вместо того, чтобы сделать операцию GetPlacedOrdersByDepartment, сделаем GetPlacedOrdersForManager, исходя из того, что логика вычисления доступных заказов может потом меняться, а вот сценарий использования — уже нет. Так или иначе, контракт операции поменялся (IEnumerable<Order> GetPlacedOrdersForManager(managerId)). Что происходит дальше?if (order.IsVip) row.Color = VipColor; во viewmodel добавляется атрибут IsVip, вычисляемый внутри viewmodel на основании переданной доменной модели. Это традиционный подход. А у вас? Либо (при неизменности модели) необходимо внести логику во view (привет тестируемости), либо внести прослойку из контроллера-viewmodel (еще один вариант — промежуточная модель прямо поверх возвращаемого диспетчером результата, с помощью метода расширения, но тоже увеличивается количество кода во view).Url.Dispatcher.Push(new AddUserCommand()) это тоже самое, что Url.Action(«Add»,User").
Это не то же самое. В первом случае вы обращаетесь напрямую к сервисному слою, во-втором — к UI-слою.
Это не преимущество вашего дизайна, в «обычном» MVC так тоже можно — хелпер пишется за несколько часов, после чего код выглядит так:
Url.Action<TicketController>(t => t.View(ticketId))
А вы, напротив, нарушаете эту изоляцию, и напрямую связываете компоненты UI (например, view) с сервисным слоем и доменной моделью.
(еще один вариант — это «умные» query, которые при проходе через сервер сами дособерут нужную информацию из контекста, но это означает, что query специфична для ui
Во view добавляется условие (грубо) if (order.IsVip) row.Color = VipColor; во viewmodel добавляется атрибут IsVip, вычисляемый внутри viewmodel на основании переданной доменной модели. Это традиционный подход. А у вас?
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»)
Создаем ISessionContext и далее SessionContext.Current.UserId. Поле Current это Singleton, который можно подменить через IoC, если надо вместо Cookies использовать mock для тестов или иную реализацию.
Query возвращает данные, какие хотите, так что я не понял, в чем подвох.
Что плохого в необходимости создать экшн и контроллер? Чем это хуже необходимости создать команду?
HttpContext.Current в старых вебформах
«Подвох» в том, что как только Query начинает возвращать данные, подогнанные под формат конкретного представления, как она тоже начинает терять универсальность.
Их теперь нельзя использовать повторно в других сценариях и контекстах. В чем выигрыш по сравнению с обычными controller/action?
Тем, что код Command Вам все равно надо написать, а без Action можно обойтись
Current это значит текущий и у каждого потока он будет свой. Все это есть в asp.net mvc
Напишите другой Query, а общие вынесите в базовый.
Action нельзя использовать повторно в отличии от Command/Query, которая может быть частью Composite
Вам все равно придется написать базовый Controller, где будет метод Run/Execute, который откроет Unit of Work или же открывать UoW для каждого (можно открывать для Begin и End request, но это вообще не универсально) Action.
но опираетесь на N-Layer подход, что в корне другое (в статье про CQRS я привел сравнение N-layer vs CQRS)
Только в вашей конкретной реализации CQRS.
Вы внимательнее код почитайте? Это свойство не рекомендуется к использованию, поскольку снижает тестируемость.
const string userid = "userId";
var sessionContext = Pleasure.MockAsObject<ISessionContext>(s=> s.SetupGet(r => r.UserId)
.Returns(userid));
IoCFactory.Instance.StubTryResolve(sessionContext);
Повторное использование через наследование? Плохая идея.
А надо? Ну и да, традиционный способ решения этой проблемы — это вынесение прикладного сервиса.
Не-а. CQRS не противоречит n-layer.
По этому, мы делаем ISessionContext, который потом подменяем для тестов.
Лет 7 назад, можно было сказать, что asp это традиционный способ разработки веб сайтов для MS
То есть, Вы пишите UserService.Add в котором вызываете new AddUserCommand() ??
… и следите за тем, чтобы каждый тест корректно регистрировал и разрегистрировал статические контекста, ага. Спасибо, я с тех пор как потратил пару часов на отладку десятка тестов, который падал в произвольном порядке именно из-за статических контекстов, больше стараюсь их не видеть никогда.
Нет, я кидаю AddUserCommand, которая (может) обрабатываться в слое, отличном от того, который ее кинул.
Многие framework поддерживают глобальные SetUp и TearDown ( в частности MSpec ), так что это не проблема.
А как же Unit Of Work, кто его открывает?
Потом вопрос атомарности Command, если они «шарятся» по разным слоям, то это тоже не совсем ясно.
Вы постоянно ссылаетесь на «какие» то слои, хотя чем их меньше, тем и лучше.
Я не знаю ни одного фреймворка, который бы поддерживал глобальный teardown,
Тот, кому он нужен. Если он нужен.
Вы можете аргументировать эту максиму?
К примеру MSpec
Он всегда нужен, только если у Вас Command не работает с базой
зачем все эти слои, если можно убрать всех посредников и оставить Dispatcher и Command/Query
Проще навигация, потому что Command/Query это атомарная задача, которая имеет логическое имя.
Unit Test не превращается в «зоопарк» из Mock IUserFacade, потом IUserService, потом IUserRepository и т.д.
Появляется возможность использовать MVD и ещё уменьшить кол-во кода.
Каждый юнит-тест тестирует ровно один слой, поэтому никакого зоопарка там нет.
Уменьшение количества кода — не догма.
А вообще слои нужны для одной простой вещи — управления сложностью через инкапсуляцию. Создав «слой» работы с БД, я больше не думаю о самой БД, я от нее абстрагирован. Создав «слой» работы с бизнесом, я больше не думаю про то, как бизнес работает с БД, я абстрагирован и от этого. Information hiding в чистом виде.
Вы работы с базой без UoW не представляете? Про неявные UoW вы не слышали? Про то, что UoW может быть ограничен внутри слоя работы с данными — тоже?
dispatcher.Push(composite =>
{
composite.Quote(step1);
composite.Quote(step2);
composite.Quote(step3);
});
Вы работы с базой без UoW не представляете?
Я это и имел ввиду, что надо написать 10 тестов, что бы покрыть один сценарий из-за того, что надо все куда то пробрасывать.
Если бы его создал «кто то», может это и имело смысл, а так сами создали, а потом больше о нем не думаете, но так не бывает.
То есть Вы считаете, что каждый слой сам решает, когда и зачем ему открывать это очень однотипное и прозрачное решение?
Какой слой, что будет делегировать?
Можно без, только для Query в ReadUncommited, а иначе как гарантировать успешность завершенных действий?
Разве плохо оперировать AddUserCommand, ApproveChangeStatusCommand, GetPatientBySearch и не думать о какой слой и зачем?
Зато вы имеете точную локализацию ошибок. Можно ведь не писать десять тестов, а написать один компонентный, просто локализация будет хуже.
Хорошо, только так не бывает. Команда не выполняется магически и сама собой, она будет выполнена где-то и кем-то, и это нужно учитывать (в том числе и при их композиции).
А вы считаете, что из вашего сценария понятно, когда и как должен быть открыт UoW, и почему так должно быть?
public void Edit(Guid id, UserInput input)
{
using (IUnitOfWork unitOfWork = CreateUnitOfWork())
{
User user = this._userRepository.Find(id);
user.SetType(input.Type);
unitOfWork.Commit();
}
}
В команде бывает больше одного разработчика вообще-то. И разделение ответственности по слоям очень неплохо работает
Ну и да, я сам тоже прекрасно абстрагируюсь — если я (для конкретного проекта) написал data facade месяц назад, то сейчас мне все равно, как он работает, главное, что он работает — а дальше я просто проверяю правильно делегирования ему вызовов.
я все время старался минимизировать количество действий, что бы выполнить задачу, поэтапно убирая слои (или объединяя)
Вот, за это отвечает ТОЛЬКО dispatcher
По поводу разделения, я так понимаю беседа будет «Мне нужно доделать User Service, скоро там будет Data Facade завершен ?».
Может Вы имеет «слои», как части приложения, то есть Admin, Private Office и т.д, тогда да, но не иначе, потому что разработчик все равно должен написать каждый слой, так что тоже самое, но больше писать.
что бы понять, как работает код, который решает задачу, нужно пробежать 10 слоев,
Быстрый go to declaration сразу из view. Работает и в «обычном» MVC.Url.Action("Add","Controller")
Url.Dispatcher().Push(new AddUserCommand())
go to delcaration попадет в ACTION Add, который просто делает Push AddUserCommand
Понимаете, вы исходите из того, что контроллеры (всегда!) наивны. А это не так.
Url.Dispatcher().Push(new AddUserCommand()
{
AdrTypePrimary = Selector.Incoding.Cookie(Key)
})
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 и т.д
IoCFactory.Instance.TryResolve, что явно указывает на то, что у вас реализован сервис-локатор (со всеми его недостатками).что явно указывает на то, что у вас реализован сервис-локатор (со всеми его недостатками)..
А как без TryResolve?
На самом деле у нас не дискуссия, а просто каждый доказывает свою точку зрения, которую удачно применяет в проектах, но мы ушли от темы MVD и переключились на все аспекты сразу, что как мне кажется в комментариях не реально обсудить.
Лично я обсуждаю достоинства и недостатки вашего конкретного проекта. Сервис-локатор и невозможность использовать DI — (для меня) фундаментальный и критический недостаток.
А почему нельзя использовать DI, если Structure map это DI framework?
или что то ещё…?
Вы статью Симана прочитали?
For<TInterface>.Use<TImp>() если он один к одному ( для других случаев Named )Enum.GetValues(typeof(TEnum)) и потом IoCFactory.Instance.TryResolve<T>(enumValue).ShouldNotBeNull() var eventBroker = IoCFactory.Instance.TryResolve<IEventBroker>();
public class IoCFactory : FactoryBase<IoCInit>
{
static readonly Lazy<IoCFactory> instance = new Lazy<IoCFactory>(() => new IoCFactory());
public static IoCFactory Instance { get { return instance.Value; } }
}
Более того, сервис-локатор без возможности подмены:
IoCFactory.Instance.Initialize(init => init.WithProvider(new StructureMapIoCProvider(new WebIoCInit())));
Вы Симана читали? В код WebAPI смотрели?
Подменять можно Provider, а не сам Instance
ServiceLocator.SetCurrent(new StructureMapServiceLocator(new WebIoCInit()));
Может больше конкретики?
Url.Dispatcher().Push(new Command())), а доступ к инфраструктуре получают через глобальный локатор (например, DeleteEntityByIdCommand<TEntity> получает репозиторий через IoCFactory.Instance.TryResolve<IRepository>() — очень, кстати, много говорит о вашем коде то, сколько усилий нужно приложить, чтобы найти эту информацию, — вместо того, чтобы объявить параметр конструктора или вбрасываемое свойство IRepository<TEntity> Repository), что затрудняет и рефакторинг (попробуйте найти все команды, использующие конкретный бизнес-сервис), и тестирование (попробуйте угадать, какие зависимости нужны конкретной команде для тестирования). Впрочем, я повторяюсь, у Симана это все описано.IoCFactory.Instance.TryResolve()
очень, кстати, много говорит о вашем коде то, сколько усилий нужно приложить, чтобы найти эту информацию, — вместо того, чтобы объявить параметр конструктора или вбрасываемое свойство IRepository(TEntity) Repository), что затрудняет и рефакторинг (попробуйте найти все команды, использующие конкретный бизнес-сервис), и тестирование (попробуйте угадать, какие зависимости нужны конкретной команде для тестирования).
Service Locator is an Anti-Pattern
попробуйте найти все команды, использующие конкретный бизнес-сервис)
тестирование (попробуйте угадать, какие зависимости нужны конкретной команде для тестирования)
Это используется только в рамках метода Push, который находится в DefaultDispatcher,
Не надо ни каких TryResolve или зависимостей в ctor.
для Command/Query Repository доступен и unit of work открыт.
Мы используем IoC для того, что бы подменять реализации IRepository, IUnitOfWork, IDispatcher, ITemplateFactory и т.д.
ни каких зависимостей не надо
Давайте на простом примере. Вот есть сценарий регистрации пользователя: получили емейл и пароль пользователя, приложение должно проверить, что емейл корректен (для этого есть внешний сервис, выраженный в виде пары интерфейс/реализация), дальше сохранить данные в БД и выслать пользователю по email уведомление, что эккаунт создан (рассылка емейлов, конечно же, тоже реализована через сервис с интерфейсом и реализацией). Для простоты считаем, что это веб-сервис, поэтому интерфейс нас не интересует, мы просто получили два строковых значения, и бросили эксепшн, если что-то не так.
Там нет взаимодействия с внешними сервисами, о котором сейчас идет речь.
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?
поэтому давать на них ссылку без пояснений достаточно бессмысленно.
Это как-то делает service locator менее антипаттерном?
аргумент не убедительный для ухода от IoCFactory
Вы бы лучше привели пример, как реализовать подмену агрегатов (IRepository, IUnitOfWork и т.д. ) framework из вне (не меняя исходники) без Dependnecy Injection.
Во-первых, repository и UoW — это не агрегаты. А во-вторых, я-то как раз считаю, что это надо реализовывать с Dependency Injection, а вот у вас реализовано без.
Во первых repository и unit of work я рассматриваю в контексте внутренних инструментов framework, а не понятия «паттерн», так что я все правильно сказал.
Во вторых, Structure Map это DI framework, который мы используем в качестве одного из провайдеров, так что Вы бы аргументировали свои слова и объяснили почему же у меня нету DI?
Потому что dependency injection — это вбрасывание зависимости в потребителя снаружи (через через конструктор, свойство или параметр метода). А то, что у вас — это service location, получение потребителем зависимости через обращение к внешнему реестру (в вашем случае — IoCFactory.Current). На примерах показывать надо?
к примеру AbstractValidator ( fluent validation), он не поддерживает параметров
. Вы не забывайте, что не всегда можно указывать зависимости в ctor
На самом деле нету ни какой ризницы, как получить Instance через ctor или через singleton container,
почему Вам он так противен ?)
Я же вам дал ссылку на статью Симана; вы ее прочитали? Там все прекрасно показано, в том числе с примерами.
var order = new Order();
var locator = new Locator();
var sut = new OrderProcessor(locator);
sut.Process(order);
IoCFactory.Instance.StubTryResolve(mock.Object), но по скольку есть оболочки для тестов, где уже все настроено такие ситуации будут редкие.А лично мне сервис-локатор противен именно тем, что решения на его основе тяжелы в тестировании, и единожды этого наевшись, я больше возвращаться не хочу.
public MyClass(IService service)
{
this.service = service;
}
public MyClass()
{
this.service = IocFactory.Instance.TryResolve<IService>();
}
Зачем передавать locator в ctor, если есть Singleton?
Статья показывает, какой то примитивный вариант мелкого Container для хранения dictionary Type,Instance.
Если у Вас вопрос о тестах, то я приводил ссылки, как делать глобальный TearDown.
по скольку есть оболочки для тестов, где уже все настроено такие ситуации будут редкие.
нету разницы
Кстати, как на счет named, то есть когда Вам надо IocFactory.Instance.TryResolve(Provider.TwitterSdk)
Во-первых, все вменяемые DI-контейнеры поддерживают атрибуты для указания нужных именованных зависимостей. А во-вторых именованные зависимости — это design smell, если они вам понадобились, то (за небольшими исключениями) вы делаете что-то странное.
Нет. Во-первых, оболочки для тестов — это признак того, что код плохо тестируется. Во-вторых, оболочки хорошо пишутся для однородных задач, однако сама природа нашей работы такова, что задачи у нас разнородны.
Есть. Если в первом случае при тестировании я явно вижу, какие зависимости нужны классу для работы, могу легко их замокать и передать нужные, то во втором случае я могу это узнать только получив ошибку при выполнении теста или прочитав код. Непродуктивно.
Как раз оболочка подчеркивает стандарт кода, а у Вас я так понимаю, каждый класс по своему уникальный?
Ещё раз повторю в рамках incoding framework Command/Query имеет IRepositry, IUnitOfWork, по этому Вам не надо часто делать TryResolve.
Конечно куда лучше, копировать один и тот же ctor с IRepository или у Вас мало command, которые работают с базой?
Вы бы кстати привели пример бы своего кода, а то мы только мой разбираем.
… про которую я вам сказал, что это так не работает. Это типичный assembly teardown, он не вызывается после каждого теста.
Хорошо, делаем базовый класс MyTestWithClean, в котором описываем TearDown и потом используем его, как базовый,
Неудобно — слишком разветвленная цепочка наследования получится.
Да. Любое инфраструктурное навязывание базовых классов затрудняет работу.
Вы используете в своей реализации антипаттерн «Локатор сервисов». Использование этого подхода не всегда является плохой практикой, но в большинстве случаев так и есть.
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 });
}
}
@(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"))
P.S. Если есть возможность упростить и сократить код, но для этого надо использовать «анти-патерн» я за, потому что я преследую продуктивность, а не лабораторные по паттернам пишу.
Ваш код трудно использовать повторно, так как неясно какие зависимости ему нужны.
Например, если я захочу использовать ваш объект AddAlbumCommand в своем приложении
Из первого пункта вытекает второй: класс AddAlbumCommand трудно покрыть тестами, так как он зависит от объекта Repository, который не совсем ясно как подменить.
IoCFactory.Instance.StubTryResolve(myRepository.Object)
MockMessage<YourCommandOrQuery>
.When(instance)
.StubGetById(instance.ArtistId,artist)
.StubGetById(instance.GenreId,genre) // establish
should be save => mock.ShouldBeSave<Product>(r=>r.ShouldEqualWeak(mock.Original))
Однако при росте приложения, как мне кажется, эти две проблемы дадут о себе знать.
Это готовое решение для конкретного проекта, то есть логика сохранения Product будет в каждом проекте своя.
вас не заботит re-usability класса AddAlbumCommand?
Repository.GetById(UserId ?? App.Current.UserId)
Правильно ли я вас понял, что в данном конкретном случае, вас не заботит re-usability класса AddAlbumCommand?
CommandBase и Dispatcher, которые предназначены для повторного использования, тоже service locator.тоже service locator.
А чем это плохо?
Вы при старте приложение конфигурируете IDispatcher, IRepository
Вы это Service locator и видеть не будете
Откуда я узнаю, что именно надо конфигурировать?
Мы уже выяснили, что это не так — первая же практическая реализация мгновенно вылетает за рамки того, что вы предусмотрели.
for<ICommunicationMessage>().Use(()=>new TwitterCommunication(app,key))
.Named(ProviderOfType.Twitter)
for<ICommunicationMessage>().Use(()=>new FacebookCommunication(app,key))
.Named(ProviderOfType.Facebook)
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) 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);
}
}
Регистрация ничем не отличается от вашей.
Вам надо добавить ещё один class (аля Factory или Gateway)
Все объекты с ctor — проблема серилизации
Вместо named нужна промежуточная сущность Factory (Gateway)
Если бы Service Locator убирал бы типизацию или как то скрывал ошибки, то я может бы и понял проблему
Зачем? Класс, который я написал, заменяет функциональность вашей команды, а не дополняет ее.
Просто разделяйте DTO и Actors, и никаких проблем с сериализацией не будет. Вы думаете, сообщения на пустом месте придумали?
Этот вывод ошибочен.
Сервис-локатор скрывает (вплоть до рантайма) ошибку регистрации зависимости. Если нужная классу зависимость не зарегистрирована, то мы об этом не узнаем вплоть до обращения к сервис-локатору (когда уже поздно).
А вызывать Вы откуда будете Gateway, не из Command?
Вы делаете сайт, зачем Вам все эти усложнения?
Сейчас вместо DTO используется Rest API, так что временна SOAP (proxy классы) прошли.
А ctor? Какая разница если Вы забыли зарегистрировать зависимости, то ошибка тоже будет.
Просто мысленно поместите этот класс вместо вашей команды (которая не делает ничего кроме отправки сообщения).
Она будет в момент создания, а не в момент выполнения. Это позволяет весьма эффективно тестировать регистрацию.
CQRS позволяет уйти от выдумывания имен (Task, Service, Gateway, Factory, Runner и т.д.) в сторону «говорящих» названий по которым проще перемещаться через навигацию.
У Вас есть Controller, вы добавили ему инъекцию в ctor IUserService, если не будет зарегистрирован IUserService, то будет exception в runtime!!!
Вы ушли от темы. Это никак не относится к вашему утверждению, что DI требует лишнего класса по сравнению с SL.
Эксепшн будет в момент создания конструктора, а не выполнения какой-то операции (причем при неизвестных условиях).
public MyController()
{
this.dispatcher = IoCFactory.Instance.TryResolve<T>()
}
А создание всех конструкторов (и вообще всех зависимостей) в приложении очень легко протестировать (в отличие от прохода по всем путям выполнения).
Ну а как нет, если мне в 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».
Вы LINQ тоже не используете (он же отложенный) ?)
примечание: если сюда добавить DI, то придет ещё регистрировать ControllerFactory, а в этой реализации НЕ надо.
В вашем же коде в фреймворке — хуже (там Lazy, причем реализованный вручную, а не через IoC-фреймворк, поэтому ошибка отложена)
Я знаю, зачем это. А еще я знаю, к чему это приводит.
По остальным пунктам возражений нет?
И к чему?
Я не вижу смысла в нашей дискусии, потому что я Вас не переубежу (Вы сами с усами) и я не собираюсь менять своей позиции, потому что то, о чем Вы мне рассказываете я уже пробовал…
К ошибкам периода выполнения, причем на непредсказуемом пути выполнения.
вот подтвердить их вам пока не удалось.
Есть dispatcher, который отвечает за работу Command/Query и сколько бы Вы раз не вызывали dispatcher.Push(new Command()), то он может упасть ТОЛЬКО если ошибка будет из-за кода внутри Command и все!..
Ваше заявления о Service Locator меня тоже не убедили.
например, о DI
public MyCommand(IRepository repository) вместо того, что бы использовать базовый класс.Для Command/Query нужно иметь публичный ctor без параметров, потому что MVC binding делает десераилизацию, так что ради DI создавать ещё промежуточный класс, который использовать, как транспорт из Web form?.. Я думаю не стоит.
А эта проблема у вас возникла из-за того, что вы нарушили структуру CQRS.
ICommandHanlder<AddUserCommand> и AddUserCommand, но получается, что AddUserCommand это просто набор полей, поэтому что бы не делать 2 класса, мы объединяем и это ни как, не сказывается на объеме, потому что поля не усложняют класс.Когда я только начинал, то делал ICommandHanlder<AddUserCommand> и AddUserCommand, но получается, что AddUserCommand это просто набор полей, поэтому что бы не делать 2 класса, мы объединяем и это ни как, не сказывается на объеме, потому что поля не усложняют класс.
Плюсы в том, что упрощается навигация по проекту
О «нарушили структуру CQRS», то даже в книгах про патернны делают сноски о возможных вариациях, то есть шаблон это набросок, который надо уже дорабатывать под код.
Вы опять ради DI усложняете проект, только потому что, у Вас был неудачный опыт с Service Locator
Вы не пробовали задуматься о том, зачем именно команда в CQRS — это именно DTO
Я не видел, чтобы где-то была описана вариация CQRS, в которой команда бы выполнялась самостоятельно.
Вот как вы будете делать гарантированную доставку и обработку команд в распределенной системе? Когда UI живет на одном узле, а выполнять команды надо на пяти других в облаке?
как раз в моей реализации с этим проблем нет,
А что меняется, Вы отправили Ajax post из UI, а кто там его обработает не так важно.
Правда? А что случится, если в вашей реализации обратиться к репозиторию сначала до сериализации, а затем после?
Я говорил о команде, которая создана как CLR-объект на узле веб-фермы, а потом должна быть обработана на одном из узлов-воркеров.
Ничего личного, но по мне просто надо сначала понять, почему и зачем в книге предлагается конкретное решение, а только потом думать о том, какие его части можно безболезненно выбросить.
Сериализация — это сбор всех публичных полей, которые имеют Get и Set, что бы потом превратить в Json/xml/binary. Поле Repository — это protected, который не попадает под сериализацию или десериализацию
Может проще сериализовать Command в базу и потом уже в backgroud выполнить?
В чем смысл сценария, что он дает?
НО теперь мы пишем меньше кода.
Прекрасно! Так что случится-то? К каким репозиториям будет обращение? Будут ли в них одни и те же объекты?
new DeactivateEntityCommand().Execute(); // without transaction and connection
dispatcher.Push(new DeactivateEntityCommand()); // open transaction and connection
Не проще. База не заточена на такие операции (в отличие от очередей).
НЕЛЬЗЯ использовать Command/Query без Dispatcher и именно он открывает Session (context) и поддерживает tranasaction (UoW).
Смотря какая, mongo очень шустро работает.
Вы на вопрос не ответили. Вопрос был простой: будут ли в репозитории до и после сериализации команды одни и те же объекты, или нет?
Вы сравнивали со специализированными очередями? А по удобству работы?
Конечно нет, потому что Dispatcher закроет Unit of work и Session, что приведет к Disposable. Если бы Вы до конца досмотрели реализацию (или дочитали статью про CQRS), а не поднимали тревогу из-за Service Locator, то Вам все было понятно.
Disaptcher имеет параметры для настройки того, как будет выполнена Command/Query и там предусмотрено Delay, что заставляет вместо выполнения поместить Command в Scheduler (планировщик)
что в некий неизвестный момент ее работы ее сериализуют
Это тоже не имеет отношения к обсуждению. Вы когда-нибудь работали со специализированными очередями/шинами сообщений (MSMQ, Azure Service Bus/Azure Queues, RabbitMQ и так далее)?
Какой такой момент? Код вдруг с того не всего начал проводить сереализацию или что?
Если нужно, то можно подменить реализацию IDisaptcher, которая будет работать с очередями, НО меня устраивает и так.
Ну как же. Команду создали. В момент создания она для своей инициализации взяла какие-то данные (может, из контекста, может, из репозитория). Это был конструктор. Потом ее сериализовали и отправили на другую ноду, где запустили. Это Execute. В этот момент она взяла и попробовала связать данные, которые она взяла, с тем, откуда она их взяла — она-то не знает, что она уже на другой ноде. Упс, клинч.
Вы снова не ответили на прямой вопрос.
У меня создается ощущение, что вы применяли свой фреймворк ровно в одном сценарии — веб-приложение, развернутое на одной ноде (или с горизонтальным масштабированием, где все ноды равноправны). Да? Если нет, то опишите, какие еще варианты компоновки решения вы успешно использовали, и как была устроена коммуникация между нодами.
если вы делаете disaptcher.Push(new Command()) то это синхронный код и пока тело Execute не завершится, то ничего не будет.
Зачем, если я решаю свои задачи и без них?
Я так смотрю решили «померится», кто что сделал?
Нет, я хочу понять границы применимости предлагаемого вами решения; только не гипотетические «а вот можно было бы», а конкретные — в каких оно уже точно работает.
в каких оно уже точно работает
Про недостатки синхронного кода в веб-приложениях я вам даже не буду рассказывать, это уже совсем за рамками дискуссии. А вот «ничего не будет» — это интересно.
Или это невозможно?
Pazar трафик 100000 пользователей в день и больше 1000 запросов в секунду.
на сервере чаще (хотя без проблем asyn + avoid ) исполняется синхронный код, потому что нужно дождаться завершения транзакции.
Заменить DefaultDispatcher другой реализацией.
Архитектура развертывания?
(перепутать avoid и await — это сильно...)
Т.е. про IOCP и неблокирующее ожидание ресурсов вы тоже не слышали?
Как именно должна работать эта реализация для описанного мной сценария? Или вы не продумывали такой сценарий?
Клиент — Сервер, все же framewok в первую очередь по Web
Вы лучше объясните, почему вдруг в процессе выполнения Command Вы её начинаете сериализовать.
Значит, мои оценки были правильными.
Это как раз тот сценарий, который вы не продумывали (и который в message-based CQRS является базовым).
Между созданием команды и ее выполнением есть разрыв, в котором может происходить что угодно.
Я же говорю, что можно сохранять Command в базу или ещё куда ( провайдер может быть любой )
Command будет выполнятся тогда когда она будет создана, но не иначе, потому что тогда, нарушается целостность. Вы до new Command() уже будете выполнять?
Вы путаете. Команда будет выполняться после ее создания, это очевидно. Но между созданием и выполнением диспетчер может сделать с командой что угодно, и сама команда об этом ничего не знает.
сделать с командой что угодно, и сама команда об этом ничего не знает
В момент создания она для своей инициализации взяла какие-то данные (может, из контекста, может, из репозитория). Это был конструктор.
Весь код должен быть в методе Execute, а на в ctor, потому что Repository будет доступен только там.
Только вы это тоже никак не контролируете (хотя могли бы). И поэтому разработчик будет писать так, как ему удобно.
Вы в asp.net mvc, тоже пишите как хотите или делаете по руководству?
Сериализация — это сбор всех публичных полей, которые имеют Get и Set, что бы потом превратить в Json/xml/binary. Поле Repository — это protected, который не попадает под сериализацию или десериализацию
Описанное вами поведение верно для дефолтных реализаций xml- и json-сериализаторов в .net.
Так что если вы его внедрите (для того же распределенного сценария), вы можете получить множество занимательных побочных эффектов.
asp.net mvc, WCF они используют именно такие подходы.
Если Вы волнуетесь, о том, что после Deserialize у Вас будет тот же самый Repository,
Если Вы сериализуете Command ДО выполнения, то Repository ещё не создан и по этому сохранять и нечего
На внешней границе. При сериализации данных в очередь обычно используют бинарник, потому что быстрее и компактнее.
обычно используют бинарник, потому что быстрее и компактнее.
При его сериализации (по крайней мере, насколько я понимаю код) заданная фабрика теряется, поэтому после десериализации любые обращения к репозиторию просто упадут с ошибкой инициализации lazy.
Статья про MVD, который используется в рамках asp.net mvc, а Вы уже все подряд.
ТО есть, JSON уже не удобен? RavenDB, MongoDb все они на JSON и мне кажется шустро работают.
Вы несете бред!!! Если объект десериализовать, то все поля заново будут вычислены, это не теория, потому что у нас есть Scheduler, который так и работает.
Вы все время забываете, что asp.net mvc — это всего лишь фронтенд
У вас в шедулере используется сериализация в json, которая работает иначе. Я говорил про бинарную.
asp.net как backend прекрасно работает?
В чем смысл, разве мало сценариев, как оптимизировать веб сервер?
НО все же повторю, сериализовать Command можно, во что угодно.
Собственно, это типовой сценарий в CQRS w/messaging — мы приняли команду пользователя, она породила десяток событий, все их надо обработать. Но пользователя-то это не волнует, его сценарий использования завязан на ровно одно событие, он его дождался и ушел домой. А кто будет обрабатывать все остальные? В каком процессе?
Не так прекрасно, как хотелось бы. Любая тяжелая обработка — и начинают страдать другие пользователи. Более того, любая асинхронная обработка с паралеллизмом — и выполнение кода становится негарантированным.
«Мы думаем, что можно, но не пробовали». А в реальности получите описанные выше проблемы.
Кладем 10 Command в Scheduler (планировщик), который обрабатывается в backgroud.
То есть, Stackoverflow нормально справился, а у Вас проблемы?
Судя по Вашему профилю, где за 4000 комментов (и 0 постов), которые почти все направленны на «провакацию», Вы просто тролль.
Судя по переходу на личность, аргументы кончились.
Обрабатываются каким процессом?
Где угодно, есть код, который отвечает за выполнение отложенных Command, откуда его вызывать не важно ( windows service, console, TaskFactory.NewStart при старте Global.asax )
Если вызывать в global.asax, то вы нагружаете процесс самого веб-сервера (IIS), который не очень дешевый.
А если вызывать в отдельном сервисе, то это уже не asp.net, и это как раз тот сценарий, про который я говорил, когда бэкэнд из asp.net выносится по тем или иным причинам
Thread.Sleep в серверном коде? Вы серьезно?PS Thread.Sleep в серверном коде? Вы серьезно?
А как Вы считаете реализовывать задержки между сеансами?
System.Threading.Timer и System.Timers.Timer, которые хорошо подходят для периодических задачTask.Delay, который, по крайней мере, позволяет отпустить поток и не потреблять ресурсы во время ожидания (понятное дело, использовать его с Wait — бессмысленно, там нужен честный async).Скажите раз Вам так все не нравится мой framework (нету DI, отсутствует DI и другие проблемы в том числе нехватка DI), зачем наш диалог?
есть более одного шедулинг-фреймворка с работой по расписанию, начиная от quartz.net, которые просто абстрагируют это от вас
если вы настолько любите писать все сами, есть System.Threading.Timer и System.Timers.Timer, которые хорошо подходят для периодических задач
по крайней мере, позволяет отпустить поток и не потреблять ресурсы во время ожидания
Чтобы прочие читатели этого поста, которые решат попробовать фреймворк или думают его использовать, заранее видели его (фреймворка) ограничения и недостатки.
Это другие задачи, мне надо все время выполнять, потому что расписание будет через recurrence schedule.
Не вижу проблемы, поток один и постоянно крутится
что бы доказывать другим, что мой инструмент плохой, особо то и не зная, как он работает.
с Вас кстати пример, когда Disapatcher.Push выкинет ошибку
IEventBrokerIoCFactory.Instance.TryResolve<IEventBroker>() возвращает nulleventBroker.Publish()NullReferenceExceptioncatch (при определенных условиях) идет повторное обращение к eventBroker, что дает новый эксепшн (привет отладке), уже внутри catchfinally, после чего мы получаем голый необработанный эксепшнIncControllerFactory?Таймеры тоже постоянно работают.
То есть постоянно ест ресурсы. Ну да, нет никаких проблем.
Не регистрируем IEventBroker
PS Интересно, если «примечание: если сюда добавить DI, то придет ещё регистрировать ControllerFactory, а в этой реализации НЕ надо», то зачем в вашем фреймворке есть IncControllerFactory?
Таймеры живут в памяти, а если скажем у Вас сотни Command в планировщики? Мой подход просто делает запрос в бд (через Query) и достает все (можно с pagianated) актуальные на данный момент и выполняет (можно параллельно сразу несколько).
Работа с базой закрывает постоянно, так что затраты будут минимальны.
Thread.Sleep ест процессорное время?Это называется не «не читаете документации».
Resolve вместо TryResolve, и была бы типизованная ошибка сразу в нужном месте.)HandlerAsyncAttribute, только это неправильная политика — все события должны быть асинхронны, а не только избранные; более того, асинхронность у вас сделана через BeginInvoke, а EndInvoke вы вызываете? я не нашел, а это, заметим, обязательно), и ошибки в них вы тоже не блокируете — а это значит, что во время выполнения команды ошибка в любом синхронном обработчике событий тоже вылетит из метода Push, хотя он, казалось бы, ни в чем не виноват.А это уже не важно. Важно, что диспетчер после этого упадет, а значит, ваше утверждение «он может упасть ТОЛЬКО если ошибка будет из-за кода внутри Command и все!.» — ошибочно.
опять не учитываете то, что ваша система — предположительно — будет использоваться не только вами и не только так, как вы предполагаете — это показательно.
Т.е. вы не в курсе, что сам по себе поток — это накладные расходы, а Thread.Sleep ест процессорное время?
все события должны быть асинхронны, а не только избранные; все события должны быть асинхронны, а не только избранные;
то во время выполнения команды ошибка в любом синхронном обработчике событий тоже вылетит из метода Push, хотя он, казалось бы, ни в чем не виноват
Nuget все настраивает, так что я бы не сказал, что прямо все плохо и запутанно.
Мы говорим о одном потоке, а тот же asp.net mvc создает для каждого Action свой, так что критичного ничего в этом ничего нет.
Это Ваш подход.
В этом идея транзакционности, потому что нужно что бы все закончили без ошибок.
Nuget настраивает, а люди переделывают. У вас нет никакого контроля над этим, и поэтому вы должны учитывать эту возможность в своем коде. А вы не учитываете — и ваш код становится хрупким.
TryResolve<IEventBroker>() ?? new DefaultEventBroker(), но IRepository, IUnitOfWorkFactory так уже не получится.В потере ресурсов нет ничего критичного? Завидую вам.
Хорошо пример с Controller Factory для вызова TryResolve, которую Вам устанавливает nuget, Вы же тоже можете удалить?
Я могу в коде Dispatcher сделать TryResolve<IEventBroker>() ?? new DefaultEventBroker(), но IRepository, IUnitOfWorkFactory так уже не получится.
Всегда можно вынести Scheduler на другой сервер
Могу, конечно.
И что? Обрабатывайте ошибки корректно, это еще Макконнел писал.
Значит кругом не безопасный код
Почему это ошибка?
Прогнозирование ВСЕХ ошибок и обработка их, только увеличит код.
Потому что вылетел exception, который кому-то теперь надо обработать.
Ненормально то, что вы утверждаете, что он их бросает только в одном случае, хотя это не так.
Похоже, Макконнела вы тоже не читали. Defensive programming, все такое.
хотя, конечно, то, как вы их обрабатываете — это печаль
Ничего не надо обрабатывать, надо просто спросить (Stackoverflow к примеру) или почитать документацию, где будет решение проблемы связанной с не правильной настройкой.
ошибок в Push быть не должно
Вы понимаете, что есть разные подходы
надо брать цитаты из книги про него (Data contract)
У Вас свой catch?
если после установки framework через Nuget, ничего не удалять, то можно приступать писать Command/Query
Ошибки вообще надо обрабатывать, это полезно.
А будут. Ошибка настройки IoC — это всего лишь один пример возможной ошибки.
fail never.
fail early
Я (например) слежу за тем, чтобы в catch и finally не было (предусмотримых) ошибок.
Книга Макконнела — про совершенный код
Пример, у Вас есть запрос 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) перехватчик.
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 имеет вид
GetEmployee(code.With(r=>r.Trim())).With(r=>r.Address).With(r=>r.City).Recovery(string.Empty).ToLower()
Заказчику вы мне тоже предлагаете сказать «это у сторонних разработчиков ошибка»? Не, так не работает, заказчику все равно, у кого ошибка. То, что ошибка у сторонних разработчиков, может дать мне немного больше времени на ее починку, вот и все; но чинить-то все равно придется.
Более того, для конкретно взятого GetXById с документированным поведением «вернуть null, если сущность не найдена» проверка нужна тем более, чтобы вместо нуллрефа показать сообщение «Сущность X с идентификатором Y не найдена».
Вы правда искренне думаете, что это единственная ошибка, которая может там возникнуть?
бросит ошибку некорректной регистрации сервиса
Если 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.
То, что и как я буду делать, будет зависеть от причины ошибки. Соответственно, чем быстрее я ее определю, тем быстрее я решу проблему.
Это утверждение неверно.
Вот внезапно и выяснилось, что проверять результат GetById надо.
А в других случаях (например, сущность не того типа) — вполне себе летят exception.
Эээ… Вам из этого названия не понятно, что этот метод возвращает сотрудника по коду? Ровно одна вещь, которая непонятна из этой строки — это может ли быть null в результате, но это фундаментальная проблема .net вообще (и она решается конвенциями и аннотациями).
Repository.Query(whereSpecification:new EmployeeByCode(code)).FirstOrDefault()Во-вторых, есть ошибки, которые не являются багами, а являются ожидаемым поведением системы, и их тоже необходимо обрабатывать.
Поэтому если мы хотим разумного поведения системы в продуктиве, мы должны выбрать подходящую стратегию обработки ошибок.
Вы будете гуглить и выйдете на Stackoverflow, а если нет, то плюнете и напишите обходной вариант.
NHibernate EntityNotFoundException проще, чем по NHibernate NullReferenceException.Покажите
Ключевое слово внезапно, потому что где нужно там и проверяйте, а выбрасывать Exception это не верно, потому что это более узкий вариант, нежели return null
return null и надо проверять результат. Зачастую (я не говорю, что в этом конкретном случае) более узкие варианты оказываются эффективнее.Вы тип сами указываете, так что давайте реальный пример, когда там возникнет Exception,
if (obj != null && !(obj is TEntity))
throw Error.DbSet_WrongEntityTypeFound((object) obj.GetType().Name, (object) typeof (TEntity).Name);
Зачем ещё выделять в метод и выдумывать ему название?
Отсутствие Event Broker это не ожидаемое поведение.
А то Вы прямо уже заранее говорите, что он не рабочий.
Вот именно. И поверьте, что гуглить по NHibernate EntityNotFoundException проще, чем по NHibernate NullReferenceException.
А это и есть реальный пример, из внутренностей EntityFramework:
более узкие варианты оказываются эффективнее.
Потому что мне так комфортнее. Читаемость это не уменьшает. Так что вам не понятно в том, что делает предложенный мной метод, из его названия?
Я не говорю, что фреймворк не рабочий. Я указываю на те места его реализации, которые я по тем или иным причинам считаю неудачными.
А закончившаяся память? А таймаут БД? Оборвавшееся соединение с БД? Ошибка валидации внутри события? Ошибка отправки email?
Вы просто разводите полемику на тему кода без ошибок,
А как может быть, что Вы делаете запрос на TEntity по Id, а Вам придет другой тип?
Над EF работала команда и у них есть время писать комменты, кучу кода и тд., но у меня нет.
Если Вы пишите код под один проект
Я считаю его избыточным
Это ошибки которые будут в методах Execute и каждый сам решает, как их обработать.
Frameowkr не отправляет письма. не отвечает за таймаут и т.д.
Вы, похоже, не поняли. Я не говорю о коде без ошибок (такого не бывает), я говорю о том, как обрабатывать ошибки (которые неизбежно будут).
А как же «много лет», которые писался ваш фреймворк? Ну и да, экономия от точной локализации ошибок, поверьте мне, выше, чем потери на расстановку маркеров.
Это не имеет отношения к непонятности. Вам что-то непонятно в том, что делает этот метод?
Чем дальше, тем меньше я вижу вещей, за которые отвечает ваш фреймворк.
Угу. Обработчик события пишет один разработчик, который решил, что ошибку обрабатывать не надо, а получит ее другой ничего не подозревающий разработчик во время Push. Как ему угадать, что произошло?
то что Вы говорите о проблеме с «возможным» отсутствие Event Broker, то я могу добить проверку на null и выкидывать логическую ошибку,
если у Вас есть сценарии, которые не может выполнить framework, то это проблема.
Вы знаете, что такое Deadline?
Вы понимаете, что frameowrk это open source, а значит и делался бесплатно.
В том, что надо переходить во внутрь, что бы понять что он на самом деле делает (критериев может быть много)
GetEmployee(code,name,email.isActive) придумали вы, я такого примера не приводил.framework это каркас, так что Вы не правильно понимает смысл.
Каждая команда сама решает, как обрабатывать ошибки, глобально или нет, но на работу Push это не влияет.
Push обрабатывает ошибки внутри себя — сильно влияет на команду.Например, ошибка в любом обработчике события.
Во-первых, open source не означает, что делалось бесплатно. Во-вторых, если бесплатно — то откуда дедлайны? Что-то у вас не сходится.
А вот это уже передергивание. Критерий ровно один, и он показан в примере кода. GetEmployee(code,name,email.isActive) придумали вы, я такого примера не приводил.
Вы, похоже, так и не прочитали, что там надо было делать на самом деле. Удивительно.
Конечно, не влияет. Зато то, как Push обрабатывает ошибки внутри себя — сильно влияет на команду.
Если будет ошибка то выполнение прервется это, то как мне удобно и комфортно, все же мы писали в первую очередь для себя.
Мы используем frameowork в своих проектах и как раз там и есть дедлайны (а они давят на Framework, что бы быстрее появились новые фичи или фиксы)
но а как тогда будет? или Вы для одного параметра будете метод делать, а для двух нет?
Если Вы про TryResolve заменить на Resolve, то я уже написал, что разницы особо нету
Если Вы делаете в Command throw exception, то он пойдет вверх и остановит обработку, НО если вы перехватите exception и поставите пустой catch, то Push продолжит работу. Вопрос, чем мешает PUSH?
Push.А если вы используете фреймворк для коммерческой разработки, то неплохо бы писать его за деньги, не?
А это уже отдельный вопрос. Если у меня будет задача поиска более чем по одному параметру, я задумаюсь о ней отдельно (но обычно мы такие вещи делаем уровнем ниже и используем LINQ).
Вы не понимаете. Проблема в том, что я, как человек, который вызывает диспетчер (и кладет туда команду на выполнение), не знаю, какой код в реальности будет выполнен (из-за того, как вы обрабатываете события); как следствие, я вообще никак не могу предсказать, какие ошибки я получу из Push.
а как же тестирование сложных запросов?
И кстати опять у Вас слои, то есть как я и думал, что бы понять код надо пройти 5 Go to declaration
Выполнится только код в рамках Execute, остальной просто сопровождает (отвечает за Unit Of work, Repository ), но ни как не влияет. на его работу.
Т.е. и события тоже выполнены не будут?
Тестируются на массивах совершенно замечательно.
Where(r=>r.IsActive && r.User.Id == userId && ( r.Date >= startDt || r.Date <= endDT). Вы наверно сложных условий не видели .,по названию метода (должно быть) понятно).
Будут синхронно,
У Вас поиск и там 20 критериев?
Вы придумайте название для метода с 10 критериями и тогда утверждайте
Вам, похоже, никогда не приходилось пытаться понять, что произошло на удаленном сервере, где нет дебаггера, по сообщению «NullReferenceException in line 86»,
Когда пишутся данные для логирования ошибок в глобальном обработчик, то можно получить текущий instance Command/Query
в отчет включить все значения свойств
зная Code можно найти (или наоборот понять, что его нет) Employee и проверить все Address.City и т.д. на наличие в базе, что сразу скажет в чем дело.
теперь мы пишем меньше кода.
AddDocument, которая автоматически проставляет текущего пользователя автором документа).AddDocument(authorId). Соответственно, обработчик команды AddDocument просто инициирует вызов AddDocument(userId), передавая туда в качестве параметра информацию из контекста.ISessionContext, который при использование Disaptcher в другом context можно подменить на WinSessionContext,
inform приложение скорей всего будет вызывать Ваш Controller
А какой «SessionContext» вы будете использовать в сервисе, который добавляет документы для абстрактных пользователей?
Это почему это вдруг?
К примеру если Вы вызываете из Web то у Вас будет WebSessionContext, но если из Windows Service, то там WinSessionContext и т.д.
Затем, что бы использовать уже существующие методы, как API для сторонних приложений.
Model View Dispatcher (cqrs over mvc)