6

Я борюсь с проблемой, связанной с DDD, с Спецификациями, и я много читал в DDD, спецификациях и репозиториях.Спецификация шаблона в доменном дизайне

Однако есть проблема, если вы попытаетесь объединить все 3 из них, не нарушая ведомый домен. Это сводится к тому, как применять фильтры с учетом производительности.

Первые несколько очевидных фактов:

  1. Хранилища к полученному DataAccess/Infrastructure слой
  2. Доменные Модели представляют бизнес-логику и перейти к слою домена
  3. Модели доступа к данным представляют персистенции слой и перейдите к Уровень Persistance/Infrastructure/DataAccess
  4. Бизнес-логика переходит на доменный уровень
  5. Технические характеристики Business Logic, поэтому они также относятся к слою Domain.
  6. Во всех этих примерах ОРМ Framework и SQL Server используется внутри Repository
  7. ПЕРСИСТЕНТНОСТЬ Модели не могут просочиться в домене слоя

До сих пор так легко. Проблема возникает, когда/если мы пытаемся применить Спецификации к репозиторию и не нарушать шаблон DDD или проблемы с производительностью.

Возможные способы применения Технических характеристик:

1) Классический способ: Спецификации с использованием модели предметной области в домене уровне

применить традиционные спецификации шаблона, с помощью метода IsSatisfiedBy, возвращая bool и композитные спецификации для объединения нескольких спецификаций.

Это позволило нам сохранить спецификации в домене уровня, но ...

  1. Он должен работать с моделями домена, в то время как хранилище использует персистенции модели, которые представляют структуру данных слоя сохранения. Это легко исправить с использованием таких карт, как AutoMapper.
  2. Однако проблема, которая не может быть решена: все спецификации должны выполняться в памяти. В больших таблицах/базах данных это означает, что огромное влияние, если вы должны перебрать все юридическое лицо, только чтобы отфильтровать один, которые отвечают Вашим требованиям

2) Технические характеристик с помощью Стойкости Модели

Это похоже до 1), но с использованием моделей устойчивости в спецификации. Это позволяет напрямую использовать Спецификацию как часть нашего предиката .Where, который будет переведен в запрос (т. Е. TSQL), и фильтрация будет выполняться в хранилище Persistence (то есть SQL Server).

  1. Хотя это дает хорошие результаты, оно явно нарушает шаблон DDD. Наша модель Persistence просачивается в слой Domain, что делает слой домена зависимым от уровня Persistence, а не наоборот.

3) Как 2), но сделать спецификации Часть Persistence Layer

  1. Это не работает, потому что домен Layer должен ссылаться на спецификации. Он все равно будет зависеть от уровня сохранения.
  2. У нас была бы логика бизнеса внутри слоя Persistence. Который также нарушает DDD рисунка на

4) Мне нравится 3, но использовать абстрагировать характеристики как интерфейсы

Мы бы Спецификация интерфейсов в нашем домене слоя наших конкретных реализаций спецификации в Persistence Layer. Теперь наш слой домена будет взаимодействовать только с интерфейсами и не будет зависеть от уровня Persistence.

  1. Это по-прежнему нарушает № 2 из 3). У нас была бы бизнес-логика в плане сохранения, что плохо.

5) Перевести дерево Expression от модели предметной области в Persistence модели

Это, конечно, решает эту проблему, но это нетривиальная задача, но она будет держать спецификации внутри нашего домена слоя в то же время выгоды от оптимизация SQL, поскольку характеристика становится частью Хранилища, где положение и переводит в TSQL

Я пытался идти этот подход и есть несколько вопросов (форма боковых реализации):

  1. Нам нужно знать конфигурацию из Mapper (если мы ее используем) или сохранить нашу собственную систему сопоставления. Это может быть частично выполнено (чтение конфигурации Mapper) с помощью AutoMapper, но существуют дополнительные проблемы
  2. Это приемлемо для одного, где одно свойство модели A сопоставляется с одним свойством модели B. Сложнее, если типы различны (т.е. из-за типов сохраняемости, например, перечисления, которые сохраняются в виде строк или пар ключ/значение в другой таблице, и нам нужно делать преобразования внутри распознавателя.
  3. Это становится довольно сложным, если несколько полей отображаются в одно поле назначения. это не является проблемой для домена Model -> Постоянство Модель отображения

** 6) Query Builder, как API **

