После подробного руководства, начинающие программисты (asp.net mvc), а тех кто уже пишет на чем то, будет сложно «переманить»
Кому нужно изучать странный способ определения банальных вещей (это я больше про *.cshtml)?
Основная причина, это типизация, которой так не хватает на JS.
P.S. Статья (как и сам framework) была в первую очередь для сотрудников нашей компании, а тут в целях информировать о том что у нас получилось (мы считаем, что не плохо) и попытки развить его во что то большое.
Я думаю, это утрирования, потому что в статье было сравнение (где я выделял наши плюсы), может конечно картинка была воинственно настроена. Скажем так, изначально я начал не правильно описывать, но теперь решил исправится ))
Если у вас 18 полей, то либо это песец какая сложная предметная область, либо legacy и 80% полей — мусор, либо не верно декомпозированы сущности, либо это «плоские» данные из аггегата. Я согласен, что такие конструкторы чудовищны, но с большой вероятностью проблема в проектировании этой Entity
public class PatientTask : ActiveHistoryEntityBase<PatientTask>, IPatient
{
public virtual DateTime? StartDate { get; set; }
public virtual DateTime? DueDate { get; set; }
public virtual string Task { get; set; }
public virtual User AssignedTo { get; set; }
public virtual Patient Patient { get; set; }
public virtual TaskStatus Status { get; set; }
public virtual TaskPriority Priority { get; set; }
public virtual string Notes { get; set; }
}
Какие поля тут мусор? Просто, очень странно, что Вы не сталкивались с длинными форма регистрации и т.д.
Verify, HasRule и прочее не надо сувать в один доменный объект.
Даже, пара методов могут быть очень громоздкими.
Не все объекты предметной области вообще должны мапиться на модель и ничего не мешает внутри SaleRule использовать Validator и т.п.
Очень сложно придумывать названия данным агрегатам, куда проще строить блоки из готовых глаголов ( GetLastUserQuery,ChangeUserStatusCommand and etc). Validator, прекрасно работает на Command/Query, потому что именно они, а не DDD передаются из форм приложения.
С моей точки зрения код, который вы привели в пример имеет много проблем (как раз из серии перемешивания бизнес-правил и построения строчки).
А построение строчки это не бизнес правило?
Самое простое — ваш код можно «разломать» не передав Appointment.
Для этого пишутся Unit Test, на тот код который вызывает данный Query.
Другой вопрос, что вот прям богатая модель нужна для сложных мест из предметной области (там где действительно важно и не понятно, что происходит).
По DDD, что Вы будете делать если Вам надо получить доступ к Repository/Query из метода Domain? примечание: для метода ChangeStatus из класса User, требуется получить доступ к стороннему сервису банка, что бы проверить платеж.
Тупой CRUD можно писать на чистом CQRS и не париться
По мне вызов методов DDD, возможен только через Command/Query (ну это в рамках моей концепции)
Плюс, который я вижу в повсеместно использование CQRS, в том что Вы не привязаны к шаблонам (определенной модели) и у Вас нету ограничений, потому что каждый Query/Command обладает одинаковым набором доступных инструментов (Repository/Dispatcher).
Я сторонник богатой доменной модели (Rich Domain Model) и не люблю анемичную, поэтому Lead обладает правильным конструктором и не дает перевести себя в несогласованное состояние (нарушить инвариант).
Замечу пару моментов, который сам усвоил при использование DDD:
1. CTOR — смотрится очень хорошо в рамках объекта с 2 — 5 полями, но крайне не читабельный при усложнение, когда у Вас 15, так что более лучшим решением будет new T() { Name = «value»,Name2=«value2»}. примечание: что бы так же поддерживать обязательность заполнения всех полей, лучше писать Unit Test и Equal weak
2. Красивые названия методов HasRule, Verify и т.д, очень скоро делают из Entity обычный GOD object, по этому лучше реализовывать логику в рамках Query/Command.
Пример, такого Query
public class GetTaskNameFromAppointmentQuery : QueryBase<string>
{
public Appointment Appointment { get; set; }
protected override string ExecuteResult()
{
var user = Dispatcher.Query(new GetCurrentUserQuery());
var sb = new List<string>();
sb.Add(string.Format("Appointment Reminder - {0} Appointment:", Appointment.Status.Name));
if (Appointment.Type != null)
sb.Add(string.Format("{0}", Appointment.Type.Name));
if (Appointment.Type == null && !string.IsNullOrWhiteSpace(Appointment.Professional))
sb.Add(string.Format("With"));
if (!string.IsNullOrWhiteSpace(Appointment.Professional))
{
string value = Appointment.Professional;
if (Appointment.ProfessionalCareId.HasValue)
{
var profCare = Dispatcher.Query(new GetDetailForAppointmentNameQuery() {
Id = Appointment.ProfessionalCareId
});
value = profCare.Name;
}
sb.Add(value);
}
if (Appointment.Date.HasValue)
sb.Add(string.Format("on {0}", Appointment.Date.ToDisplayString()));
if (Appointment.Date.HasValue && Appointment.Time.HasValue)
{
sb.Add(string.Format("at {0}", user.ConvertFromUTC(Appointment.Date.Add(Appointment.Time.Value))
.GetTime()
.ToTimeString()));
}
return string.Join(" ", sb);
}
}
примечание: в примере, видно user.ConvertFromUTC который как метод, но куда лучше использовать отдельный Query
Пример использования
var taskCommand = new UpdatePatientTaskCommand()
{
Priority = PatientTask.TaskPriority.Normal,
Status = Status,
AssignedTo = assignedTo.GetValueOrDefault(),
Task = Dispatcher.Query(new GetTaskNameFromAppointmentQuery() { Appointment = appointment })
};
Dispatcher.Push(taskCommand);
3. Пример с Query (выше) показал, то что в рамках Query/Command можно использовать Repository/Dispatcher, а вот в рамках Entity Вы очень ограничены.
4. Удобство тестирования, дело в том, что Mock для Entity можно сделать только, если Вы получаете её через метод, что позволяет Вам её подменить, но не когда делает new T()
P.S. Я не говорю, что мне открылась истина и DDD это не лучший подход, но я столкнулся с аргументами, которые вынудили сделать такой вывод )))
Я думаю, после того, как пару раз пропустите вызов метода Commit или появится необходимость одновременно расширить функционал Command/Query, Вы придете к варианту централизованного вызова.
Тем более, что проблему дублирования linq-запросов в коде можно изящно решить вот так:
public class Account : IEntity
{
[BusinessRule]
public static Expression<Func<Lead, bool>> ActiveRule = x => x.IsDeleted && x.Ballance > 0;
}
Мне кажется, более универсально (а так же к тестированию пригодно) разбить Where на 2 части, а сами expression упаковывать в классы
new LeadOnlyDeletedWhere().And(new LeadGreaterBalance(0))
__queryFactory.GetQuery<Product>()
.Where(Product.ActiveRule) // это статический экспрешн, как в примере с Account. Используется ExpressionSpecification
.OrderBy(x => x.Id)
.Paged(0, 10) // получаем 10 продуктов для первой страницы
Наш, пример с specifications.
Repository.Query(whereSpecification: new ProductByFullTextWher("test")
.And(new ProductBySomethingElseWhere("some"),
orderSpecification:new ProductByNameOrder(),
paginatedSpecification:new PaginatedSpecification(current,size))
Отмечу, основные плюсы:
1. Можно делать StubQuery, в рамках теста и потом отдельно тестировать specification.
2. Specification, позволяют скрыть логику, к примеру ProductByRangeWhere(start,end)
ICommandFactory и IQueryFactory
Те же static helper, по этому я бы посоветовал двигаться в сторону использования Command/Query повсеместно, к примеру SendEmailCommand(), GetConnectionStringQuery и т.д.
Плюсы?
— Больше не надо, выдумывать ManagerHelper,CoreService,CommandHelper,FileBuilder and etc
— Command/Query помогают бороться с появление GOD object в проекте, потому что они атомарные и делают только одно действие ( иногда смежно AddOrEdit, но это скорее исключение, чем правило)
-Повторно можно использовать, в рамках других Command/Query (к примеру BulkAddProduct, который в foreach использует AddProduct, что бы не дублировать логику)
public class GroupEncounterRedFlagCommand : CommandBase
{
public Guid EncounterId { get; set; }
public Guid RedFlagGroupId { get; set; }
public string Value { get; set; }
public override void Execute()
{
foreach (var redFlag in Repository.GetById<RedFlagGroup>(RedFlagGroupId).RedFlags)
{
Dispatcher.Push(new ChangeEncounterRedFlagCommand()
{
EncounterId = EncounterId,
RedFlagId = redFlag.Id,
Value = Value
});
}
}
}
-Проще навигация по проекту, ищем действие, что проще чем CommandFactory.Add, так что лучше AddGenericCommandT
-Мы даже, вызываем Command/Query по Ajax без controller (MVD)
Проблемы:
— Command/Query должны создавать UnitOfWork, только в момент обращения к Repository, иначе cache внутри будет все равно тратить ресурсы на открытие подключения.
-Command/Query должны иметь разный тип транзакций, ReadUncommited для query и ReadCommited для Command
и ещё много других нюансов, которые я нашел в процессе написания Incoding Framework.
Будут синхронно, я же говорю основная задач 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 критериями и тогда утверждайте. (для примера Выше дайте название).
А если вы используете фреймворк для коммерческой разработки, то неплохо бы писать его за деньги, не?
Это не Ваше наверно дело.
А это уже отдельный вопрос. Если у меня будет задача поиска более чем по одному параметру, я задумаюсь о ней отдельно (но обычно мы такие вещи делаем уровнем ниже и используем LINQ).
а как же тестирование сложных запросов? Вам не нравится идея использовать Specification, которые можно тестировать отдельно и упаковывать в них логику? И кстати опять у Вас слои, то есть как я и думал, что бы понять код надо пройти 5 Go to declaration
Вы не понимаете. Проблема в том, что я, как человек, который вызывает диспетчер (и кладет туда команду на выполнение), не знаю, какой код в реальности будет выполнен (из-за того, как вы обрабатываете события); как следствие, я вообще никак не могу предсказать, какие ошибки я получу из Push.
Выполнится только код в рамках Execute, остальной просто сопровождает (отвечает за Unit Of work, Repository ), но ни как не влияет. на его работу.
P.S. Спасибо за дискуссию, но время это ценная вещь, а игра в «придумай» аргумент уже надоела. Я так и не понял, что Вы хотели в итоге получить, потому что я все равно останусь при своем мнение (Вы для меня не авторитет) и Вас не переубедить (причина та же)
Если будет ошибка то выполнение прервется это, то как мне удобно и комфортно, все же мы писали в первую очередь для себя.
Во-первых, open source не означает, что делалось бесплатно. Во-вторых, если бесплатно — то откуда дедлайны? Что-то у вас не сходится.
Мы используем frameowork в своих проектах и как раз там и есть дедлайны (а они давят на Framework, что бы быстрее появились новые фичи или фиксы)
А вот это уже передергивание. Критерий ровно один, и он показан в примере кода. GetEmployee(code,name,email.isActive) придумали вы, я такого примера не приводил.
но а как тогда будет? или Вы для одного параметра будете метод делать, а для двух нет?
Вы, похоже, так и не прочитали, что там надо было делать на самом деле. Удивительно.
Если Вы про TryResolve заменить на Resolve, то я уже написал, что разницы особо нету, ДА и в тысячный раз повторю, НИКОГДА ПРИ ПРАВИЛЬНОМ использование не будет Event Broker null, а те кто специально не зарегистрируют Event Broker меня не волнуют.
Конечно, не влияет. Зато то, как Push обрабатывает ошибки внутри себя — сильно влияет на команду.
Если Вы делаете в Command throw exception, то он пойдет вверх и остановит обработку, НО если вы перехватите exception и поставите пустой catch, то Push продолжит работу. Вопрос, чем мешает PUSH?
Вы, похоже, не поняли. Я не говорю о коде без ошибок (такого не бывает), я говорю о том, как обрабатывать ошибки (которые неизбежно будут).
Давайте так, то что Вы говорите о проблеме с «возможным» отсутствие Event Broker, то я могу добить проверку на null и выкидывать логическую ошибку, но это не так важно, а вот если у Вас есть сценарии, которые не может выполнить framework, то это проблема.
А как же «много лет», которые писался ваш фреймворк? Ну и да, экономия от точной локализации ошибок, поверьте мне, выше, чем потери на расстановку маркеров.
Вы предлогаете поверить Вашему NDA? Я тоже могу травить байки о безопасном коде и о том, что все ошибки обработаны, но смысла не вижу. Вы знаете, что такое Deadline? Вы понимаете, что frameowrk это open source, а значит и делался бесплатно.
Это не имеет отношения к непонятности. Вам что-то непонятно в том, что делает этот метод?
В том, что надо переходить во внутрь, что бы понять что он на самом деле делает (критериев может быть много) примечание: GetEmployee(code,name,email.isActive) — как Вам это ?
Чем дальше, тем меньше я вижу вещей, за которые отвечает ваш фреймворк.
framework это каркас, так что Вы не правильно понимает смысл.
Угу. Обработчик события пишет один разработчик, который решил, что ошибку обрабатывать не надо, а получит ее другой ничего не подозревающий разработчик во время Push. Как ему угадать, что произошло?
Каждая команда сама решает, как обрабатывать ошибки, глобально или нет, но на работу Push это не влияет.
Вот именно. И поверьте, что гуглить по NHibernate EntityNotFoundException проще, чем по NHibernate NullReferenceException.
Есть тесты которые покрывают ситуации. Вы просто разводите полемику на тему кода без ошибок, потому что если следовать Вам, то мне надо добавить десятки проверок на «ошибки», которые мало вероятны, а если следовать докам то и не возможны.
А это и есть реальный пример, из внутренностей EntityFramework:
А как может быть, что Вы делаете запрос на TEntity по Id, а Вам придет другой тип? Над EF работала команда и у них есть время писать комменты, кучу кода и тд., но у меня нет.
более узкие варианты оказываются эффективнее.
Если Вы пишите код под один проект
Потому что мне так комфортнее. Читаемость это не уменьшает. Так что вам не понятно в том, что делает предложенный мной метод, из его названия?
Я считаю его избыточным, Вот видите, теперь у Вас появился аргумент, что ВАМ так нравится и комфортно, а то что мне удобно с Service Locator и без «паранойи» к ошибкам.
Я не говорю, что фреймворк не рабочий. Я указываю на те места его реализации, которые я по тем или иным причинам считаю неудачными.
Все эти места рабочие, если делать правильно, НО если хотите не зависеть от особенностей библиотеки, то лучше написать свой код.
А закончившаяся память? А таймаут БД? Оборвавшееся соединение с БД? Ошибка валидации внутри события? Ошибка отправки email?
Это ошибки которые будут в методах Execute и каждый сам решает, как их обработать. Frameowkr не отправляет письма. не отвечает за таймаут и т.д.
То, что и как я буду делать, будет зависеть от причины ошибки. Соответственно, чем быстрее я ее определю, тем быстрее я решу проблему.
Вы будете гуглить и выйдете на Stackoverflow, а если нет, то плюнете и напишите обходной вариант.
Это утверждение неверно.
Покажите
Вот внезапно и выяснилось, что проверять результат GetById надо.
Ключевое слово внезапно, потому что где нужно там и проверяйте, а выбрасывать Exception это не верно, потому что это более узкий вариант, нежели return null
А в других случаях (например, сущность не того типа) — вполне себе летят exception.
Вы тип сами указываете, так что давайте реальный пример, когда там возникнет Exception,
Эээ… Вам из этого названия не понятно, что этот метод возвращает сотрудника по коду? Ровно одна вещь, которая непонятна из этой строки — это может ли быть null в результате, но это фундаментальная проблема .net вообще (и она решается конвенциями и аннотациями).
Мой вариант Repository.Query(whereSpecification:new EmployeeByCode(code)).FirstOrDefault()
Зачем ещё выделять в метод и выдумывать ему название? Если у Вас будут разные варианты запросово Employee, то Вам надо имена давать разные (GetEmployeeByActive, GetEmployeeBySearch ), а тут на основе Specification видно все.
Во-вторых, есть ошибки, которые не являются багами, а являются ожидаемым поведением системы, и их тоже необходимо обрабатывать.
Отсутствие Event Broker это не ожидаемое поведение.
Поэтому если мы хотим разумного поведения системы в продуктиве, мы должны выбрать подходящую стратегию обработки ошибок.
Вы попробуйте framework в работе следуя инструкциям и тогда судите, о том как он работает. А то Вы прямо уже заранее говорите, что он не рабочий.
Для того, что бы быть уверенным, что все зависимости зарегистрированы, можно:
1. Многие IoC framework позволяют использовать Conventions, что позволяет явно не указывать For<TInterface>.Use<TImp>() если он один к одному ( для других случаев Named )
2. Написать тест, который будет прогонять все Interface на предмет получения TryResolve.
примечание: если у Вас Named по Enum, то можно Enum.GetValues(typeof(TEnum)) и потом IoCFactory.Instance.TryResolve<T>(enumValue).ShouldNotBeNull()
Вам, похоже, никогда не приходилось пытаться понять, что произошло на удаленном сервере, где нет дебаггера, по сообщению «NullReferenceException in line 86»,
Когда пишутся данные для логирования ошибок в глобальном обработчик, то можно получить текущий instance Command/Query и в отчет включить все значения свойств, НО поскольку у Вас слои и нету четких ответственных за код, то да, Вам придется повозится. примечание: зная Code можно найти (или наоборот понять, что его нет) Employee и проверить все Address.City и т.д. на наличие в базе, что сразу скажет в чем дело.
Понятно. Вам, похоже, никогда не приходилось пытаться понять, что произошло на удаленном сервере, где нет дебаггера, по сообщению «NullReferenceException in line 86», когда строка 86 имеет вид
Но, конечно куда лучше обвешать, каждое обращение через 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) — будет 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, я не думаю, что они все ошибки перехватывают, но это рабочий (хорошо) инструмент и ни кто не будет от него отказываться если авторы пропустили ВОЗМОЖНЫЙ баг.
Не надо мешать все в кучу. Пример, у Вас есть запрос 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. На все отвечать смысла не вижу, потому что остальное все в духе «Я прочитал кучу умных книг и использую все паттерны и пишу самый безопасный код» (Но кроме пары строк Вы так и ничего не показали своего)
Это не framework, а язык, так что это сложно сравнивать.
Решил, что это удачное место для продвижения
Одновременно client/server, не много.
После подробного руководства, начинающие программисты (asp.net mvc), а тех кто уже пишет на чем то, будет сложно «переманить»
Основная причина, это типизация, которой так не хватает на JS.
P.S. Статья (как и сам framework) была в первую очередь для сотрудников нашей компании, а тут в целях информировать о том что у нас получилось (мы считаем, что не плохо) и попытки развить его во что то большое.
Приятно видеть, что помнишь ))
Я думаю, это утрирования, потому что в статье было сравнение (где я выделял наши плюсы), может конечно картинка была воинственно настроена. Скажем так, изначально я начал не правильно описывать, но теперь решил исправится ))
Какие поля тут мусор? Просто, очень странно, что Вы не сталкивались с длинными форма регистрации и т.д.
Даже, пара методов могут быть очень громоздкими.
Очень сложно придумывать названия данным агрегатам, куда проще строить блоки из готовых глаголов ( GetLastUserQuery,ChangeUserStatusCommand and etc). Validator, прекрасно работает на Command/Query, потому что именно они, а не DDD передаются из форм приложения.
А построение строчки это не бизнес правило?
Для этого пишутся Unit Test, на тот код который вызывает данный Query.
По DDD, что Вы будете делать если Вам надо получить доступ к Repository/Query из метода Domain?
примечание: для метода ChangeStatus из класса User, требуется получить доступ к стороннему сервису банка, что бы проверить платеж.
По мне вызов методов DDD, возможен только через Command/Query (ну это в рамках моей концепции)
Плюс, который я вижу в повсеместно использование CQRS, в том что Вы не привязаны к шаблонам (определенной модели) и у Вас нету ограничений, потому что каждый Query/Command обладает одинаковым набором доступных инструментов (Repository/Dispatcher).
Замечу пару моментов, который сам усвоил при использование DDD:
1. CTOR — смотрится очень хорошо в рамках объекта с 2 — 5 полями, но крайне не читабельный при усложнение, когда у Вас 15, так что более лучшим решением будет new T() { Name = «value»,Name2=«value2»}.
примечание: что бы так же поддерживать обязательность заполнения всех полей, лучше писать Unit Test и Equal weak
2. Красивые названия методов HasRule, Verify и т.д, очень скоро делают из Entity обычный GOD object, по этому лучше реализовывать логику в рамках Query/Command.
Пример, такого Query
примечание: в примере, видно user.ConvertFromUTC который как метод, но куда лучше использовать отдельный Query
Пример использования
3. Пример с Query (выше) показал, то что в рамках Query/Command можно использовать Repository/Dispatcher, а вот в рамках Entity Вы очень ограничены.
4. Удобство тестирования, дело в том, что Mock для Entity можно сделать только, если Вы получаете её через метод, что позволяет Вам её подменить, но не когда делает new T()
P.S. Я не говорю, что мне открылась истина и DDD это не лучший подход, но я столкнулся с аргументами, которые вынудили сделать такой вывод )))
Отвык я от Ваших умных комментариев, понятно откуда мне это знать )))
Это вариант, а лучший или нет, то скорей всего надо решать по ситуация, если Вам он не подходит, то скорей всего задача другая.
ух, какая конкретика ))) Уже пошел код свой удалять, после такого аргументов…
На данную проблему делал акцент lair
P.S. кстати, не заметил вызова Rollback ( сорри если пропустил )
Можно добавить возможность переопределять, но lock таблицу при чтение (без записи) это не лучший вариант в плане perfromance
Вот, для Query ReadUncommited и используется.
Можно долго рассуждать о тонких настройках, но я опираюсь все же более часто встречаемые сценарии и по этому придерживаюсь однотипности решений.
Нужно помнить, о том что надо вызывать метод. Проблема решается, если мы будем использовать Dispatcher, который скрывает все эти моменты.
Мне кажется, более универсально (а так же к тестированию пригодно) разбить Where на 2 части, а сами expression упаковывать в классы
Наш, пример с specifications.
Отмечу, основные плюсы:
1. Можно делать StubQuery, в рамках теста и потом отдельно тестировать specification.
2. Specification, позволяют скрыть логику, к примеру ProductByRangeWhere(start,end)
Те же static helper, по этому я бы посоветовал двигаться в сторону использования Command/Query повсеместно, к примеру SendEmailCommand(), GetConnectionStringQuery и т.д.
Плюсы?
— Больше не надо, выдумывать ManagerHelper,CoreService,CommandHelper,FileBuilder and etc
— Command/Query помогают бороться с появление GOD object в проекте, потому что они атомарные и делают только одно действие ( иногда смежно AddOrEdit, но это скорее исключение, чем правило)
-Повторно можно использовать, в рамках других Command/Query (к примеру BulkAddProduct, который в foreach использует AddProduct, что бы не дублировать логику)
-Проще навигация по проекту, ищем действие, что проще чем CommandFactory.Add, так что лучше AddGenericCommandT
-Мы даже, вызываем Command/Query по Ajax без controller (MVD)
Проблемы:
— Command/Query должны создавать UnitOfWork, только в момент обращения к Repository, иначе cache внутри будет все равно тратить ресурсы на открытие подключения.
-Command/Query должны иметь разный тип транзакций, ReadUncommited для query и ReadCommited для Command
и ещё много других нюансов, которые я нашел в процессе написания Incoding Framework.
Будут синхронно, я же говорю основная задач 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 критериями и тогда утверждайте. (для примера Выше дайте название).
Это не Ваше наверно дело.
а как же тестирование сложных запросов? Вам не нравится идея использовать Specification, которые можно тестировать отдельно и упаковывать в них логику? И кстати опять у Вас слои, то есть как я и думал, что бы понять код надо пройти 5 Go to declaration
Выполнится только код в рамках Execute, остальной просто сопровождает (отвечает за Unit Of work, Repository ), но ни как не влияет. на его работу.
P.S. Спасибо за дискуссию, но время это ценная вещь, а игра в «придумай» аргумент уже надоела. Я так и не понял, что Вы хотели в итоге получить, потому что я все равно останусь при своем мнение (Вы для меня не авторитет) и Вас не переубедить (причина та же)
Если будет ошибка то выполнение прервется это, то как мне удобно и комфортно, все же мы писали в первую очередь для себя.
Мы используем frameowork в своих проектах и как раз там и есть дедлайны (а они давят на Framework, что бы быстрее появились новые фичи или фиксы)
но а как тогда будет? или Вы для одного параметра будете метод делать, а для двух нет?
Если Вы про TryResolve заменить на Resolve, то я уже написал, что разницы особо нету, ДА и в тысячный раз повторю, НИКОГДА ПРИ ПРАВИЛЬНОМ использование не будет Event Broker null, а те кто специально не зарегистрируют Event Broker меня не волнуют.
Если Вы делаете в Command throw exception, то он пойдет вверх и остановит обработку, НО если вы перехватите exception и поставите пустой catch, то Push продолжит работу. Вопрос, чем мешает PUSH?
Давайте так, то что Вы говорите о проблеме с «возможным» отсутствие Event Broker, то я могу добить проверку на null и выкидывать логическую ошибку, но это не так важно, а вот если у Вас есть сценарии, которые не может выполнить framework, то это проблема.
Вы предлогаете поверить Вашему NDA? Я тоже могу травить байки о безопасном коде и о том, что все ошибки обработаны, но смысла не вижу. Вы знаете, что такое Deadline? Вы понимаете, что frameowrk это open source, а значит и делался бесплатно.
В том, что надо переходить во внутрь, что бы понять что он на самом деле делает (критериев может быть много)
примечание: GetEmployee(code,name,email.isActive) — как Вам это ?
framework это каркас, так что Вы не правильно понимает смысл.
Каждая команда сама решает, как обрабатывать ошибки, глобально или нет, но на работу Push это не влияет.
Есть тесты которые покрывают ситуации. Вы просто разводите полемику на тему кода без ошибок, потому что если следовать Вам, то мне надо добавить десятки проверок на «ошибки», которые мало вероятны, а если следовать докам то и не возможны.
А как может быть, что Вы делаете запрос на TEntity по Id, а Вам придет другой тип? Над EF работала команда и у них есть время писать комменты, кучу кода и тд., но у меня нет.
Если Вы пишите код под один проект
Я считаю его избыточным, Вот видите, теперь у Вас появился аргумент, что ВАМ так нравится и комфортно, а то что мне удобно с Service Locator и без «паранойи» к ошибкам.
Все эти места рабочие, если делать правильно, НО если хотите не зависеть от особенностей библиотеки, то лучше написать свой код.
Это ошибки которые будут в методах Execute и каждый сам решает, как их обработать. Frameowkr не отправляет письма. не отвечает за таймаут и т.д.
Вы будете гуглить и выйдете на Stackoverflow, а если нет, то плюнете и напишите обходной вариант.
Покажите
Ключевое слово внезапно, потому что где нужно там и проверяйте, а выбрасывать Exception это не верно, потому что это более узкий вариант, нежели return null
Вы тип сами указываете, так что давайте реальный пример, когда там возникнет Exception,
Мой вариант
Repository.Query(whereSpecification:new EmployeeByCode(code)).FirstOrDefault()Зачем ещё выделять в метод и выдумывать ему название? Если у Вас будут разные варианты запросово Employee, то Вам надо имена давать разные (GetEmployeeByActive, GetEmployeeBySearch ), а тут на основе Specification видно все.
Отсутствие Event Broker это не ожидаемое поведение.
Вы попробуйте framework в работе следуя инструкциям и тогда судите, о том как он работает. А то Вы прямо уже заранее говорите, что он не рабочий.
Для того, что бы быть уверенным, что все зависимости зарегистрированы, можно:
1. Многие IoC framework позволяют использовать Conventions, что позволяет явно не указывать
For<TInterface>.Use<TImp>()если он один к одному ( для других случаев Named )2. Написать тест, который будет прогонять все Interface на предмет получения TryResolve.
примечание: если у Вас Named по Enum, то можно
Enum.GetValues(typeof(TEnum))и потомIoCFactory.Instance.TryResolve<T>(enumValue).ShouldNotBeNull()Когда пишутся данные для логирования ошибок в глобальном обработчик, то можно получить текущий instance Command/Query и в отчет включить все значения свойств, НО поскольку у Вас слои и нету четких ответственных за код, то да, Вам придется повозится.
примечание: зная Code можно найти (или наоборот понять, что его нет) Employee и проверить все Address.City и т.д. на наличие в базе, что сразу скажет в чем дело.
Если null это допустипые значения, то можно
Но, конечно куда лучше обвешать, каждое обращение через IF и выбрасывать exception. Единственно, где стоит выкинуть exception и то для отображения его на UI это Not found employee by Code, но остальные обращения можно через безопасный контекст.
Вы не будете фиксить Nhibernate если там ошибка, а найдете просто альтернативный сценарий, скажем использовать ExecuteSql.
Зачем выкидывать 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 и там 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, я не думаю, что они все ошибки перехватывают, но это рабочий (хорошо) инструмент и ни кто не будет от него отказываться если авторы пропустили ВОЗМОЖНЫЙ баг.
Не надо мешать все в кучу. Пример, у Вас есть запрос session.GetById(Id).Name, Вы будете проверять объект после GetById на null?
Понятно, что баги бывают, но о них сообщают на bugtracker и разработчики их фиксят (покрывают тестами), НО не ставят try {} catch.
Это называет скрытые ошибки, то есть Вы предполагаете пустой Catch
Код dipsatcher Push использовался тысячи раз и при правильной настройки все ок, но если все же найдут ошибки, то их надо просто пофиксить.
В рамках Command/Query пишите логику обработки Ваших ошибок, но зачем Вам думать о ошибках в dispatcher.Push/Query, где код протестирован как Unit test, так и в проектах.
Вы считаете кого то удивите, называя и так известные книги, о которых знает каждый разработчик?
P.S. На все отвечать смысла не вижу, потому что остальное все в духе «Я прочитал кучу умных книг и использую все паттерны и пишу самый безопасный код» (Но кроме пары строк Вы так и ничего не показали своего)