Предприятия 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 "Тип значения" будет тем, что Боб Мартин называет структурой данных.

Надеюсь, что это поможет и не просто добавляет к шуму:)