Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
повторение boiler-plate-кода (всевозможные using, try-catch, log и т.д.)
При такой организации кода вы упадете именно в том месте, где попытаетесь установить не верный email, а не при сохранении в БД, которое может быть очень далеко от момента простановки значения, особенно при массовых операциях.
[Key, Index("IX_Email", 1, IsUnique = true)] public string Email { get { return _email; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentNullException("email"); } _email = value; } }
Моя реализация IEntity наиболее абстрактная – это метод, возвращающий Id в виде строки.
Я вижу разницу лишь в форме записи и лишней зависимости от EF, которая, впрочем, легко выпиливается при необходимости.
CQ[R]S – Command, Query [Responsibility] Segregation
Связки UoW+Command+Query+Specification+Validator
А как же валидация на клиенте? Эксепшн-то вы получили, но как вам получить список невалидных полей? (и это мы еще не затрагиваем сохранение драфтов)Про драфты не понял, а DataAnnotation отлично поддерживается jquery.unobtrusive. Вопрос не понял полностью:)
И при этом ваш следующий абзац называется Persistence Ignorance. Нет, серьезно? Собственно, именно поэтому как раз про ignorance у вас нет ни слова, только про persistenceОпять-же не понял. Уберите аттрибты, добавьте FluentValidation и классы паммингов. Сам объект не содержит никаких данных о том, как он хранится.
Так где же реальные примеры, зачем это надо? Абстрактные «соцсети» — это не пример, там можно и на OLTP/OLAP сделать. И нет ни слова про то, как же, собственно достигается консистентность всего этого циркаЭто не абстрактные соц.сети, а конкретные фиды в VK и Facebook
И самое интересное: хотя вы и сторонник Rich Model, в примерах команд у вас только CRUD. Это означает, что либо у вас на самом деле доменная модель анемичная, либо вы выполняете бизнес-операции мимо CQRS. Так как же оно на самом деле? Опишите хотя бы тот же пример с лайкамиЗдесь вы правы, примеры не привел, спасибо. Поищу и добавлю попозже.
Про драфты не понял
DataAnnotation отлично поддерживается jquery.unobtrusive.
Required стоит на свойстве Email, а проверка в коде (еще и не через Code Contracts). Я же специально пример вашего кода привел.Опять-же не понял. Уберите аттрибты, добавьте FluentValidation и классы паммингов.
Это не абстрактные соц.сети, а конкретные фиды в VK и Facebook
UnitOfWorkScope.GetFromScope().Commit();
dispatcher.Push(new AddSomeEntityCommand());
dispatcher.Push(s=>{
s.Quote(new Some1Command());
s.Quote(new Some2Command());
}); // share transaction
Тем более, что проблему дублирования linq-запросов в коде можно изящно решить вот так:
public class Account : IEntity { [BusinessRule] public static Expression<Func<Lead, bool>> ActiveRule = x => x.IsDeleted && x.Ballance > 0; }
new LeadOnlyDeletedWhere().And(new LeadGreaterBalance(0))
__queryFactory.GetQuery<Product>() .Where(Product.ActiveRule) // это статический экспрешн, как в примере с Account. Используется ExpressionSpecification .OrderBy(x => x.Id) .Paged(0, 10) // получаем 10 продуктов для первой страницы
Repository.Query(whereSpecification: new ProductByFullTextWher("test")
.And(new ProductBySomethingElseWhere("some"),
orderSpecification:new ProductByNameOrder(),
paginatedSpecification:new PaginatedSpecification(current,size))
ICommandFactory и IQueryFactory
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
});
}
}
}
@model AddAcoGroupCommand
<form action="@Url.Dispatcher().Push(new AddAcoGroupCommand())">
@Html.HiddenFor(r=>r.Id)
<input type="submit"/>
</form>
Command/Query должны иметь разный тип транзакций, ReadUncommited для query и ReadCommited для Command
Во-вторых, это же локальные правила, для конкретного Command и конкретного Query нужны такие настройки — но не для всех.
Во-первых, я бы для таких значений использовал неблокирующее чтение и обновление.
Можно добавить возможность переопределять, но lock таблицу при чтение (без записи) это не лучший вариант в плане perfromance
Вот, для Query ReadUncommited и используется.
Можно долго рассуждать о тонких настройках, но я опираюсь все же более часто встречаемые сценарии и по этому придерживаюсь однотипности решений.
Вы про SNAPSHOT и READ_COMMITTED_SNAPSHOT не слышали?
А читать данные, которые другая транзакция еще не объявила консистентными — лучший вариант?
Неплохо отдавать себе отчет в том, к чему ведут ваши «однотипные» решения.
Command/Query должны создавать UnitOfWork, только в момент обращения к Repository, иначе cache внутри будет все равно тратить ресурсы на открытие подключения.Я вообще отказался от репозиториев. По остальным моментам, думаю что у нас примерно, разница в основном во вкусовщине.
повторение boiler-plate-кода (всевозможные using, try-catch, log и т.д.)
Я сторонник богатой доменной модели (Rich Domain Model) и не люблю анемичную, поэтому Lead обладает правильным конструктором и не дает перевести себя в несогласованное состояние (нарушить инвариант).
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);
}
}
var taskCommand = new UpdatePatientTaskCommand()
{
Priority = PatientTask.TaskPriority.Normal,
Status = Status,
AssignedTo = assignedTo.GetValueOrDefault(),
Task = Dispatcher.Query(new GetTaskNameFromAppointmentQuery() { Appointment = appointment })
};
Dispatcher.Push(taskCommand);
Если у вас 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 и т.п.
С моей точки зрения код, который вы привели в пример имеет много проблем (как раз из серии перемешивания бизнес-правил и построения строчки).
Самое простое — ваш код можно «разломать» не передав Appointment.
Другой вопрос, что вот прям богатая модель нужна для сложных мест из предметной области (там где действительно важно и не понятно, что происходит).
Тупой CRUD можно писать на чистом CQRS и не париться
Рентабельный код 2: крадущийся DDD, затаившийся CQRS