Наскільки точна "Бізнес-логіка повинна бути в сервісі, а не в моделі"?


397

Ситуація

Раніше цього вечора я дав відповідь на запитання щодо 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;
    }
}

Висновок

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

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

  • Це засіб просто витягнути логіку з контролера і замість цього помістити всередину служби?

  • Чи слід укладати договір між контролером та доменом?

  • Чи повинен бути шар між доменом і службовим шаром?

І, останнє, але не менш важливе: слідування за оригінальним коментарем

Ділова логіка справді повинна бути в сервісі. Не в моделі.

  • Це правильно?

    • Як я можу ввести свою бізнес-логіку в сервіс замість моделі?

6
Я розглядаю службовий рівень як місце, куди слід поставити неминучу частину сценарію транзакцій, що діє на сукупні корені. Якщо мій рівень обслуговування стає занадто складним, це сигналізує мені про те, що я рухаюся в напрямку Anemic Model, і моя модель домену потребує уваги та огляду. Я також намагаюся вкласти логіку в SL, яка за своєю природою не буде дублюватися.
Павло Воронін

Я подумав, що послуги є частиною шару Модель. Я помиляюся, думаючи про це?
Флоріан Маргайн

Я використовую правило: не залежати від того, що вам не потрібно; інакше на мене можуть вплинути (як правило, негативно) зміни, які мені не потрібні. Як наслідок, я закінчую безліч чітко визначених ролей. Мої об’єкти даних містять поведінку до тих пір, поки всі клієнти нею користуються. В іншому випадку я переміщую поведінку на класи, що реалізують необхідну роль.
белучин

1
Логіка управління потоком програми належить до контролера. Логіка доступу до даних належить до сховища. Логіка перевірки належить до рівня обслуговування. Службовий рівень - це додатковий рівень у програмі ASP.NET MVC, який опосередковує зв’язок між контролером та рівнем сховища. Сервісний рівень містить логіку перевірки бізнесу. сховище. asp.net/mvc/overview/older-versions-1/models-data/…
Kbdavis07

3
IMHO, правильна модель домену в стилі OOP дійсно повинна уникати "ділових послуг" і зберігати ділову логіку в моделі. Але практика показує, що настільки заманливо створити величезний бізнес-шар із чітко названими методами, а також здійснити транзакцію (або одиницю роботи) навколо всього методу. Набагато простіше обробити кілька модельних класів одним методом службового обслуговування, ніж планувати відносини між цими модельними класами, оскільки тоді у вас виникнуть складні запитання, щоб відповісти: де є сукупний корінь? Де слід запустити / здійснити транзакцію з базою даних? І т. Д.
JustAMartin

Відповіді:


368

Щоб визначити, що таке обов'язки служби , спочатку потрібно визначити, що таке служба .

Служба - це не канонічний або загальний програмний термін. Насправді суфікс Serviceна ім'я класу дуже схожий на злісний менеджер : він майже нічого не говорить про те, що об’єкт насправді робить .

Насправді, сервіс, який повинен зробити, дуже специфічний для архітектури:

  1. У традиційній багатошаровій архітектурі сервіс буквально є синонімом шару бізнес-логіки . Це шар між інтерфейсом користувача та даними. Тому всі правила ведення бізнесу переходять до послуг. Рівень даних повинен розуміти лише основні операції CRUD, а рівень користувальницького інтерфейсу повинен мати справу лише з відображенням DTO презентації до та з бізнес-об'єктів.

  2. У розподіленій архітектурі в стилі RPC (SOAP, UDDI, BPEL тощо) послуга є логічною версією фізичної кінцевої точки . Це, по суті, сукупність операцій, які підтримуючий бажає надати як публічний API. Різні посібники з найкращих практик пояснюють, що сервісна операція насправді повинна бути операцією на рівні бізнесу, а не CRUD, і я, як правило, згоден.

    Однак, оскільки маршрутизації все через фактичне дистанційного обслуговування може серйозно пошкодити продуктивності, це звичайно краще НЕ мати ці послуги на самому справі реалізації бізнес - логіки себе; натомість вони повинні загорнути "внутрішній" набір бізнес-об'єктів. В одній службі може бути один або кілька бізнес-об'єктів.

  3. У архітектурі MVP / MVC / MVVM / MV * послуги взагалі не існують. Або якщо вони є, термін використовується для позначення будь-якого загального об'єкта, який може бути введений в контролер або переглянути модель. Логіка бізнесу у вашій моделі . Якщо ви хочете створити "сервісні об'єкти", щоб упорядкувати складні операції, це розглядається як деталь реалізації. Дуже багато людей, на жаль, реалізують подібний MVC, але це вважається антидіаграмою ( Anemic Domain Model ), оскільки сама модель нічого не робить, це лише купа властивостей для інтерфейсу користувача.

    Деякі люди помилково думають, що використання методу контролера на 100 рядків і передавання його в сервіс якось сприяє кращій архітектурі. Це насправді не так; все, що вона робить, це додати ще один, можливо, непотрібний шар непрямості. Практично кажучи, контролер все ще виконує роботу, він просто робить це через погано названий об’єкт "помічник". Я настійно рекомендую презентацію Wicked Domain Models Jimmy Bogard для наочного прикладу того, як перетворити анемічну модель домену в корисну. Він передбачає ретельне вивчення моделей, на які ви виставляєте, і які операції дійсно дійсні в бізнес- контексті.

    Наприклад, якщо у вашій базі даних є замовлення, а у вас стовпець "Загальна кількість", вашій програмі, ймовірно, не слід дозволяти фактично змінювати це поле на довільне значення, оскільки (а) це історія та (б) це має бути визначається тим, що в порядку, а також, можливо, деякими іншими часовими даними / правилами. Створення служби для управління Замовленнями не обов'язково вирішує цю проблему, оскільки код користувача все ще може захопити фактичний об'єкт Замовлення та змінити суму на ньому. Замість цього, порядок сам повинен нести відповідальність за забезпечення того, що він може бути змінений тільки в безпечних і послідовних способах.

  4. У DDD послуги призначені спеціально для ситуації, коли у вас є операція, яка належним чином не належить до жодного сукупного кореня . Тут ви повинні бути обережними, адже часто потреба в сервісі може означати, що ви не використовували правильних коренів. Але якщо вважати, що ви це зробили, сервіс використовується для координації операцій у декількох коренях або іноді для обробки проблем, які взагалі не включають модель домену (наприклад, можливо, введення інформації в базу даних BI / OLAP).

    Одним із важливих аспектів служби DDD є те, що дозволено використовувати сценарії транзакцій . Працюючи над великими програмами, ви, ймовірно, врешті-решт зіткнетеся з випадками, коли виконати щось із T-SQL або PL / SQL процедурою просто легше, ніж суєта з доменною моделлю. Це нормально, і воно належить до Сервісу.

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

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

    За необхідності, послуги - це фактична пропозиція від кінця до кінця в SOA. Значить, сервіс - це не стільки специфічний компонент, скільки цілий стек , а вся ваша програма (або весь ваш бізнес) - це набір цих служб, які працюють поруч без перетину, за винятком рівнів обміну повідомленнями та інтерфейсу користувача. Кожна служба має власні дані, власні правила ведення бізнесу та власний інтерфейс користувача. Їм не потрібно організовувати один одного, оскільки вони повинні бути узгодженими з бізнесом - і, як і сам бізнес, кожна служба має свій набір обов'язків і діє більш-менш незалежно від інших.

    Отже, за визначенням SOA, кожен фрагмент бізнес-логіки в будь-якому місці міститься в сервісі, але знову ж таки, як і вся система . Служби в SOA можуть мати компоненти , і вони можуть мати кінцеві точки , але називати послугу будь-яким кодом досить небезпечно, оскільки це суперечить тому, що має означати початковий "S".

    Оскільки SOA, як правило, дуже зацікавлений у обміні повідомленнями, операції, які, можливо, ви були упаковані в службу раніше, як правило, інкапсульовані в обробники , але кратність відрізняється. Кожен обробник обробляє один тип повідомлення, одну операцію. Це суворе тлумачення Принципу єдиної відповідальності , але забезпечує велику ремонтопридатність, оскільки кожна можлива операція відбувається у своєму класі. Тож вам не потрібна централізована бізнес-логіка, оскільки команди представляють бізнес-операції, а не технічні.

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

Правильної чи неправильної відповіді немає, лише те, що стосується вашої ситуації.


12
Дякую за дуже детальну відповідь, ви прояснили все, що я можу придумати. Хоча інші відповіді хороші і до відмінної якості, я вважаю, що ця відповідь перевершує їх, тому я прийму цю. Я додам його сюди для інших відповідей: вишукана якість та інформація, але, на жаль, я зможу лише дати вам підсумки.
Jeroen Vannevel

2
Я не згоден з тим, що в традиційній багатошаровій архітектурі сервіс є синонімом до рівня бізнес-логіки.
CodeART

1
@CodeART: це в трирівневій архітектурі. Я вже бачив 4 ешелону архітектури , де є «прикладного рівня» між шарами уявлення і бізнес, який іноді також називають шар «сервіс», але , чесно кажучи, єдині місця , де я коли - небудь бачив це реалізовано успішно величезні розтягування нескінченно налаштовані продукти для вашого бізнесу, як SAP або Oracle, і я не думав, що це дійсно варто згадувати тут. Я можу додати уточнення, якщо вам подобається.
Aaronaught

1
Але якщо ми візьмемо 100+ лінійний контролер (наприклад, що контролер приймає повідомлення - тоді дезаріалізувати об'єкт JSON, здійснити перевірку, застосувати ділові правила, зберегти в db, повернути об'єкт результату) і перенести певну логіку до одного з тих, що називається метод обслуговування не робить ' t що допоможе нам окремо одиницю протестувати кожну її частину безболісно?
artjom

2
@Aaronaught Я хотів уточнити одне, чи є у нас об’єкти домену, відображені на db через ORM, і в них немає ділової логіки, це анемічна модель домену чи ні?
artjom

40

Що стосується вашої назви , я не думаю, що питання має сенс. Модель MVC складається з даних та логіки бізнесу. Сказати, що логіка повинна бути в Сервісі, а не в моделі, як сказати: "Пасажир повинен сидіти на сидінні, а не в машині".

Потім знову термін «Модель» - це перевантажений термін. Можливо, ви не мали на увазі модель MVC, але ви мали на увазі модель в сенсі об’єкта передачі даних (DTO). AKA Entity. Про це говорить Мартін Фаулер.

Як я це бачу, Мартін Фаулер говорить про речі в ідеальному світі. У реальному світі сплячого та JPA (на землі Яви) DTO є надзвичайно протікаючою абстракцією. Я хотів би вкласти свою ділову логіку в свою сутність. Це зробило б речі чистішими. Проблема полягає в тому, що ці об'єкти можуть існувати в керованому / кешованому стані, що дуже важко зрозуміти і постійно заважає вашим зусиллям. Підводячи підсумок моєї думки: Мартін Фаулер рекомендує правильний шлях, але ОРМ заважає вам це робити.

Я думаю, що у Боба Мартіна є більш реалістична пропозиція, і він дає це у цьому відео, яке не є безкоштовним . Він говорить про те, щоб утримувати ваші DTO без логіки. Вони просто зберігають дані та переносять їх на інший шар, орієнтований набагато більш об'єктно і не використовує DTO безпосередньо. Це дозволяє уникнути течі, що просочуються, не вкусити вас. Шар з DTO і самими DTO не є ОО. Але як тільки ви вийдете з цього шару, ви станете настільки ж OO, як захисник Мартіна Фаулера.

Користь цього поділу полягає в тому, що він відтягує стійкий шар. Ви можете перейти з JPA до JDBC (або навпаки), і жодна бізнес-логіка не повинна буде змінюватися. Це просто залежить від DTO, це не байдуже, як ці DTO заповнюються.

Щоб трохи змінити теми, потрібно врахувати той факт, що бази даних SQL не орієнтовані на об'єкти. Але в ORM зазвичай є сутність - яка є об'єктом - за таблицею. Тож з самого початку ви вже програли битву. На мій досвід, ви ніколи не можете представляти Суб'єкт саме так, як вам потрібно, в об'єктно-орієнтованому вигляді.

Що стосується « на службі», Боб Мартін був би проти того , щоб клас з ім'ям FooBarService. Це не об'єктно орієнтоване. Що робить послуга? Все, що стосується FooBars. Це також може бути маркованим FooBarUtils. Я думаю, що він виступав би за рівень обслуговування (кращою назвою було б рівень бізнес-логіки), але кожен клас у цьому шарі мав би значущу назву.


2
Погодьтеся з вашою точкою щодо ORM; вони розповсюджують брехню, що ви відображаєте свою сутність безпосередньо на db, коли насправді сутність може зберігатися в декількох таблицях.
Енді

@Daniel Kaplan Ви знаєте, яке оновлене посилання стосується відео про Боб Мартіна?
Брайан Мореарті

25

Я зараз працюю над проектом «Грінфілд», і нам довелося прийняти кілька архітектурних рішень лише вчора. Як не дивно, мені довелося переглянути кілька розділів «Шаблони архітектури прикладних програм підприємства».

Ось що ми придумали:

  • Рівень даних. База даних запитів та оновлень. Шар піддається впливу ін'єкційних сховищ.
  • Доменний шар. Тут живе логіка бізнесу. Цей шар використовує сховища для ін'єкцій і відповідає за більшість бізнес-логіки. Це суть програми, яку ми ретельно перевіримо.
  • Сервісний рівень. Цей шар розмовляє з доменним шаром і обслуговує запити клієнта. У нашому випадку сервісний рівень досить простий - він ретранслює запити до доменного рівня, обробляє безпеку та кілька інших наскрізних проблем. Це не сильно відрізняється від контролера в додатку MVC - контролери невеликі і прості.
  • Клієнтський шар. Розмовляє зі службовим рівнем через SOAP.

Ми закінчуємо наступним:

Клієнт -> Сервіс -> Домен -> Дані

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

Сказавши все це, я думаю, що це досить близько до того, що мав на увазі Мартін Фаулер

Ці служби живуть на вершині доменної моделі та використовують модель домену для отримання даних.

На схемі нижче це досить добре ілюструє:

http://martinfowler.com/eaaCatalog/serviceLayer.html

http://martinfowler.com/eaaCatalog/ServiceLayerSketch.gif


2
Ви вирішили на SOAP тільки вчора? Це вимога чи ви просто не мали кращої ідеї?
JensG

1
REST не скоротить його для наших вимог. Мило або відпочинок, це не має ніякого значення для відповіді. З мого розуміння, сервіс - це шлюз до логіки домену.
CodeART

Абсолютно згідний з шлюзом. SOAP - це (стандартизоване) програмне забезпечення, тому мені довелося запитати. І так, жодного впливу на питання / відповідь також немає.
JensG

6
Зроби собі послугу і вбий рівень обслуговування. Ваш користувач повинен використовувати ваш домен безпосередньо. Я бачив це раніше і ваш домен незмінно стає купою анемічних dtos, а не багатих моделей.
Енді

Ці слайди висвітлюють пояснення щодо сервісного шару та цієї графіки вгорі досить акуратно: slideshare.net/ShwetaGhate2/…
Marc Juchli

9

Це одна з тих речей, яка дійсно залежить від випадку використання. Загальний пункт сервісного рівня полягає в об'єднанні бізнес-логіки разом. Це означає, що декілька контролерів можуть викликати один і той же UserService.MakeHimPay (), не піклуючись про те, як здійснюється платіж. Те, що відбувається в сервісі, може бути таким же простим, як і зміна властивості об'єкта, або це може бути складна логіка роботи з іншими службами (тобто, виклик стороннім службам, виклик логіки перевірки або навіть просто щось збереження в базі даних. )

Це не означає, що вам потрібно позбавити ВСЮ логіку з об’єктів домену. Іноді просто має сенс мати метод на об’єкті домену робити деякі розрахунки на собі. У вашому останньому прикладі послуга є надлишковим шаром над об'єктом сховища / домену. Він забезпечує хороший буфер проти змін вимог, але насправді це не потрібно. Якщо ви вважаєте, що вам потрібна послуга, спробуйте виконати просту логіку "змінити властивість X на об'єкт Y" замість об'єкта домену. Логіка класів домену, як правило, потрапляє в "обчислити це значення з полів", а не піддавати всі поля через геттери / сеттери.


2
Ваша позиція на користь сервісного рівня з бізнес-логікою має багато сенсу, але це все ще залишає деякі питання. У своєму дописі я цитував кілька поважних джерел, які розповідають про рівень обслуговування як про фасадну порожнечу будь-якої логіки бізнесу. Це прямо суперечить вашій відповіді, чи могли б ви прояснити цю різницю?
Jeroen Vannevel

5
Я вважаю, що це дійсно залежить від ТИПУ ділової логіки та інших факторів, таких як мова, яка використовується. Деяка бізнес-логіка не дуже добре вписується в об'єкти домену. Одним із прикладів є фільтрування / сортування результатів після відведення їх назад із бази даних. Це ділова логіка, але це не має сенсу для об’єкта домену. Я вважаю, що сервіси найкраще використовувати для простої логіки або перетворення результатів, а логіка в домені є найбільш корисною, коли вона стосується збереження даних або обчислення даних від об'єкта.
firelore

8

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

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            if(value < 0) throw new ArgumentOutOfRangeException("value");
            this.someProperty = value;
        }
    }
}

Отже, у сеттера є основна логіка перевірки (не може бути негативною). Проблема полягає в тому, що ви не можете реально повторно використовувати цю логіку. Десь є екран або ViewModel або контролер, який повинен зробити перевірку, перш ніж він фактично здійснить зміну об’єкта домену, оскільки він повинен повідомити користувача перед тим, як або коли він натисне кнопку "Зберегти", що вони не можуть цього зробити, і чому . Тестування на виняток, коли ви викликаєте сеттера, - це некрасивий злом, оскільки ви дійсно повинні були виконати всю перевірку, перш ніж ви навіть розпочали транзакцію.

Ось чому люди рухають логіку перевірки якоюсь послугою, наприклад MyEntityValidator. Тоді сутність та логіка виклику можуть як отримати посилання на службу перевірки, так і повторно використовувати її.

Якщо ви цього не зробите, і ви все ще хочете повторно використовувати логіку перевірки, ви в остаточному підсумку вводите її в статичні методи класу сутності:

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            string message;
            if(!TryValidateSomeProperty(value, out message)) 
            {
                throw new ArgumentOutOfRangeException("value", message);
            }
            this.someProperty = value;
        }
    }

    public static bool TryValidateSomeProperty(int value, out string message)
    {
        if(value < 0)
        {
            message = "Some Property cannot be negative.";
            return false;
        }
        message = string.Empty;
        return true;
    }
}

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


1
То що, на вашу думку, найкраще рішення для перевірки?
Flashrunner

3
@Flashrunner - моя логіка перевірки безумовно є шаром бізнес-логіки, але також дублюється в шарах сутності та бази даних у деяких випадках. Бізнес-шар добре впорається з цим, повідомляючи користувача тощо, але інші шари просто викидають помилки / винятки і не дозволяють програмісту (я) робити помилки, які пошкоджують дані.
Скотт Уїтлок

6

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

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

Розглянемо найосновнішу об'єктно-орієнтовану концепцію: Об'єкт інкапсулює дані та операції. Наприклад, закриття рахунку - це операція, яку об’єкт облікового запису повинен виконувати на собі; отже, маючи сервісний рівень виконувати, ця операція не є об'єктно-орієнтованим рішенням. Він є процедурним, і саме про це звертається Мартін Фаулер, коли він говорить про анемічну модель домену.

Якщо у вас є сервісний рівень закрити обліковий запис, а не закривати сам об’єкт облікового запису, у вас немає реального об’єкта акаунта. "Об'єкт" вашого облікового запису - це лише структура даних. У кінцевому підсумку, як пропонує Мартін Фаулер, це купа мішків з геттерами та сетерами.


1
Відредаговано. Я насправді знайшов це досить корисним поясненням, і не думаю, що воно заслуговує на голоси.
BadHorsie

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

4

Як би ви реалізували свою бізнес-логіку в рівні обслуговування? Коли ви здійснюєте платіж від користувача, ви створюєте платіж, а не просто вирахування вартості з властивості.

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


2

Версія tl; dr:
Мій досвід та думки говорять про те, що будь-які об'єкти, що мають бізнес-логіку, повинні бути частиною доменної моделі. Модель даних, швидше за все, не повинна мати ніякої логіки. Служби, швидше за все, повинні поєднати їх разом і мати справу з наскрізними проблемами (бази даних, ведення журналів тощо). Однак прийнята відповідь є найбільш практичною.

Більш довгий варіант, на який натякали інші, полягає в тому, що словосполучення є «еквівокацією». Пост перемикається між моделлю даних та моделлю домену так, ніби вони однакові, що є дуже поширеною помилкою. Також може бути незначне еквівокація на слово "послуга".

На практиці ви не повинні мати сервіс, який вносить зміни до будь-яких об'єктів домену; Причиною цього є те, що ваша служба, ймовірно, має певний метод для кожної власності на вашому об'єкті, щоб змінити значення цього властивості. Це проблема, оскільки тоді, якщо у вас є інтерфейс для вашого об'єкта (або навіть якщо ні), служба більше не відповідає принципу відкритого закриття; натомість щоразу, коли ви додаєте більше даних до своєї моделі (незалежно від домену та даних), вам доводиться додавати більше функцій у вашу службу. Існують певні шляхи навколо цього, але це найпоширеніша причина, по якій я бачив, що програми "підприємств" не спрацьовують, особливо коли ці організації думають, що "підприємство" означає "мати інтерфейс для кожного об'єкта в системі". Чи можете ви уявити собі додавання нових методів до інтерфейсу, то до двох-трьох різних реалізацій (одна в додатку, макетна реалізація та налагодження однієї, вбудованої пам'яті?), лише для однієї властивості вашої моделі? Мені це здається жахливою ідеєю.

Тут є більш довга проблема, в яку я не буду вступати, але суть у цьому: Hardcore об'єктно-орієнтоване програмування говорить про те, що ніхто за межами відповідного об'єкта не повинен мати змогу змінити значення властивості в межах об'єкта, а також навіть " див. "значення властивості всередині об'єкта. Це можна усунути, зробивши дані лише для читання. Ви все ще можете зіткнутися з проблемами, наприклад, коли багато людей використовують дані навіть як лише для читання, і вам доведеться змінити тип цих даних. Цілком можливо, що всі споживачі повинні будуть змінитися, щоб їх задовольнити. Ось чому, коли ви робите API, який споживає будь-хто і кожен, вам рекомендується не мати публічних або навіть захищених властивостей / даних; це, зрештою, причина створення OOP, зрештою.

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

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


1

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

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

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


0

