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

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

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

  • Представление
  • Сервис
  • Репозиторий
  • Данные/Объект

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

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;
}

Какие-либо мысли?

18
задан Beep beep 19 December 2009 в 16:33
поделиться

8 ответов

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

Услуги

Услуги бывают трех видов: Услуги домена , Услуги приложений и Услуги инфраструктуры

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

Repository

RepositoryRepository : Используется для проверки доступа к данным и Conсистентности данных. В чистом DDD ваш Aggregate Roots будет отвечать за проверку согласованности (перед сохранением каких-либо объектов). В вашем случае будут использоваться проверки с уровня Domain Services.


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

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

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

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

Поддерживающую информацию вы можете найти в книге Эванса:

  • "Services and the Isolated Domain Layer" (стр. 106)
  • "Specifications" (стр. 224)
  • "Query Specifications" (стр. 229)
24
ответ дан 30 November 2019 в 06:17
поделиться

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

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

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

5
ответ дан 30 November 2019 в 06:17
поделиться

Я обнаружил, что новая книга Дино Эспозито Microsoft® .NET: Архитектура приложений для предприятия является отличным хранилищем знаний для подобных вопросов и проблем.

3
ответ дан 30 November 2019 в 06:17
поделиться

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

Я бы поставил это под сомнение. Вы можете расслабить это и позволить использовать и то, и другое на вашем уровне представления и сделать вашу жизнь проще для начала. Может быть, вы спросите себя, чего вы добиваетесь, скрывая такие репозитории. Вы уже абстрагируетесь от настойчивости и ставите под сомнение ИМПЛЕМЕНТАЦИЮ с ними. Это здорово и для чего они предназначены. Создается впечатление, что вы пытаетесь создать сервисный уровень, который скрывает тот факт, что ваши сущности вообще сохраняются. Я бы спросил, зачем?

Что касается подсчета суммарных сумм заказов и т.д. Ваш сервисный уровень будет естественным домом. Класс SalesOrderCalculator с методами LineTotal(LineItem lineItem) и OrderTotal(Order Order Order) подойдет. Вы также можете рассмотреть возможность создания соответствующего Factory, например, OrderServices.CreateOrderCalculator() для переключения реализации, если это необходимо (например, налог на скидку по заказу имеет специфические для страны правила). Это также может сформировать единую точку входа в службу заказов и упростить поиск с помощью IntelliSense.

Если все это звучит неработоспособно, возможно, вам необходимо более глубоко подумать о том, чего добиваются ваши абстракции, как они соотносятся друг с другом и о принципе Single Responsibility Principle. Репозиторий - это абстракция инфраструктуры (сокрытие сущностей HOW сохраняется и извлекается). Сервисы абстрагируют выполнение бизнес действий или правил и позволяют создать лучшую структуру для версионности или дисперсии. Обычно они не являются многослойными так, как вы описываете. Если у вас есть сложные правила безопасности в ваших Сервисах, ваши Репозитории могут быть лучшим домом. В типичной модели стиля DDD Repositories, Entities, Value Objects (Объекты ценности) и Services (Службы) будут использоваться вдоль друг друга на одном уровне и как часть одной модели. Слои выше (обычно представление) поэтому будут изолированы этими абстракциями. Внутри модели реализация одного сервиса может использовать абстракции другого. Дальнейшее уточнение добавляет правила того, кто содержит ссылки на какие сущности или объекты ценности, обеспечивающие более формальный контекст жизненного цикла. Для получения дополнительной информации по этому вопросу я бы порекомендовал изучить книгу Эрика Эванса или Domain Driven Design Quickly .

.
4
ответ дан 30 November 2019 в 06:17
поделиться

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

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

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();
    ...
  }
}

Если вам нужно очень мало этих дополнений, вы можете просто поместить их все в класс "DomainExtensions", но в противном случае я бы предложил относиться к ним с регулярным уважением и хранить все расширения сущностей в одном классе в своем собственном файле.

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

.
1
ответ дан 30 November 2019 в 06:17
поделиться

Если ваша технология 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);
   }

}
4
ответ дан 30 November 2019 в 06:17
поделиться

Уровень обслуживания.

1
ответ дан 30 November 2019 в 06:17
поделиться

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

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

Другими словами, сначала идет модель домена Domain Model, а затем все технологические реализации рассматриваются картографами mappers, которые сопоставляют модель домена с соответствующей технологией. Часто это включает оба способа: на уровень доступа к данным, где выбор ORM может накладывать ограничения, и на уровень пользовательского интерфейса, где технология пользовательского интерфейса накладывает дополнительные требования.

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

В вашем случае, то, что вы называете моделью анемического домена, на самом деле является уровнем доступа к данным. Лучшим вариантом будет определение Repositories, которые моделируют доступ к вашим сущностям технологически нейтральным способом.

В качестве примера рассмотрим вашу сущность заказа. Моделирование Ордена без ограничений технологией может привести нас к чему-то подобному:

public class Order
{
    // constructors and properties

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

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

Это должно быть сделано через абстрактный 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 (такие как Microsoft's Entity Framework) требуют, чтобы вы извлекали классы данных из определенных базовых классов, так что это совсем не подходит для Domain Objects в качестве POCO. Для этого требуется отображение.

Важно понимать, что вы, возможно, имеете сильно типизированные классы данных, которые семантически напоминают ваши Доменные Объекты. Однако, это чистая реализация, так что не путайте с этим. Класс Order, который выводится, например, из EntityObject не является Domain Class - это реализационная деталь, поэтому, когда вы реализуете IOrderRepository, вам нужно сопоставить Order Data Class с Order Doman Class.

Это может быть утомительной работой, но вы можете использовать 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);
}
11
ответ дан 30 November 2019 в 06:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: