Предприятия DDD, использующие услуги
У меня есть приложение, которое я пытаюсь построить с хотя бы номинальной моделью типа DDD-типа, и я борюсь с определенной частью.
Моя сущность имеет некоторую бизнес-логику, которая использует некоторые финансовые расчеты и расчеты скорости, которые у меня есть в некоторых сервисах домена, а также некоторые постоянные значения, которые я помещаю в объект значения.
Я борюсь с тем, как заставить сущность использовать логику внутри служб домена, или же логика внутри этих сервисов даже присутствует. Это то, что у меня есть до сих пор:
public class Ticket
{
public Ticket(int id, ConstantRates constantRates, FinancialCalculationService f, RateCalculationService r)
{
Id = id;
ConstantRates = constantRates;
FinancialCalculator = f;
RateCalculator = r;
}
private FinancialCalculationService FinancialCalculator { get; set; }
private RateCalculationService RateCalculator { get; set; }
private ConstantRates ConstantRates { get; set; }
public int Id { get; private set; }
public double ProjectedCosts { get; set; }
public double ProjectedBenefits { get; set; }
public double CalculateFinancialGain()
{
var discountRate = RateCalculator.CalculateDiscountRate(ConstantRates.Rate1, ConstantRates.Rate2,
ConstantRates.Rate3);
return FinancialCalculator.CalculateNetPresentValue(discountRate,
new[] {ProjectedCosts*-1, ProjectedBenefits});
}
}
public class ConstantRates
{
public double Rate1 { get; set; }
public double Rate2 { get; set; }
public double Rate3 { get; set; }
}
public class RateCalculationService
{
public double CalculateDiscountRate(double rate1, double rate2, double rate3 )
{
//do some jibba jabba
return 8.0;
}
}
public class FinancialCalculationService
{
public double CalculateNetPresentValue(double rate, params double[] values)
{
return Microsoft.VisualBasic.Financial.NPV(rate, ref values);
}
}
Я чувствую, что некоторые из этих логических вычислений относятся к этим службам домена, но мне не очень нравится, что мне придется вручную вводить эти зависимости из моего репозитория. Есть ли альтернативный способ, которым это должно быть смоделировано? Я не прав, не нравится это?
Прочитав "Синюю книгу", но до сих пор ничего не строил в этом стиле, я ищу руководство.
ИЗМЕНИТЬ
Спасибо всем за отзыв! Основываясь на том, что я слышу, похоже, что моя модель должна выглядеть следующим образом. Это выглядит лучше?
public class Ticket
{
public Ticket(int id)
{
Id = id;
}
private ConstantRates ConstantRates { get; set; }
public int Id { get; private set; }
public double ProjectedCosts { get; set; }
public double ProjectedBenefits { get; set; }
public double FinancialGain { get; set; }
}
public class ConstantRates
{
public double Rate1 { get; set; }
public double Rate2 { get; set; }
public double Rate3 { get; set; }
}
public class FinancialGainCalculationService
{
public FinancialGainCalculationService(RateCalculationService rateCalculator,
FinancialCalculationService financialCalculator,
ConstantRateFactory rateFactory)
{
RateCalculator = rateCalculator;
FinancialCalculator = financialCalculator;
RateFactory = rateFactory;
}
private RateCalculationService RateCalculator { get; set; }
private FinancialCalculationService FinancialCalculator { get; set; }
private ConstantRateFactory RateFactory { get; set; }
public void CalculateFinancialGainFor(Ticket ticket)
{
var constantRates = RateFactory.Create();
var discountRate = RateCalculator.CalculateDiscountRate(constantRates.Rate1, constantRates.Rate2,
constantRates.Rate3);
ticket.FinancialGain = FinancialCalculator.CalculateNetPresentValue(discountRate,
new[] {ticket.ProjectedCosts*-1, ticket.ProjectedBenefits});
}
}
public class ConstantRateFactory
{
public ConstantRates Create()
{
return new ConstantRates();
}
}
public class RateCalculationService
{
public double CalculateDiscountRate(double rate1, double rate2, double rate3 )
{
//do some jibba jabba
return 8.0;
}
}
public class FinancialCalculationService
{
public double CalculateNetPresentValue(double rate, params double[] values)
{
return Microsoft.VisualBasic.Financial.NPV(rate, ref values);
}
}
В настоящий момент модель домена оказывается довольно анемичной, но поскольку я добавляю функции, возможно, она будет иметь больше.
РЕДАКТИРОВАТЬ 2
Хорошо, я получил еще несколько отзывов о том, что, возможно, мои "расчетные" услуги больше похожи на объекты стратегии, которые для моего Entity зависят. Здесь еще раз возьмем его с большей логикой в Entity и используя эти объекты стратегии. Мысли об этом? Любые проблемы с копированием этих помощников непосредственно в Entity? Я не думаю, что хочу издеваться над ними в своих тестах, но OTOH Я не могу проверить метод CalculateFinancialGain, не тестируя эти объекты стратегии.
public class Ticket
{
public Ticket(int id, ConstantRates constantRates)
{
Id = id;
ConstantRates = constantRates;
}
private ConstantRates ConstantRates { get; set; }
public int Id { get; private set; }
public double ProjectedCosts { get; set; }
public double ProjectedBenefits { get; set; }
public double CalculateFinancialGain()
{
var rateCalculator = new RateCalculator();
var financeCalculator = new FinanceCalculator();
var discountRate = rateCalculator.CalculateDiscountRate(ConstantRates.Rate1, ConstantRates.Rate2,
ConstantRates.Rate3);
return financeCalculator.CalculateNetPresentValue(discountRate,
ProjectedCosts*-1,
ProjectedBenefits);
}
}
public class ConstantRates
{
public double Rate1 { get; set; }
public double Rate2 { get; set; }
public double Rate3 { get; set; }
}
public class RateCalculator
{
public double CalculateDiscountRate(double rate1, double rate2, double rate3 )
{
//do some jibba jabba
return 8.0;
}
}
public class FinanceCalculator
{
public double CalculateNetPresentValue(double rate, params double[] values)
{
return Microsoft.VisualBasic.Financial.NPV(rate, ref values);
}
}
Ответы
Ответ 1
Попросите службу принять объект Ticket
в качестве параметра. Услуги должны быть неактивными, и одна и та же служба должна предоставлять свои услуги любому количеству объектов.
В вашей ситуации я вытащил бы FinancialCalculatorService
и RateCalculatorService
из вашей сущности и сделал бы методы в каждой службе принимать объект Ticket
как параметр.
Возьмите секунду и прочитайте pg. 105 Проект, управляемый доменом Эриком Эвансом
Ответ 2
Учитывая то, что мы видели в классах, я не думаю, что они действительно являются сервисами в синем смысле книги, и Я бы сохранил калькуляторы в Ticket
.
Ни FinancialCalculatorService
, либо RateCalculationService
не имеет зависимостей от объектов домена - оба они работают с примитивными значениями. Приложениям не нужно беспокоиться о том, как рассчитать финансовую выгоду, которая возникла бы из билета, поэтому важно инкапсулировать эту информацию внутри самого билета.
Если у них действительно нет зависимостей от объектов домена, подумайте о них как о "автономных классах", а не о "услугах" (опять же, в терминологии синих книг). Конечно, для Ticket
зависит от объектов стратегии (FinancialCalculator
и RateCalculator
), которые сами по себе не имеют экзотических зависимостей и сами не изменяют состояние объектов домена.
Обновить для редактирования 2. Я думаю, что одним из преимуществ создания отдельных классов калькуляторов является то, что вы можете тестировать их независимо от Ticket
. Строго говоря, билеты не отвечают за выполнение этих вычислений, они несут ответственность за правильное обращение к тем сотрудничающим классам. Поэтому я был бы склонен сделать их способными к инъекции/макету, как они были в вашем первоначальном примере.
Ответ 3
Я бы сказал, что службы используют объекты, а не наоборот.
другое дело, не уверенное в вашем домене, но вы определенный билет - это объект, а не объект значения?
Ответ 4
На самом деле вы натолкнулись на вопрос о том, что было довольно много обсуждений. Есть верующие на обеих сторонах треков, поэтому вам нужно решить для себя, что имеет наибольший смысл.
Лично у меня нет моих сущностей, использующих сервисы, поскольку он создает много работы вокруг "Как я могу получить услуги в своих сущностях?" вопрос.
Мне кажется, что CalculateFinancialGains() - это скорее вызов уровня обслуживания. Это приводит к тому, что Билет очень анемичен, но я предполагаю, что он имеет другое поведение? И если это не так, то, вероятно, запах...
Ответ 5
Этот вопрос на самом деле является примером обсуждения, которое содержится в книге "Чистый код" (стр. 96-97). Основной вопрос заключается в том, следует ли использовать процедурный подход или объектно-ориентированный подход. Надеюсь, что я не нарушаю, повторяя здесь пару частей, но вот что говорит Боб Мартин для руководства:
Процедурный код (код с использованием структур данных) позволяет легко добавлять новые функции без изменения существующих структур данных. С другой стороны, код OO упрощает добавление новых классов без изменения существующих функций.
Ответ также верен:
Процедурный код затрудняет добавление новых структур данных, поскольку все функции должны меняться. OO-код затрудняет добавление новых функций, потому что все классы должны меняться.
Мое понимание того, что DDD "Тип значения" будет тем, что Боб Мартин называет структурой данных.
Надеюсь, что это поможет и не просто добавляет к шуму:)