DDD дублированная логика домена - PullRequest
0 голосов
/ 03 февраля 2020

Краткое описание проблемы

У нас есть 2 ограниченных контекста, которые представляют одни и те же объекты данных, но предлагают различные функциональные возможности для работы с этими объектами. Некоторые из более сложных вычислений начинают появляться в обоих контекстах одинаково. Копируем ли мы вставлять и писать модульные тесты? Мы сопоставляем и извлекаем общий объект политики расчета?

Основная цель - ремонтопригодность. Все остальное второстепенно.

Подробности

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

У нас есть 2 ограниченных контекста, которые обрабатывают инвестиции. Допустим, у них есть следующие классы:

Контекст - Инвестирование

  • Инвестиция
  • InvestmentInstruction

Контекст - Отчетность

  • Investment
  • InvestmentInstruction

Контекст инвестирования действительно инвестирует. Т.е. перемещает средства между банковскими счетами и инвестиционными продуктами и т. Д.

Контекст отчетности доступен только для чтения и предоставляет некоторые методы для расчета некоторых деталей инвестиций.

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

Вот пример псевдокода для тех, кто лучше понимает, глядя на код:

namespace Investing
{
    public class Investment
    {
        private List<InvestmentIsntruction> Instructions {get;private set;}

        public decimal GetRemainingContributionAllowed()
        {
            return SOME_PREDEFINED_LIMIT - Instructions.Sum(x => x.Amount);
        }
    }
}

namespace Reporting
{
    public class Investment
    {
        private List<InvestmentIsntruction> Instructions {get;private set;}

        public decimal GetRemainingContributionAllowed()
        {
            return SOME_PREDEFINED_LIMIT - Instructions.Sum(x => x.Amount);
        }
    }
}

Упрощенная формула для расчета предела. Предположим, что фактическая формула намного сложнее, как мы можем эффективно разделить эту логику c? Мы не слишком разборчивы в решении. Нам нужно решение, которое можно правильно мотивировать с точки зрения дизайна.

Решения до сих пор

1) Просто скопируйте, вставьте логи c и напишите модульные тесты, чтобы убедиться, что результаты всегда одинаковы. Смысл в том, что контексты разные. Возможно, в будущем формула отчетности может отличаться, например, и t ie между контекстами будет легко нарушить (настроить юнит-тест).

2) Извлечь общий объект калькулятора с помощью следующего интерфейса:

decimal GetRemainingAllowance(IEnumerable<IInvestmentIntsruction> instructions, decimal predefinedLimit);

Реализация интерфейса IInvestmentInstruction для классов каждого контекста. Мотивация здесь, конечно, будет единым местом для расчета. Что нам не нравится, так это интерфейс класса InvestmentInstruction. Если позже у нас будет больше перекрывающихся вычислений, могут быть реализованы десятки подобных интерфейсов. Например:

public class InvestmentInstruction : ILimitCalculationInstruction, IOtherCalcuationInstruction, IYetSomethingElse{}

Каждый интерфейс предоставляет что-то полезное для некоторых распространенных вычислений с перекрытием.

3) На данный момент это наше предпочтительное решение - Извлечение полностью независимого объекта калькулятора с собственным интерфейсом в виде в решении 2. Затем сопоставьте доменные объекты с общей моделью, когда они должны использоваться этим калькулятором. Например:

public class Calculator
{
    public decimal GetRemainingAllowance(IEnumerable<IInvestmentIntsruction> instructions, decimal predefinedLimit){...}
}

public class DedicatedCalcuatorInvestmentIsntructionModel : IInvestmentInstruction {}

namespace Investing
{
    public class Investment
    {
        private List<InvestmentIsntruction> Instructions {get;private set;}

        public decimal GetRemainingContributionAllowed(Calculator calculator)
        {
            var mappedInstructions = this.Instructions.Select(x => new DedicatedCalcuatorInvestmentIsntructionModel(){//Assign properties} );

            return calculator.GetRemainingAllowance(mappedInstructions, SOME_PREDEFINED_LIMIT);
        }
    }
}

namespace Reporting
{
    public class Investment
    {
        private List<InvestmentIsntruction> Instructions {get;private set;}

        public decimal GetRemainingContributionAllowed(Calculator calculator)
        {
             var mappedInstructions = this.Instructions.Select(x => new DedicatedCalcuatorInvestmentIsntructionModel(){//Assign properties} );

            return calculator.GetRemainingAllowance(mappedInstructions, SOME_PREDEFINED_LIMIT);
        }
    }
}
...

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

Вопрос

Какое из наших решений вы бы порекомендовали, если таковые имеются? Какие еще есть способы решения этой проблемы?

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

1 Ответ

2 голосов
/ 03 февраля 2020

Другой вариант - денормализовать требуемое значение GetRemainingContributionAllowed и сохранить его. Сохранение этого значения может быть выполнено всякий раз, когда в Investment вносятся соответствующие изменения. Альтернативой может быть создание отчета в некотором роде процесса , где на первом этапе домен должен вычислить значение и затем сохранить его. В этом случае бит сообщения будет только читать.

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

...