Репозиторії DDD у службі додатків чи доменів


29

Я сьогодні вивчаю DDD, і у мене виникають питання щодо управління репозиторіями з DDD.

Насправді я зустрів дві можливості:

Перший

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

Таким чином, в одному з методів обслуговування додатків ми називаємо метод обслуговування домену (перевірка правил бізнесу), і якщо умова хороша, сховище викликається спеціальним методом для збереження / вилучення сутності з бази даних.

Простий спосіб зробити це:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repository = repository
  }

  postAction(data){
    if(this.domainService.validateRules(data)){
      this.repository.persist(new Entity(data.name, data.surname))
    }
    // ...
  }

}

Другий

Друга можливість - замість цього ввести сховище всередині domainService і використовувати сховище лише через службу домену:

class ApplicationService{

  constructor(domainService){
    this.domainService = domainService
  }

  postAction(data){
    if(this.domainService.persist(data)){
      console.log('all is good')
    }
    // ...
  }

}

class DomainService{

  constructor(repository){
    this.repository = repository
  }

  persist(data){
    if(this.validateRules(data)){
      this.repository.save(new Entity(data.name))
    }
  }

  validateRules(data){
    // returns a rule matching
  }

}

Відтепер я не в змозі розрізнити, хто з них найкращий (якщо є один найкращий) або що вони мають на увазі обидва у своєму контексті.

Чи можете ви надати мені приклад, де один міг бути кращим за інший і чому?



"ввести сховище та модель домену в службу додатків." Що ви маєте на увазі, вводячи кудись "доменну модель"? AFAICT з точки зору моделі домену DDD означає весь набір понять із домену та взаємодії між ними, які мають значення для програми. Це абстрактна річ, це не якийсь об’єкт пам'яті. Ви не можете ввести його.
Олексій

Відповіді:


31

Коротка відповідь - ви можете використовувати репозиторії з служби додатків або служби домену - але важливо врахувати, чому і як ви це робите.

Призначення служби домену

Служби доменів повинні інкапсулювати поняття / логіку домену - як такий, метод обслуговування домену:

domainService.persist(data)

не належить до доменного сервісу, так persistяк не є частиною всюдисущої мови і операція наполегливості не є частиною логіки доменного бізнесу.

Як правило, сервіси домену корисні, коли у вас є бізнес-правила / логіка, які вимагають узгодження або роботи з більш ніж одним агрегатом. Якщо логіка включає лише один сукупність, вона повинна бути в методі для сутності цього агрегату.

Сховища в службах прикладних програм

Тож у цьому сенсі у вашому прикладі я віддаю перевагу вашому першому варіанту - але навіть є місце для вдосконалення, оскільки ваша доменна служба приймає необроблені дані з api - чому доменна служба повинна знати про структуру data?. Крім того, дані, як видається, пов'язані лише з одним агрегатом, тому для використання доменної служби для цього є обмежене значення - як правило, я ставлю валідацію всередині конструктора сутності. напр

postAction(data){

  Entity entity = new Entity(data.name, data.surname);

  this.repository.persist(entity);

  // ...
}

і киньте виняток, якщо він недійсний. Залежно від вашої програми застосувань, може бути простим механізм збору винятків та відображення його у відповідній відповіді для типу api - наприклад, для REST-api повернути код стану 400.

Сховища в доменних службах

Незважаючи на вищезазначене, іноді корисно вводити та використовувати репозиторій у службі домену, але лише у тому випадку, якщо ваші сховища реалізовані таким чином, що вони приймають та повертають лише сукупні корені, а також там, де ви абстрагуєте логіку, яка включає декілька агрегатів. напр

postAction(data){

  this.domainService.doSomeBusinessProcess(data.name, data.surname, data.otherAggregateId);

  // ...
}

реалізація послуги домену виглядатиме так:

doSomeBusinessProcess(name, surname, otherAggregateId) {

  OtherEntity otherEntity = this.otherEntityRepository.get(otherAggregateId);

  Entity entity = this.entityFactory.create(name, surname);

  int calculationResult = this.someCalculationMethod(entity, otherEntity);

  entity.applyCalculationResultWithBusinessMeaningfulName(calculationResult);

  this.entityRepository.add(entity);

}

Висновок

Ключовим тут є те, що служба домену інкапсулює процес, який є частиною всюдисущої мови. Для того, щоб виконати свою роль, їй потрібно використовувати сховища - і це цілком чудово.

Але додавання доменної служби, яка завершує сховище методом, який називається, persistдодає мало значення.

Виходячи з цього, якщо ваша служба додатків виражає випадок використання, який вимагає роботи лише з одним агрегатом, немає проблем із використанням сховища безпосередньо з служби додатків.


Гаразд, так що, якщо я веду правила ведення бізнесу (допускаючи правила шаблону специфікацій), якщо це стосується лише однієї сутності, я повинен, окрім валідації в цій сутності? Здається дивним вводити такі бізнес-правила, як керування хорошим форматом пошти користувача всередині організації. Чи не так? Щодо глобальної відповіді, дякую. З'ясувалося, що не існує "правила за замовчуванням, яке застосовується", і воно дійсно залежить від наших користувальницьких випадків. У мене є якась робота, щоб добре розрізнити всю цю роботу
mfrachet

2
Для уточнення, правила, які належать до суб'єкта господарювання, - це лише ті правила, на які покладено відповідальність цього суб'єкта. Я погоджуюся, що керування хорошим форматом електронної пошти користувача не означає, що він належить до юридичної особи Користувача. Особисто мені подобається розміщувати такі правила перевірки у об’єкті цінності, який представляє електронну адресу. Користувач матиме властивість типу EmailAddress, а конструктор EmailAddress приймає рядок і видає виняток, якщо рядок не відповідає потрібному формату. Потім ви можете повторно використовувати EmailOdress ValueObject для інших об'єктів, яким потрібно зберігати адресу електронної пошти.
Кріс Саймон

Гаразд, я розумію, чому зараз використовувати Value Value Object. Але це означає, що об'єкт значення має властивість, яке є бізнес-правилом, яке керує форматом?
mfrachet

1
Ціннісні об'єкти повинні бути незмінними. Як правило, це означає, що ви ініціалізуєте та підтверджуєте в конструкторі, а для будь-яких властивостей використовуєте загальнодоступний шаблон get / private set. Але ви можете використовувати мовні конструкції для визначення рівності, процесу ToString тощо. Наприклад, kacper.gunia.me/ddd-building-blocks-in-php-value-object або github.com/spring-projects/spring-gemfire-examples/ крапля / майстер /…
Кріс Саймон

Дякую @ChrisSimon, нарешті і відповідь на реальну ситуацію DDD, що включає код, а не просто теорію. Я провів 5 днів на лову SO та Web для функціонального прикладу створення та збереження агрегату, і це найяскравіше пояснення, яке я знайшов.
e_i_pi

2

Проблема з прийнятою відповіддю:

Модель домену не може залежати від сховища, і сервіс домену є частиною доменної моделі -> сервіс домену не повинен залежати від сховища.

Натомість ви повинні зібрати всі ваші сутності, які потрібні для виконання бізнес-логіки вже в службі прикладних програм, а потім просто надати своїм моделям об'єкти, що створюються.

На основі вашого прикладу це може виглядати приблизно так:

class ApplicationService{

  constructor(domainService, repository){
    this.domainService = domainService
    this.repositoryA = repositoryA
    this.repositoryB = repositoryB
    this.repositoryC = repositoryC
  }

  // any parsing and/or pre-business validation already happened in controller or whoever is a caller
  executeUserStory(data){
    const entityA = this.repositoryA.get(data.criterionForEntityA)
    const entityB = this.repositoryB.get(data.criterionForEntityB)

    if(this.domainService.validateSomeBusinessRules(entityA, entityB)){
      this.repositoryC.persist(new EntityC(entityA.name, entityB.surname))
    }
    // ...
  }
}

Отже, правило: Модель домену не залежить від зовнішніх шарів

Додаток проти сервісу домену З цієї статті :

  • Доменні служби дуже деталізовані, оскільки як програми додатків - це фасад, призначений для надання API.

  • Доменні сервіси містять логіку домену, яку природно не можна розмістити в об'єкті чи об'єкті значення, тоді як служби додатків оркеструють виконання логіки домену та самі не реалізують жодну логіку домену.

  • Методи сервісу домену можуть мати інші елементи домену, як операнди та зворотні значення, тоді як служби прикладних програм працюють на тривіальних операндах, таких як значення ідентичності та примітивні структури даних.

  • Прикладні служби заявляють про залежність від інфраструктурних служб, необхідних для виконання логіки домену.


1

Жоден з ваших моделей не є добрим, якщо ваші послуги та об'єкти не містять певного цілісного відповідальності.

Перш за все скажіть, що є вашим доменним об’єктом, і поговоріть про те, що він може робити в межах мови домену. Якщо це може бути дійсним чи недійсним, чому б не мати це як властивість самого об’єкта домену?

Якщо, наприклад, валідність об'єктів має сенс лише з точки зору іншого об'єкта, то, можливо, ви несете відповідальність «правило перевірки X для об’єктів домену», яке може бути капсульованим у набір послуг.

Чи вимагає перевірка об'єкта необхідна для зберігання його у правилах вашого бізнесу? Напевно, ні. Відповідальність за "зберігання об'єктів" зазвичай складається в окремому об'єкті сховища.

Тепер у вас є операція, яку ви хочете виконати, яка охоплює коло обов'язків, створіть об'єкт, підтвердіть його і, якщо це дійсно, зберігайте його.

Чи властива ця операція об'єкту домену? Потім зробіть його частиною цього об’єкта, тобтоExamQuestion.Answer(string answer)

Чи відповідає це якійсь іншій частині вашого домену? покладіть його тудиBasket.Purchase(Order order)

Ви хочете скористатися послугами ADM REST? Добре тоді.

Controller.Post(json) 
{ 
    parse(json); 
    verify(parsedStruct); 
    save(parsedStruct); 
    return 400;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.