Ситуація
Раніше цього вечора я дав відповідь на запитання щодо StackOverflow.
Питання:
Редагування наявного об'єкта повинно здійснюватися в шарі сховища або в сервісі?
Наприклад, якщо у мене є Користувач, який має борг. Я хочу змінити його борг. Чи потрібно це робити в UserRepository або в сервісі, наприклад, BuyingService, отримуючи об’єкт, редагуючи його та зберігаючи його?
Моя відповідь:
Ви повинні залишити відповідальність за вимкнення об'єкта на той самий об’єкт і використовувати сховище для отримання цього об’єкта.
Приклад ситуації:
class User {
private int debt; // debt in cents
private string name;
// getters
public void makePayment(int cents){
debt -= cents;
}
}
class UserRepository {
public User GetUserByName(string name){
// Get appropriate user from database
}
}
Я отримав коментар:
Ділова логіка справді повинна бути в сервісі. Не в моделі.
Що говорить Інтернет?
Отож, мене це шукало, оскільки я ніколи не (свідомо) використовував сервісний рівень. Я почав читати схему Service Layer і шаблон Unit of Work, але поки що не можу сказати, що переконаний, що потрібно використовувати рівень обслуговування.
Візьмемо для прикладу цю статтю Мартіна Фаулера про антидіапазон анемічної моделі домену:
У просторі домену є об'єкти, багато яких названі іменами, і ці об'єкти пов'язані з багатими відносинами та структурою, які мають справжні доменні моделі. Улов виникає, коли ви дивитесь на поведінку, і ви розумієте, що навряд чи є поведінка на цих об'єктах, що робить їх трохи більше, ніж мішків геттерів та сетерів. Дійсно часто ці моделі оснащені правилами дизайну, які говорять про те, що ви не повинні розміщувати ніяких логік домену в об’єктах домену. Натомість є набір сервісних об’єктів, які фіксують всю логіку домену. Ці служби живуть на вершині доменної моделі та використовують модель домену для отримання даних.
(...) Логіка, яка повинна бути в доменному об'єкті, - це доменна логіка - перевірки, обчислення, бізнес-правила - як би ви не хотіли її називати.
Мені це здавалося саме такою, що склалася: я виступав за маніпулювання даними об'єкта, вводячи методи всередині цього класу, які роблять саме це. Однак я розумію, що це повинно бути задано в будь-якому випадку, і це, мабуть, має більше спільного з тим, як викликаються ці методи (використовуючи репозиторій).
У мене також було відчуття, що в цій статті (див. Нижче) рівень обслуговування розглядається як фасад, на якому делегати працюють за базовою моделлю, ніж власне трудомісткий шар.
Шар програми [його ім’я для Service Layer]: визначає завдання, які повинно виконувати програмне забезпечення, і спрямовує експресивні об'єкти домену для вирішення проблем. Завдання, за які відповідає цей рівень, є значущими для бізнесу або необхідними для взаємодії з прикладними шарами інших систем. Цей шар залишається тонким. Він не містить ділових правил чи знань, а лише координує завдання та делегує роботу спільним доменним об’єктам у наступному шарі вниз. У ньому немає стану, що відображає ділову ситуацію, але він може мати стан, який відображає хід виконання завдання для користувача або програми.
Який армований тут :
Сервісні інтерфейси. Служби відкривають сервісний інтерфейс, на який надсилаються всі вхідні повідомлення. Ви можете розглядати інтерфейс сервісу як фасад, який піддається діловій логіці, реалізованій у додатку (як правило, логіці на бізнес-рівні) потенційним споживачам.
І ось :
Службовий рівень повинен бути позбавлений будь-якої логіки додатків чи бізнесу та повинен зосереджуватися насамперед на кількох проблемах. Він повинен обробляти виклики бізнес-рівня, перекладати ваш Домен на загальну мову, яку можуть зрозуміти ваші клієнти, та обробляти комунікаційне середовище між сервером та запитуючим клієнтом.
Це серйозна контрастність з іншими ресурсами, які говорять про рівень обслуговування:
Сервісний рівень повинен складатися з класів з методами, які є одиницями роботи з діями, що належать до однієї транзакції.
Або друга відповідь на питання, яке я вже пов’язав:
У якийсь момент ваша програма захоче ділової логіки. Крім того, ви можете перевірити введення даних, щоб переконатися, що запитується щось недобре чи неефективно. Ця логіка належить до рівня обслуговування.
"Рішення"?
Дотримуючись вказівок у цій відповіді , я придумав наступний підхід, який використовує службовий шар:
class UserController : Controller {
private UserService _userService;
public UserController(UserService userService){
_userService = userService;
}
public ActionResult MakeHimPay(string username, int amount) {
_userService.MakeHimPay(username, amount);
return RedirectToAction("ShowUserOverview");
}
public ActionResult ShowUserOverview() {
return View();
}
}
class UserService {
private IUserRepository _userRepository;
public UserService(IUserRepository userRepository) {
_userRepository = userRepository;
}
public void MakeHimPay(username, amount) {
_userRepository.GetUserByName(username).makePayment(amount);
}
}
class UserRepository {
public User GetUserByName(string name){
// Get appropriate user from database
}
}
class User {
private int debt; // debt in cents
private string name;
// getters
public void makePayment(int cents){
debt -= cents;
}
}
Висновок
Все разом тут не багато чого змінилося: код з контролера перемістився на рівень обслуговування (що добре, тому в цьому підході є перевага). Однак це не схоже на те, що це мало нічого спільного з моєю оригінальною відповіддю.
Я усвідомлюю, що дизайнерські зразки - це керівні принципи, а не правила, встановлені в камені, які слід виконувати, коли це можливо. Проте я не знайшов остаточного пояснення рівня обслуговування та того, як його слід розглядати.
Це засіб просто витягнути логіку з контролера і замість цього помістити всередину служби?
Чи слід укладати договір між контролером та доменом?
Чи повинен бути шар між доменом і службовим шаром?
І, останнє, але не менш важливе: слідування за оригінальним коментарем
Ділова логіка справді повинна бути в сервісі. Не в моделі.
Це правильно?
- Як я можу ввести свою бізнес-логіку в сервіс замість моделі?