Подтвердить что ты не робот

Как я могу разрешить конфликт между свободной связью/зависимостью и моделью богатого домена?

Изменить: это не конфликт на теоретическом уровне, а конфликт на уровне реализации.

Другое Редактирование: Проблема заключается не в том, что модели домена являются данными только -DTOs по сравнению с более богатой, более сложной картографией объекта, где Order имеет OrderItems и некоторую вычисляющую полную логику. Конкретная проблема заключается, например, в том, что, например, заказ должен получить последние оптовые цены OrderItem с некоторых веб-сервисов в Китае (например). Таким образом, у вас есть сервис Spring, который позволяет звонить на эту услугу PriceQuery в Китае. Заказ рассчиталTotal, который выполняет итерацию по каждому OrderItem, получает самую последнюю цену и добавляет ее к общей сумме.

Итак, как бы вы гарантировали, что каждый заказ имеет ссылку на эту услугу PriceQuery? Как вы можете восстановить его при де-сериализации, загрузке из БД и новых экземплярах? Это мой точный вопрос.

Простым способом было бы передать ссылку на метод calculateTotal, но что, если ваш объект использует эту услугу на протяжении всей своей жизни? Что, если он используется в 10 методах? Каждый раз становится бесполезным, чтобы передавать ссылки.

Другим способом было бы перевести вычислениеОбратно из Ордера и в OrderService, но это нарушает OO-дизайн, и мы движемся к старому способу "Транзакции Script".

Оригинальное сообщение:

Краткая версия: Богатые объекты домена требуют ссылок на многие компоненты, но эти объекты сохраняются или сериализуются, поэтому любые ссылки, которые они хранят на внешние компоненты (Spring beans в этом случае: службы, репозитории, что-либо), являются временными и уничтожаются. Они должны быть повторно инъецированы, когда объект де-сериализуется или загружается из БД, но это очень уродливо, и я не вижу элегантный способ сделать это.

Более длинная версия: Некоторое время я занимался свободной связью и DI с помощью Spring. Это очень помогло мне в управлении и проверке. Некоторое время назад, тем не менее, я читал проект, управляемый доменами, и некоторый Мартин Фаулер. В результате я пытался конвертировать мои модели домена из простых DTO (обычно простых представлений строки таблицы, а только без логических данных) в более богатую модель домена.

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

В основном есть точки, где я вручную вводя ссылку на контекст приложения в свой домен:

  • когда объект загружается из репозитория или другого ответственного объекта, поскольку ссылки на компоненты являются временными и, очевидно, не сохраняются.
  • когда объект создается из Factory, поскольку вновь созданный объект не имеет ссылок на компоненты
  • когда объект де-сериализуется в задании Quartz или в каком-либо другом месте, так как ссылки на переходные компоненты становятся протертыми

Во-первых, это уродливо, потому что я передаю объект ссылку на контекст приложения и ожидаю, что он вытащит ссылки на ссылки на нужные ему компоненты. Это не инъекция, а прямая тяга.

Во-вторых, это уродливый код, потому что во всех упомянутых местах мне нужна логика для инъекции appContext

В-третьих, он подвержен ошибкам, потому что я должен помнить, чтобы вводить во все эти места для всех этих объектов, что сложнее, чем кажется.

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

4b9b3361

Ответ 2

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

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

Рассмотрим вековой пример Orders и OrderItems. "Анемическая" модель домена будет выглядеть примерно так:

class Order {
    Long orderId;
    Date orderDate;
    Long receivedById;  // user which received the order
 }

 class OrderItem {
     Long orderId;      // order to which this item belongs
     Long productId;    // product id
     BigDecimal amount;
     BigDecimal price;
 }

На мой взгляд, точкой разработки, основанной на домене, является использование классов для лучшей моделировки отношений между объектами. Таким образом, не-анемичная модель будет выглядеть примерно так:

class Order {
   Long orderId;
   Date orderDate;
   User receivedBy;
   Set<OrderItem> items;
}

class OrderItem {
   Order order;
   Product product;
   BigDecimal amount;
   BigDecimal price;
}

Предположительно, вы бы использовали ORM-решение для отображения здесь. В этой модели вы сможете написать такой метод, как Order.calculateTotal(), который суммирует все amount*price для каждого элемента заказа.

Итак, модель будет богатой, в некотором смысле, что операции, которые имеют смысл с точки зрения бизнеса, например calculateTotal, будут помещены в объект домена Order. Но, по крайней мере, на мой взгляд, дизайн, основанный на доменах, не означает, что Order должен знать о ваших службах сохранения. Это должно быть сделано на отдельном и независимом уровне. Операции сохранения не являются частью бизнес-домена, они являются частью реализации.

И даже в этом простом примере есть много подводных камней. Если все Product загружаются с каждым OrderItem? Если имеется огромное количество элементов заказа, и вам нужен сводный отчет для огромного количества заказов, вы бы использовали Java, загружали объекты в память и вызывали calculateTotal() для каждого заказа? Или SQL-запрос - намного лучшее решение, со всех сторон. Вот почему достойное решение ORM, такое как Hibernate, предлагает механизмы для решения именно таких практических задач: ленивая загрузка с прокси-серверами для первого и HQL для последней. Какая польза от теоретически обоснованной модели, если генерация отчетов требует возраста?

