Как избежать очень больших объектов с помощью Domain Driven Design - PullRequest
28 голосов
/ 24 мая 2010

Мы следуем доменно-управляемому дизайну для реализации большого сайта.

Однако, помещая поведение в доменные объекты, мы получаем очень большие классы.

Например, в нашем объекте WebsiteUser у нас много методов - например, работа с паролями, история заказов, возврат средств, сегментация клиентов. Все эти методы напрямую связаны с пользователем. Многие из этих методов делегируют внутренне другому дочернему объекту, но
это все еще приводит к некоторым очень большим классам.

Я стараюсь не подвергать множество дочерних объектов например user.getOrderHistory (). getLatestOrder ().

Какие еще стратегии можно использовать, чтобы избежать этой проблемы?

Ответы [ 6 ]

19 голосов
/ 24 мая 2010

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

Первое, что я бы порекомендовал, это взять один день или около того и прочитать Дизайн, управляемый доменом. Быстро , доступный для бесплатной загрузки с Info-Q. Это обеспечит обзор различных типов объектов домена: сущностей, объектов стоимости, сервисов, репозиториев и фабрик.

Второе, что я бы порекомендовал, - это прочитать «Принцип единой ответственности» .

Третье, что я бы порекомендовал, - это начать погружаться в Test Driven Development . Хотя обучение дизайну с помощью написания тестов вначале не обязательно делает ваши проекты великолепными, они, как правило, ведут вас к слабосвязанным проектам и выявляют проблемы проектирования раньше.

В приведенном вами примере у WebsiteUser определенно слишком много обязанностей. На самом деле, вам вообще может не потребоваться WebsiteUser, поскольку пользователи обычно представлены ISecurityPrincipal.

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

В долгосрочной перспективе вы действительно должны взять книгу Эрика Эванса Domain Driven Design *1022*. Это большое чтение, но оно того стоит. Я бы также порекомендовал вам забрать либо Agile Software Development, Принципы, Шаблоны и Практики или Agile Принципы, Шаблоны и Практики в C # в зависимости от ваших языковых предпочтений.

10 голосов
/ 25 мая 2010

Хотя у реальных людей много обязанностей, вы направляетесь к анти-шаблону объекта Бога .

Как уже намекнули другие, вы должны выделить эти обязанности в отдельные Репозитории и / или Доменные службы . E.g.:

SecurityService.Authenticate(credentials, customer)
OrderRepository.GetOrderHistoryFor(Customer)
RefundsService.StartRefundProcess(order)

Будьте конкретны с соглашениями об именах (то есть используйте OrderRepository или OrderService вместо OrderManager )

Вы столкнулись с этой проблемой из-за удобства . то есть удобно обрабатывать WebsiteUser как совокупный корень и получать к нему доступ через все.

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

Еще один способ думать об этом: точно так же, как Сущности не должны выполнять свои собственные постоянство (именно поэтому мы используем Репозитории ), ваш WebsiteUser не должен обрабатывать возврат / сегментация / и т. д.

Надеюсь, это поможет!

3 голосов
/ 24 мая 2010

Очень простое правило: «Большинство методов в вашем классе ДОЛЖНЫ использовать большинство переменных экземпляра в вашем классе» - если вы будете следовать этому правилу, классы будут автоматически иметь правильный размер.

2 голосов
/ 22 апреля 2013

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

2 голосов
/ 25 мая 2010

Вы можете рассмотреть вопрос о инверсии некоторых вещей. Например, клиенту не нужно иметь свойство Order (или историю заказов) - вы можете оставить его вне класса Customer. Так что вместо

public void doSomethingWithOrders(Customer customer, Calendar from, Calendar to) {
    List = customer.getOrders(from, to);
    for (Order order : orders) {
        order.doSomething();
    }
}

вы могли бы вместо этого сделать:

public void doSomethingWithOrders(Customer customer, Calendar from, Calendar to) {
    List = orderService.getOrders(customer, from, to);
    for (Order order : orders) {
        order.doSomething();
    }
}

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

2 голосов
/ 24 мая 2010

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

Например, в вашем случае вы можете иметь:

User u = ...;
OrderHistoryManager histMan = user.getOrderHistoryManager();

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

Думайте об этом так. Если у вас был объект «Человек», и вы должны были реализовать метод chew(). Вы бы поместили его в объект Human или дочерний объект Mouth.

...