Организации DDD, использующие Услуги - PullRequest
11 голосов
/ 04 марта 2010

У меня есть приложение, которое я пытаюсь построить по крайней мере с номинальной моделью домена типа 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);
    }

}

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

Прочитав «Синюю книгу», но на самом деле ничего такого в этом стиле не построил, я ищу руководство.

EDIT

Спасибо всем за отзывы! Исходя из того, что я слышу, похоже, что моя модель должна выглядеть следующим образом. Это выглядит лучше?

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

Хорошо, я получил еще несколько отзывов о том, что, возможно, мои «расчетные» сервисы больше похожи на объекты стратегии, от которых вполне может зависеть моя сущность. Вот еще один пример с большей логикой в ​​сущности и использованием этих стратегических объектов. Мысли об этом? Есть ли какие-либо проблемы с созданием экземпляров этих помощников непосредственно в организации? Не думаю, что мне захочется их высмеивать в моих тестах, но ОТО, я не могу протестировать метод 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);
    }

}

Ответы [ 5 ]

6 голосов
/ 04 марта 2010

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

В вашей ситуации я бы вытянул FinancialCalculatorService и RateCalculatorService из вашей сущности и заставил бы методы каждой службы принять сущность Ticket в качестве параметра.

Возьмите секунду и прочитайте стр. 105 из Домен-управляемый дизайн Эрика Эванса

3 голосов
/ 04 марта 2010

Учитывая то, что мы видели в классах, я не думаю, что они действительно услуги в смысле синей книги , и я бы держал калькуляторы в Ticket.

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

Если у них действительно нет зависимостей от сущностей домена, подумайте о них как об «отдельных классах», а не «услугах» (еще раз, в терминологии синей книги). Это, безусловно, подходит для Ticket зависимости от объектов стратегии (FinancialCalculator и RateCalculator), которые сами не имеют экзотических зависимостей и сами не изменяют состояние сущностей домена.

Обновление для редактирования 2 . Я думаю, что одно из преимуществ создания отдельных классов калькуляторов заключается в том, что вы можете тестировать их независимо от Ticket. Строго говоря, билеты не несут ответственности за выполнение этих вычислений, они отвечают за правильные звонки тем сотрудничающим классам. Поэтому я был бы склонен сделать их пригодными для инъекций / имитации, как они были в вашем первоначальном примере.

2 голосов
/ 04 марта 2010

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

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

Мне кажется, что CalculateFinancialGains () - это скорее вызов уровня обслуживания. Это приводит к тому, что Ticket очень анемичен, но я предполагаю, что у него другое поведение? И если это не так, это, вероятно, запах ...

2 голосов
/ 04 марта 2010

Я бы сказал, что службы используют объекты, а не наоборот.

другая вещь, не уверенная в вашем домене, но вы уверены, что тикет - это сущность, а не объект стоимости?

1 голос
/ 04 марта 2010

Этот вопрос на самом деле является примером обсуждения в книге «Чистый код» (стр. 96-97). Основной вопрос заключается в том, использовать ли процедурный подход или объектно-ориентированный подход. Надеюсь, я не нарушаю, повторяя пару частей здесь, но вот что Боб Мартин заявляет для руководства:

Процедурный код (код с использованием структур данных) позволяет легко добавлять новые функции без изменения существующих структур данных. Код OO, с другой стороны, позволяет легко добавлять новые классы без изменения существующих функций.

Комплимент также верен:

Процедурный код затрудняет добавление новых структур данных, поскольку все функции должны меняться. ОО-код затрудняет добавление новых функций, потому что все классы должны измениться.

Насколько я понимаю, DDD "Тип значения" - это то, что Боб Мартин называет структурой данных.

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

...