Последний из них делает какой-то API запросов, который передается в спецификацию и из которого слой репозитория/сдерживания будет генерировать дерево выражений, которое должно быть передано в .Where, и которое использует интерфейс для объявления всех фильтруемых полей.

Я сделал несколько попыток в этом направлении, но не был слишком доволен результатами. Что-то вроде

public interface IQuery<T> 
{ 
    IQuery<T> Where(Expression<Func<T, T>> predicate); 
} 
public interface IQueryFilter<TFilter> 
{ 
    TFilter And(TFilter other); 
    TFilter Or(TFilter other); 
    TFilter Not(TFilter other); 
} 

public interface IQueryField<TSource, IQueryFilter> 
{ 
    IQueryFilter Equal(TSource other); 
    IQueryFilter GreaterThan(TSource other); 
    IQueryFilter Greater(TSource other); 
    IQueryFilter LesserThan(TSource other); 
    IQueryFilter Lesser(TSource other); 
} 
public interface IPersonQueryFilter : IQueryFilter<IPersonQueryFilter> 
{ 
    IQueryField<int, IPersonQueryFilter> ID { get; } 
    IQueryField<string, IPersonQueryFilter> Name { get; } 
    IQueryField<int, IPersonQueryFilter> Age { get; } 
} 

и в спецификации мы бы передать IQuery<IPersonQueryFilter> query конструктору спецификации, а затем применить спецификации к нему при использовании или его сочетания.

IQuery<IGridQueryFilter> query = null; 

query.Where(f => f.Name.Equal("Bob")); 

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

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

Как вы решаете это в приложении реального мира, не занимаясь фильтрацией или утечкой памяти Стойкость к доменному слою?

Существуют ли какие-либо фреймворки, которые решают проблемы выше с помощью одного из двух способов (Query Builder, например, синтаксиса для деревьев выражений или транслятора дерева выражений)?

+0

Я рад, что нашел ваш вопрос: у меня были ** именно те же вопросы и те же предложения об отделении моделей и шаблона спецификации! Я нашел это [ссылка] (http://enterprisecraftsmanship.com/2016/04/05/having-the-domain-model-separate-from-the-persistence-model/), где парень объясняет, что наличие отдельных моделей приносит немного больше чистоты для вашей модели, но принести много накладных расходов. Для него это не стоит. Я надеюсь, что эта статья была бы полезна :-) – Arcord

+0

Демонстрация общих спецификаций с универсальным репозиторием EF, где spec используется как для фильтрации, так и для загрузки загружаемых данных: http://deviq.com/specification-pattern/ – ssmith

ответ

2

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

Спецификация шаблона помогает развить вездесущий язык, я думаю, что это похоже на DSL. Он заявляет, что делать, а не как это делать. Например, в контексте заказа заказы считаются просроченными, если они были размещены, но не оплачены в течение 30 минут. С шаблоном Specification ваша команда может говорить с коротким, но уникальным термином: OverdueOrderSpecification.Представьте себе, обсуждение ниже:

случай -1

Business people: I want to find out all overdue orders and ... 
Developer: I can do that, it is easy to find all satisfying orders with an overdue order specification and.. 

случай -2

Business people: I want to find out all orders which were placed before 30 minutes and still unpaid... 
Developer: I can do that, it is easy to filter order from tbl_order where placed_at is less that 30minutes before sysdate.... 

Какой из них вы предпочитаете?

Обычно нам нужен обработчик DSL для синтаксического анализа dsl, в этом случае он может быть в адаптере сохранения, переводит спецификацию в критерии запроса. Эта зависимость (infrastrructure.persistence => domain) не нарушает принципала архитектуры.

class OrderMonitorApplication { 
    public void alarm() { 
     // The specification pattern keeps the overdue order ubiquitous language in domain 
     List<Order> overdueOrders = orderRepository.findBy(new OverdueSpecification()); 
     for (Order order: overdueOrders) { 
      //notify admin 
     } 
    } 
} 

class HibernateOrderRepository implements orderRepository { 
    public List<Order> findBy(OrderSpecification spec) { 
     criteria.le("whenPlaced", spec.placedBefore())//returns sysdate - 30 
     criteria.eq("status", spec.status());//returns WAIT_PAYMENT 
     return ... 
    } 
} 
+0

Спасибо, я еще раз посмотрю на CQRS – Tseng

1

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

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

Я лично не вижу в этом многих преимуществ. Я считаю, что у вас есть резидентная модель (обычно) в вашей базе данных и модель домена в памяти в вашем приложении. Зазор между ними соединяется действием, а не моделью. Это действие может быть выполнено ORM. Мне еще предстоит продать тот факт, что «объектная модель Persistence» действительно имеет смысл семантически, не говоря уже об обязательном уважении принципов DDD (*).

Теперь существует подход CQRS, где у вас есть отдельная модель чтения, но это совершенно другое животное, и в этом случае я бы не видел Specifications, действующего на объекты Read Model вместо Entities, в качестве нарушения DDD. Спецификация - это, в конце концов, очень общий шаблон, который ничто в DDD принципиально не ограничивает Entities.

(*) Edit: Automapper создатель Джимми Богард, кажется, чтобы найти его усложненной, а также - см How do I use automapper to map many-to-many relationships?

+0

Это для пример http://www.mehdi-khalili.com/orm-anti-patterns-part-4-persistence-domain-model Существует много вещей, говорящих против использования модели сохранения в качестве модели домена. Для отношений внешних ключей с Entity Framework требуется, чтобы у нас было поле StudentID и поле Student, содержащее фактическую информацию, но StudentID (например, Autoindexed) является чистым полем уровня персистентности и требуется только для реляционных баз данных. Это было бы необходимо только для объединений, поскольку объединения в ints лучше, чем использование строк (т.используя некоторую уникальную строку идентификации) – Tseng

+0

Я совершенно не согласен с этой статьей, по целому ряду причин, от «да», Entity Framework * может * сделать это, если вы копаете немного », чтобы« вы устанавливаете догмы в камне для себя только для того, чтобы обойти 1 или 2 недостатка в ORM ", чтобы" ORMs уже есть то, как вы набираете вещи, не выполняйте работу дважды ". – guillaume31

+0

@Tseng Предъявление внешнего ключа, которое вы делаете, кажется неточным: http://stackoverflow.com/questions/15595818/mapkey-vs-hasforeignkey-difference-fluent-api http://msdn.microsoft.com/en-us/data/jj591620 # IndependentAssociation – guillaume31

3

После того, как я реализовал спецификации, но ...

  1. Она была основана на LINQ и IQueryable.
  2. Он использовал единый унифицированный репозиторий (но для меня это неплохо, и я думаю, что это основная причина использования Спецификации).
  3. Он использовал одну модель для домена и постоянных потребностей (что я считаю плохим).

Repository:

public interface IRepository<TEntity> where TEntity : Entity, IAggregateRoot 
{ 
    TEntity Get<TKey>(TKey id); 

    TEntity TryGet<TKey>(TKey id); 

    void DeleteByKey<TKey>(TKey id); 

    void Delete(TEntity entity); 

    void Delete(IEnumerable<TEntity> entities); 

    IEnumerable<TEntity> List(FilterSpecification<TEntity> specification); 

    TEntity Single(FilterSpecification<TEntity> specification);   

    TEntity First(FilterSpecification<TEntity> specification); 

    TResult Compute<TResult>(ComputationSpecification<TEntity, TResult> specification); 

    IEnumerable<TEntity> ListAll(); 

    //and some other methods 
} 

спецификация фильтра:

public abstract class FilterSpecification<TAggregateRoot> where TAggregateRoot : Entity, IAggregateRoot 
{ 

    public abstract IQueryable<TAggregateRoot> Filter(IQueryable<TAggregateRoot> aggregateRoots); 

    public static FilterSpecification<TAggregateRoot> CreateByPredicate(Expression<Func<TAggregateRoot, bool>> predicate) 
    { 
     return new PredicateFilterSpecification<TAggregateRoot>(predicate); 
    }  

    public static FilterSpecification<TAggregateRoot> operator &(FilterSpecification<TAggregateRoot> op1, FilterSpecification<TAggregateRoot> op2) 
    { 
     return new CompositeFilterSpecification<TAggregateRoot>(op1, op2); 
    }   

    public static FilterSpecification<TAggregateRoot> CreateDummy() 
    { 
     return new DummyFilterSpecification<TAggregateRoot>(); 
    } 

} 


public class CompositeFilterSpecification<TAggregateRoot> : FilterSpecification<TAggregateRoot> where TAggregateRoot : Entity, IAggregateRoot 
{ 

    private readonly FilterSpecification<TAggregateRoot> _firstOperand; 
    private readonly FilterSpecification<TAggregateRoot> _secondOperand; 

    public CompositeFilterSpecification(FilterSpecification<TAggregateRoot> firstOperand, FilterSpecification<TAggregateRoot> secondOperand) 
    { 
     _firstOperand = firstOperand; 
     _secondOperand = secondOperand; 
    } 

    public override IQueryable<TAggregateRoot> Filter(IQueryable<TAggregateRoot> aggregateRoots) 
    { 
     var operand1Results = _firstOperand.Filter(aggregateRoots); 
     return _secondOperand.Filter(operand1Results); 
    } 
} 

public class PredicateFilterSpecification<TAggregateRoot> : FilterSpecification<TAggregateRoot> where TAggregateRoot : Entity, IAggregateRoot 
{ 

    private readonly Expression<Func<TAggregateRoot, bool>> _predicate; 

    public PredicateFilterSpecification(Expression<Func<TAggregateRoot, bool>> predicate) 
    { 
     _predicate = predicate; 
    } 

    public override IQueryable<TAggregateRoot> Filter(IQueryable<TAggregateRoot> aggregateRoots) 
    { 
     return aggregateRoots.Where(_predicate); 
    } 
} 

Другой вид спецификации:

public abstract class ComputationSpecification<TAggregateRoot, TResult> where TAggregateRoot : Entity, IAggregateRoot 
{ 

    public abstract TResult Compute(IQueryable<TAggregateRoot> aggregateRoots); 

    public static CompositeComputationSpecification<TAggregateRoot, TResult> operator &(FilterSpecification<TAggregateRoot> op1, ComputationSpecification<TAggregateRoot, TResult> op2) 
    { 
     return new CompositeComputationSpecification<TAggregateRoot, TResult>(op1, op2); 
    } 

} 

и использований:

OrderRepository.Compute(new MaxInvoiceNumberComputationSpecification()) + 1 
PlaceRepository.Single(FilterSpecification<Place>.CreateByPredicate(p => p.Name == placeName)); 
UnitRepository.Compute(new UnitsAreAvailableForPickingFilterSpecification() & new CheckStockContainsEnoughUnitsOfGivenProductComputatonSpecification(count, product)); 

Пользовательские реализации могут выглядеть

public class CheckUnitsOfGivenProductExistOnPlaceComputationSpecification : ComputationSpecification<Unit, bool> 
{ 
    private readonly Product _product; 
    private readonly Place _place; 

    public CheckUnitsOfGivenProductExistOnPlaceComputationSpecification(
     Place place, 
     Product product) 
    { 
     _place = place; 
     _product = product; 
    } 

    public override bool Compute(IQueryable<Unit> aggregateRoots) 
    { 
     return aggregateRoots.Any(unit => unit.Product == _product && unit.Place == _place); 
    } 
} 

Наконец, я вынужден сказать, что простой Specficiation реализация подходит плохо в соответствии с DDD. Вы провели большие исследования в этой области, и маловероятно, что кто-то предлагает что-то новое :). Также обратите внимание на блог http://www.sapiensworks.com/blog/.

+0

Я дам CQRS более пристальный взгляд. В блоге, который вы указали, говорится, что использовать CRUD для структур данных и CQRS для Rich Business Objects в одном проекте можно. Каков ваш опыт? – Tseng

+0

У меня недостаточно опыта использования CQRS. –

Смежные вопросы