У MVC Model визначається як бізнес-логіка. Стверджувати, що це має бути десь інше, невірно, якщо він не використовує MVC. Я розглядаю службові шари як подібні до модульної системи. Це дозволяє поєднати набір відповідних функціональних можливостей у приємний пакет. Внутрішній рівень цього сервісного шару мав би модель, яка виконує ту ж роботу, що і ваша.

Модель складається з даних програми, правил бізнесу, логіки та функцій. http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller


0

Концепцію рівня обслуговування можна розглядати з точки зору DDD. Aaronaught згадував про це у своїй відповіді, я лише трохи докладно про це.

Загальним підходом є наявність контролера, який є специфічним для типу клієнта. Скажімо, це може бути веб-браузер, це може бути якась інша програма, це може бути функціональний тест. Формати запиту та відповіді можуть відрізнятися. Тому я використовую службу додатків як інструмент для використання шестикутної архітектури . Я ввожу туди класи інфраструктури, характерні для конкретного запиту. Наприклад, ось як міг виглядати мій контролер, що обслуговує запити веб-браузера:

class WebBroserController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new PayPalClient(),
                new OrderRepository()
            )
        ;

        $response = new HtmlView($responseData);
    }
}

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

class FunctionalTestController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new FakePayPalClient(),
                new OrderRepository(),
                $data
            )
        ;

        return new JsonData($responseData);
    }
}

Тож служба додатків - це середовище, яке я створив для ведення бізнес-логіки. Саме там називаються класи моделей - по суті, щодо інфраструктурної реалізації.

Отже, відповідаючи на ваші запитання з цього погляду:

Це засіб просто витягнути логіку з контролера і замість цього помістити всередину служби?

Немає.

Чи слід укладати договір між контролером та доменом?

Що ж, так можна назвати.

Чи повинен бути шар між доменом і службовим шаром?

Ні.


Існує радикально інший підхід, який повністю заперечує використання будь-якого виду послуг. Наприклад, Девід Уест у своїй книзі « Об’єктне мислення» стверджує, що для виконання своєї роботи будь-який об’єкт повинен мати всі необхідні ресурси. Такий підхід призводить, наприклад, до відмови будь-якої ОРМ .


-2

Для запису.

SRP:

  1. Модель = Дані, сюди йде сетер та геттери.
  2. Логіка / послуги = тут приймаються рішення.
  3. Сховище / DAO = тут ми постійно зберігаємо або отримуємо інформацію.

У такому випадку добре виконати наступні дії:

Якщо заборгованість не потребує певного розрахунку:

userObject.Debt = 9999;

Однак якщо для цього потрібен певний розрахунок:

userObject.Debt= UserService.CalculateDebt(userObject)

або також

UserService.UpdateDebt(userObject)

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

UserRepository.UpdateDebt(userObject)

У цьому випадку, ми хочемо отримати користувача з бази даних і потім оновити борг, нам це слід зробити в кілька кроків (насправді два), і це не вимагає, щоб обернути / інкапсулювати його у функції сервісу.

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)

І якщо тоді потрібно буде зберігати його, ми можемо додати третій крок

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)
UserRepository.Save(userobject);

Про запропоноване рішення

а) Ми не повинні боятися залишити кінцевого розробника написати пару, а не інкапсулювати його у функції.

b) А щодо інтерфейсу, деякі розробники люблять інтерфейс, і вони прекрасні, але в декількох випадках вони взагалі не потрібні.

в) Мета сервісу - створити один без атрибутів, головним чином тому, що ми можемо використовувати спільні / статичні функції. Також легко провести одиничний тест.


як це відповідає на поставлене запитання: Наскільки точна «Бізнес-логіка повинна бути в сервісі, а не в моделі»?
гнат

3
Що таке вирок: "We shouldn't be afraid to left the end-developer to write a couple of instead of encapsulate it in a function."Я можу процитувати лише Льюїса Блек", якби не моя коня, я б не провів цей рік у коледжі ".
Малахій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.