#oop #domain-driven-design #procedural-programming #anemic-domain-model
#ооп #дизайн, управляемый доменом #процедурное программирование #анемичная доменная модель
Вопрос:
Допустим, есть два класса: один — класс user, который содержит информацию о пользователе; другой — класс платежных транзакций. Сценарий прост: если возраст пользователя> 65 лет, создайте платежную транзакцию типа A; в противном случае создайте платежную транзакцию типа B.
Есть несколько способов сделать это:
- Создайте метод, не принадлежащий пользователю или транзакции, просто вызовите CreateTransaction . Логика изложена в этом методе:
func CreateTransaction(user, transaction) {
if user.GetAge() > 65:
transaction.CreateA()
else:
transaction.CreateB()
}
- Другой вариант — создать метод для пользовательского класса:
class User {
...
func CreateTransaction(transaction) {
if user.GetAge() > 65:
transaction.CreateA()
else:
transaction.CreateB()
}
}
Затем существует метод CreateTransactionController, который вызывает функцию, например:
func CreateTransactinController(user, transaction) {
user.CreateTransaction()
}
Мой вопрос в том, рассматривается ли вариант 1 как процедурное программирование, поскольку логика фактически не принадлежит ни одному объекту? (Или анемичный шаблон?)
Является ли разница между 1 и 2 просто другим местом для размещения логики?
Спасибо!
Ответ №1:
Поскольку вы отметили этот вопрос как DDD, я отвечу, как модель, управляемая доменом, будет реализовывать это.
Вопрос, на который нужно ответить, заключается в том, заключен ли a Transaction
в User
объект. Если он вложен, это означает, что вы всегда просматриваете запись пользователя для извлечения транзакций (и никогда не обращаетесь к транзакциям напрямую). Если транзакция сама по себе имеет жизненный цикл, может быть доступна напрямую, Управляет другими частями домена и т. Д., Она не может быть заключена в a User
и является полномасштабным агрегатом.
Включение transaction
внутри user
означало бы, что пользователь владеет операциями, связанными с транзакциями, поэтому вариант 2 будет правильным.
Если transaction
это другой агрегат, вы могли бы использовать a Domain Service
(например, ваш вариант 1), но это неверно, потому что вы обрабатываете два агрегата ( user
и transaction
) одновременно. Вам лучше включить эту функциональность в Transaction
агрегат.
Следующий вопрос, который нужно решить, — это как вы определяете тип транзакции. Один из способов:
- Запрос API отправит возраст пользователя как часть запроса
- Контроллер вызывает службу и передает возраст пользователя как целое число
- Служба вызывает фабричный метод, который принимает возраст как целое число, инициализирует и возвращает правильный тип транзакции.
- К моменту поступления запроса возраст пользователя в серверной части мог измениться, или запрос мог быть неверным. Вы решаете эту проблему, используя «корректирующую политику», которая выполняется после создания платежной транзакции позже. Если возраст пользователя соответствует выбранному типу транзакции, тогда все хорошо. Если это не так, транзакция отменяется.
Обычно так вы обрабатываете изменения, которые зависят от атрибутов из нескольких агрегатов. Вы продолжаете и изменяете состояние агрегата в системе, но позже проверяете связанные агрегированные данные и отменяете изменение, если что-то не соответствует.
Лучший способ — создать a Specification
, явной задачей которого является получение правильного типа оплаты на основе возраста пользователя. Спецификация охватывает вашу бизнес-логику (> 65), дает контекст возрастным требованиям и действует как центральное место, где вы будете управлять логикой.
Вы можете прочитать больше о спецификациях здесь: https://martinfowler.com/apsupp/spec.pdf