Конечно, вся проблема довольно сложная, и многое другое, что я могу написать или рассмотреть за один присест. И я не говорю с позиции авторитета, но простой, повседневной практики в развертывании бизнес-приложений. Надеюсь, вы получите что-то из этого ответа. Не стесняйтесь предоставлять дополнительные сведения и примеры того, с чем вы имеете дело...

Изменить. Что касается службы PriceQuery и пример отправки электронной почты после вычисления итоговой суммы, я бы сделал различие между:

  • тот факт, что электронное письмо должно быть отправлено после расчета цены
  • какую часть заказа следует отправить? (это может также включать, скажем, шаблоны электронной почты).
  • фактический метод отправки электронной почты

Кроме того, нужно задаться вопросом, отправляет ли электронное письмо неотъемлемую способность Order или еще одну вещь, которую можно сделать с ним, например, продолжать ее, сериализовать в разные форматы (XML, CSV, Excel) и т.д.

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

 interface EmailSender {
     public void setSubject(String subject);
     public void addRecipient(String address, RecipientType type);
     public void setMessageBody(String body);
     public void send();
 }

Теперь, внутри класса Order, определите операцию, с помощью которой заказ "знает", как отправить себя как электронное письмо, используя отправителя электронной почты:

class Order {
...
    public void sendTotalEmail(EmailSender sender) {
        sender.setSubject("Order " + this.orderId);
        sender.addRecipient(receivedBy.getEmailAddress(), RecipientType.TO);
        sender.addRecipient(receivedBy.getSupervisor().getEmailAddress(), RecipientType.BCC);
        sender.setMessageBody("Order total is: " + calculateTotal());
        sender.send();
    }

Наконец, вы должны иметь фасад к вашим приложениям, точка, где происходит фактический ответ на действие пользователя. На мой взгляд, именно здесь вы должны получить (посредством Spring DI) фактические реализации сервисов. Это может быть, например, класс Spring MVC Controller:

public class OrderEmailController extends BaseFormController {
   // injected by Spring
   private OrderManager orderManager;  // persistence
   private EmailSender emailSender;    // actual sending of email

public ModelAndView processFormSubmission(HttpServletRequest request,
                                          HttpServletResponse response, ...) {
    String id = request.getParameter("id");
    Order order = orderManager.getOrder(id);
    order.sendTotalEmail(emailSender);

    return new ModelAndView(...);
}

Вот что вы получите с этим подходом:

  • объекты домена не содержат сервисы, они используют их
  • объекты домена отключаются от фактической реализации службы (например, SMTP, отправки в отдельный поток и т.д.) по характеру механизма интерфейса.
  • интерфейсы служб являются универсальными, многоразовыми, но не знают о каких-либо реальных объектах домена. Например, если заказ получает дополнительное поле, вам нужно изменить только класс Order.
  • вы можете легко обманывать сервисы и легко тестировать объекты домена.
  • вы можете легко протестировать реализацию реальных сервисов.

Я не знаю, соответствует ли это стандартам определенных гуру, но это подход, основанный на "земной" основе, который на практике работает достаточно хорошо.

Ответ 3

Regardinig

Что делать, если ваш Ордер должен отправить по электронной почте каждый раз, когда общее количество рассчитывается?

Я бы использовал события. Если это имеет смысл для вас, когда порядок вычисляет его общее количество, пусть он вызывает событие как eventDispatcher.raiseEvent(новый ComputedTotalEvent (this)).
Затем вы слушаете этот тип событий и вызываете ваш заказ, как сказано ранее, чтобы он отформатировал шаблон электронной почты и отправил его.
Объекты вашего домена остаются скудными, без каких-либо знаний об этом вашем требовании.
Короче говоря, разделите свою проблему на 2 требования:
- Я хочу знать, когда заказ вычисляет его общее количество.
- Я хочу отправить электронное письмо, когда у заказа есть (новое и другое) общее количество;

Ответ 4

Самый простой способ, который я могу представить, - добавить некоторую логику в ваш уровень доступа к данным, который будет вводить объект домена с его зависимостями, прежде чем возвращать его на более высокий уровень (обычно называемый уровнем сервиса). Вы можете аннотировать свойства каждого класса, чтобы указать, что нужно подключить. Если вы не используете Java 5+, вы можете реализовать интерфейс для каждого компонента, который нужно вставить, или даже объявить все это в XML и передать эти данные в контекст, который будет выполнять проводку. Если вы хотите получить фантазию, вы можете вывести это в один аспект и применить его глобально на вашем уровне доступа к данным, чтобы все методы, которые вытаскивали объекты домена, подключались к ним сразу после их возврата.

Ответ 5

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

Ответ 6

Шаблон Identity Map может помочь в вашем сценарии. Проверьте статью Образцы на практике, написанные Джереми Миллером, где он обсуждает этот шаблон.