Если вы вынуждены использовать модель анемической домены, где вы размещаете свою бизнес -логику и рассчитанные поля?

StackOverflow https://stackoverflow.com/questions/1933351

Вопрос

Наш нынешний инструмент O/RM на самом деле не допускает богатых моделей доменов, поэтому мы вынуждены использовать анемические (DTO) сущности повсюду. Это сработало нормально, но я продолжаю бороться с тем, где разместить основную объектную бизнес-логику и рассчитанные поля.

Текущие слои:

  • Презентация
  • обслуживание
  • Репозиторий
  • Данные/сущность

Наш уровень репозитория имеет большую часть основной логики Fetch/Validate/Save, хотя уровень сервиса выполняет много более сложной проверки и сохранения (поскольку операции сохранения также выполняют регистрацию, проверка разрешений и т. Д.). Проблема в том, куда поместить код таким:

Decimal CalculateTotal(LineItemEntity li)
{
  return li.Quantity * li.Price;
}

или же

Decimal CalculateOrderTotal(OrderEntity order)
{
  Decimal orderTotal = 0;
  foreach (LineItemEntity li in order.LineItems)
  {
    orderTotal += CalculateTotal(li);
  }
  return orderTotal;
}

Какие-нибудь мысли?

Это было полезно?

Решение

Вернемся к основам:

Услуги

Услуги поступают в 3 вкуса: Доменные услуги, Служба приложения, а также Инфраструктурные услуги

  • Доменные услуги : Инкапсулирует бизнес -логику, которая не естественно вписывается в объект домена. В твоем случае, все вашей бизнес -логики.
  • Служба приложения : Используется внешними потребителями, чтобы поговорить с вашей системой
  • Инфраструктурные услуги : Используется для абстрактных технических проблем (например, MSMQ, поставщик электронной почты и т. Д.)

Репозиторий

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


Предложенное решение: Разделите свои существующие услуги отдельно

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

Использовать Служба приложения чтобы разоблачить необходимые методы выбора (FetchOpenOrdersWithLines), которые пересылают запросы на ваши Репозиторий (и используйте дженерики, как предложил Джереми). Вы также можете рассмотреть возможность использования Спецификации запроса Чтобы обернуть свои запросы.

От твоего Репозиторий, использовать Характеристики в твоей Доменные услуги слой, чтобы проверить согласованность объекта и т. Д., Прежде чем сохранять ваши объекты.

Вы можете найти вспомогательную информацию в книге Эванса:

  • «Услуги и изолированный доменный слой» (стр. 106)
  • "Характеристики" (стр. 224)
  • "Спецификации запроса" (стр. 229)

Другие советы

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

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

Другими словами, Доменная модель на первом месте, и все технологические реализации впоследствии решаются Создатели Эта карта между доменной моделью и рассматриваемой технологией. Это часто будет включать в себя обоих способов: к уровню доступа к данным, где выбор ORM может вводить ограничения, и для уровня пользовательского интерфейса, где технология пользовательского интерфейса налагает дополнительные требования.

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

В вашем случае то, что вы называете моделью анемичной домена, на самом деле является уровнем доступа к данным. Ваше лучшее средство - это определить Репозитории Эта модель доступ к вашим объектам нейтральным образом.

В качестве примера, давайте посмотрим на ваш заказ. Моделирование порядка, не ограниченного технологиями

public class Order
{
    // constructors and properties

    public decimal CalculateTotal()
    {
        return (from li in this.LineItems
                select li.CalculateTotal()).Sum();
    }
}

Обратите внимание, что это простой старый объект CLR ( Поко ), таким образом, не ограничивается технологиями. Теперь вопрос в том, как вы получаете это в своем магазине данных?

Это должно быть сделано с помощью абстрактного iorderRepository:

public interface IOrderRepository
{
    Order SelectSingle(int id);

    void Insert(Order order);

    void Update(Order order);

    void Delete(int id);

    // more, specialized methods can go here if need be
}

Теперь вы можете реализовать iorderRepository, используя ваш ORM по выбору. Тем не менее, некоторые ORM (такие как Entity Framework Microsoft) требуют, чтобы вы получали классы данных из определенных базовых классов, поэтому это вообще не соответствует объектам домена в качестве POCO. Таким образом, картирование требуется.

Важно понять, что вы, возможно, сильно напечатали классы данных, которые семантически напоминайте ваши доменные сущности. Тем не менее, это чистая деталь реализации, поэтому не путайте это. Класс заказов, который вытекает из EG EntityObject не класс доменов - Это деталь реализации, поэтому, когда вы реализуете iorderRepository, вам нужно сопоставить заказ Класс данных к заказу Доман класс.

Это может быть утомительная работа, но вы можете использовать Automapper сделать это для вас.

Вот как может выглядеть реализация метода Selectsingle:

public Order SelectSinge(int id)
{
    var oe = (from o in this.objectContext.Orders
              where o.Id == id
              select o).First();
    return this.mapper.Map<OrderEntity, Order>(oe);
}

Это именно то, для чего предназначен уровень сервиса - я также видел приложения, где он называется BusinessLogic Layer.

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

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

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

Я бы это задавал вопросом. Вы можете расслабить это и позволить оба использовать в вашем презентационном слое и сделать вашу жизнь проще для начала. Может быть, спросите себя, чего вы достигаете, скрывая такие репозитории. Вы уже абстрагируете настойчивость и запрашиваете реализацию с ними. Это здорово и для чего они предназначены. Кажется, что вы пытаетесь создать сервисный слой, который скрывает тот факт, что ваши сущности вообще сохраняются. Я бы спросил, почему?

Что касается расчета итоги заказа и т. Д., Ваш сервисный слой будет естественным домом. Класс SalesorderCalculator с методами Linetotal (LineItem LineItem) и OrderTotal (заказ заказа) будет хорошо. Вы также можете рассмотреть вопрос о создании соответствующей заводской заводской, например, Ordersersers.createRoderCalculator () для переключения реализации, если это необходимо (например, налог на скидку на заказ имеет, например, правила конкретной страны). Это также может сформировать единственную точку входа для заказа услуг и облегчить поиск вещей через Intellisense.

Если все это звучит неработочно, это может быть, вам нужно более глубоко подумать о том, чего достигают ваши абстракции, как они относятся друг к другу и Принцип единственной ответственности. Анкет Репозиторий - это абстракция инфраструктуры (скрывая, как сущности сохраняются и извлечены). Услуги абстрагируют внедрение деловых действий или правил и обеспечивают лучшую структуру для управления версиями или дисперсией. Они, как правило, не наложены так, как вы описываете. Если у вас есть сложные правила безопасности в ваших услугах, ваши репозитории могут быть лучшим домом. В типичной модели стиля DDD репозитории, объекты, объекты стоимости и услуги будут использоваться вместе на стороне друг друга в одном и том же уровне и как часть одной и той же модели. Слои выше (обычно презентация), следовательно, будут изолированы этими абстракциями. Внутри модели реализация одной услуги может использовать абстракцию другой. Дальнейшее уточнение добавляет правила, кто содержит ссылки, на которые объекты или объекты ценностей обеспечивают более формальный контекст жизненного цикла. Для получения дополнительной информации об этом я бы порекомендовал изучить Эрик Эванс книга или же Доменная дизайн быстро.

Если ваша технология ORM только хорошо обрабатывает объекты DTO, это не значит, что вам нужно выбрасывать богатые объекты сущности. Вы все еще можете обернуть свои объекты DTO объектами объектов:

public class MonkeyData
{
   public string Name { get; set; }
   public List<Food> FavoriteFood { get; set; }
}

public interface IMonkeyRepository
{
   Monkey GetMonkey(string name) // fetches DTO, uses entity constructor
   void SaveMonkey(Monkey monkey) // uses entity GetData(), stores DTO
}


public class Monkey
{
   private readonly MonkeyData monkeyData;

   public Monkey(MonkeyData monkeyData)
   {
      this.monkeyData = monkeyData;
   }

   public Name { get { return this.monkeyData.Name; } }

   public bool IsYummy(Food food)
   {
      return this.monkeyData.FavoriteFood.Contains(food);
   }

   public MonkeyData GetData()
   {
      // CLONE the DTO here to avoid giving write access to the
      // entity innards without business rule enforcement
      return CloneData(this.monkeyData);
   }

}

Я нашел новую книгу Дино Эспозито Microsoft® .net: архитекция приложений для предприятия быть отличным хранилищем знаний для этих типов вопросов и вопросов.

Сервисный слой.

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

Например (из ваших примеров):

public static class LineItemEntityExtensions
{
  public static decimal CalculateTotal(this LineItemEntity li)
  {
    return li.Quantity * li.Price;
  }
}

public static class OrderEntityExtensions
{
  public static decimal CalculateOrderTotal(this OrderEntity order)
  {
    decimal orderTotal = 0;
    foreach (LineItemEntity li in order.LineItems)
      orderTotal += li.CalculateTotal();
    return orderTotal;
  }
}

public class SomewhereElse
{
  public void DoSomething(OrderEntity order)
  {
    decimal total = order.CalculateOrderTotal();
    ...
  }
}

Если есть очень мало этих дополнений, которые вы хотите, вы можете просто поместить их все в класс «домены», но в противном случае я бы предложил относиться к ним с регулярным уважением и сохранить все расширения субъекта в одном классе в своем собственном файле Анкет

К вашему сведению: единственный раз, когда я это сделал, это когда у меня было решение L2S, и я не хотел связываться с частичными. У меня также не было много расширений, потому что решение было небольшим. Мне нравится идея с полным взорванным доменным слоем лучше.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top