Насколько точна «бизнес-логика должна быть в сервисе, а не в модели»?


398

ситуация

Ранее этим вечером я дал ответ на вопрос о 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]: определяет задания, которые должно выполнять программное обеспечение, и направляет выразительные доменные объекты для решения проблем. Задачи, за которые отвечает этот уровень, значимы для бизнеса или необходимы для взаимодействия с прикладными уровнями других систем. Этот слой держится тонким. Он не содержит бизнес-правил или знаний, а только координирует задачи и делегирует работу для совместной работы объектов домена в следующем слое вниз. Он не имеет состояния, отражающего бизнес-ситуацию, но может иметь состояние, отражающее ход выполнения задачи для пользователя или программы.

Что здесь усилено :

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

И здесь :

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

Это серьезный контраст с другими ресурсами, которые говорят о сервисном уровне:

Уровень обслуживания должен состоять из классов с методами, которые являются единицами работы с действиями, которые принадлежат одной и той же транзакции.

Или второй ответ на вопрос, который я уже связал:

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

"Решение"?

Следуя рекомендациям в этом ответе , я пришел к следующему подходу, использующему уровень обслуживания:

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
Я рассматриваю уровень обслуживания как место, где нужно разместить неизбежную часть сценария транзакции, действующую на совокупные корни. Если мой уровень обслуживания становится слишком сложным, это сигнализирует мне, что я двигаюсь в направлении Анемической Модели, и моя модель предметной области требует внимания и пересмотра. Я также пытаюсь поместить логику в SL, которая не будет дублироваться по своей природе.
Павел Воронин

Я думал, что сервисы были частью слоя Model. Я ошибаюсь, думая об этом?
Florian Margaine

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

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

3
ИМХО, правильная модель предметной области в стиле ООП должна действительно избегать «бизнес-сервисов» и сохранять бизнес-логику в модели. Но практика показывает, что так заманчиво создать огромный бизнес-уровень с четко названными методами, а также разместить транзакцию (или единицу работы) вокруг всего метода. Намного проще обрабатывать несколько классов моделей в одном методе бизнес-сервиса, чем планировать отношения между этими классами моделей, потому что тогда у вас возникнут сложные вопросы: где находится агрегатный корень? Где я должен начать / зафиксировать транзакцию базы данных? И т.д.
JustAMartin

Ответы:


369

Чтобы определить обязанности службы , сначала необходимо определить, что это за служба .

Услуга не является каноническим или общим программным термином. На самом деле суффикс Serviceимени класса во многом похож на злобный Менеджер : он почти ничего не говорит о том, что на самом деле делает объект .

В действительности то, что должен делать сервис, сильно зависит от архитектуры:

  1. В традиционной многоуровневой архитектуре сервис буквально ассоциируется с уровнем бизнес-логики . Это слой между пользовательским интерфейсом и данными. Поэтому все бизнес-правила переходят в сервисы. Уровень данных должен понимать только основные операции CRUD, а уровень пользовательского интерфейса должен иметь дело только с отображением DTO представления в бизнес-объектах и ​​из них.

  2. В распределенной архитектуре в стиле RPC (SOAP, UDDI, BPEL и т. Д.) Служба является логической версией физической конечной точки . По сути, это набор операций, которые сопровождающий желает предоставить в качестве общедоступного API. Различные руководства по передовому опыту объясняют, что на самом деле операция сервиса должна быть операцией бизнес-уровня, а не CRUD, и я склонен согласиться.

    Однако, поскольку маршрутизация всего через реальный удаленный сервис может серьезно снизить производительность, обычно лучше не делать, чтобы эти сервисы сами реализовывали бизнес-логику; вместо этого им следует обернуть «внутренний» набор бизнес-объектов. Один сервис может включать один или несколько бизнес-объектов.

  3. В архитектуре MVP / MVC / MVVM / MV * сервисы вообще не существуют. Или, если они это делают, термин используется для обозначения любого общего объекта, который может быть введен в контроллер или модель представления. Бизнес-логика в вашей модели . Если вы хотите создать «служебные объекты» для организации сложных операций, это рассматривается как деталь реализации. К сожалению, многие люди реализуют MVC подобным образом, но это считается анти-паттерном ( Anemic Domain Model ), потому что сама модель ничего не делает, это просто набор свойств для пользовательского интерфейса.

    Некоторые люди ошибочно полагают, что использование метода с 100-строчным контроллером и внедрение его в службу каким-то образом приводит к лучшей архитектуре. Это действительно не так; все, что он делает, это добавляет еще один, возможно, ненужный слой косвенности. Практически говоря, контроллер все еще выполняет свою работу, он просто делает это с помощью плохо названного объекта-помощника. Я настоятельно рекомендую презентацию Wimed Domain Models Джимми Богарда для наглядного примера того, как превратить анемичную модель предметной области в полезную. Это включает в себя тщательное изучение моделей, которые вы выставляете, и какие операции действительно действительны в бизнес- контексте.

    Например, если ваша база данных содержит «Заказы», ​​и у вас есть столбец «Общая сумма», вашему приложению, вероятно, нельзя разрешить фактически изменять это поле на произвольное значение, поскольку (а) это история и (б) оно должно быть определяется тем, что в порядке, а также, возможно, некоторыми другими чувствительными ко времени данными / правилами. Создание службы для управления заказами не обязательно решает эту проблему, потому что пользовательский код может по- прежнему захватывать фактический объект заказа и изменять сумму на нем. Вместо этого, порядок сам должен нести ответственность за обеспечение того, что он может быть изменен только в безопасных и последовательных способах.

  4. В DDD сервисы предназначены специально для ситуации, когда у вас есть операция, которая должным образом не принадлежит какому-либо агрегированному корню . Здесь нужно быть осторожным, потому что часто потребность в услуге может означать, что вы не использовали правильные корни. Но, предположив, что вы это сделали, сервис используется для координации операций между несколькими корнями или иногда для решения проблем, которые вообще не затрагивают модель предметной области (например, возможно, запись информации в базу данных BI / OLAP).

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

    Это радикальный отход от определения сервисов многоуровневой архитектуры. Сервисный уровень инкапсулирует доменные объекты; служба DDD инкапсулирует все, что не входит в доменные объекты, и не имеет смысла быть.

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

    По необходимости, сервисы на самом деле являются сквозным предложением в SOA. Это означает, что сервис - это не столько отдельный компонент, сколько целый стек , и все ваше приложение (или весь ваш бизнес) представляет собой набор этих сервисов, работающих бок о бок без пересечений, за исключением уровней обмена сообщениями и пользовательского интерфейса. Каждый сервис имеет свои данные, свои бизнес-правила и собственный интерфейс. Им не нужно взаимодействовать друг с другом, потому что они должны быть ориентированы на бизнес - и, как и сам бизнес, каждый сервис имеет свой собственный набор обязанностей и работает более или менее независимо от других.

    Таким образом, согласно определению SOA, каждая часть бизнес-логики в любом месте содержится внутри службы, но опять же, как и вся система . Сервисы в SOA могут иметь компоненты , и они могут иметь конечные точки , но довольно опасно называть любой фрагмент кода сервисом, потому что он конфликтует с тем, что должно означать исходное «S».

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

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

Там нет правильного или неправильного ответа, только то, что относится к вашей ситуации.


12
Спасибо за очень сложный ответ, вы прояснили все, что я могу придумать. В то время как другие ответы также хороши до превосходного качества, я считаю, что этот ответ превосходит их все, поэтому я приму этот. Я добавлю это здесь для других ответов: превосходное качество и информация, но, к сожалению, я только смогу дать вам голос.
Йерун Ванневел

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

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

1
Но если мы возьмем более 100 линейных контроллеров (например, этот контроллер принимает сообщение - затем десериализовать объект JSON, выполнить проверку, применить бизнес-правила, сохранить в БД, вернуть объект результата) и переместить некоторую логику в один из вызываемых сервисных методов. Разве это не помогает нам отдельно тестировать каждую его часть безболезненно?
artjom

2
@ Aaronaught Я хотел бы уточнить одну вещь, если у нас есть доменные объекты, сопоставленные с БД через ORM, и в них нет бизнес-логики. Это модель анемичного домена или нет?
artjom

40

Что касается вашего названия , я не думаю, что вопрос имеет смысл. Модель MVC состоит из данных и бизнес-логики. Сказать, что логика должна быть в Сервисе, а не Модель, это как сказать: «Пассажир должен сидеть на сиденье, а не в машине».

Опять же, термин «модель» является перегруженным термином. Возможно, вы имели в виду не модель MVC, а модель в смысле объекта передачи данных (DTO). АКА Сущность. Об этом говорит Мартин Фаулер.

Как я понимаю, Мартин Фаулер говорит о вещах в идеальном мире. В реальном мире Hibernate и JPA (на земле Java) DTO - это супер утечка абстракций. Я хотел бы поместить мою бизнес-логику в мою сущность. Это сделало бы вещи чище. Проблема в том, что эти объекты могут существовать в управляемом / кэшированном состоянии, которое очень сложно понять и постоянно мешает вашим усилиям. Подводя итог моему мнению: Мартин Фаулер рекомендует правильный путь, но ОРМ мешают вам это сделать.

Я думаю, что у Боба Мартина есть более реалистичное предложение, и он дает его в этом видео, которое не бесплатно . Он говорит о том, чтобы держать ваши DTO свободными от логики. Они просто хранят данные и переносят их на другой уровень, который гораздо более объектно-ориентирован и не использует DTO напрямую. Это позволяет избежать утечки абстракции от вас кусать. Слой с DTO и самими DTO не являются OO. Но как только вы выходите из этого слоя, вы становитесь таким же OO, как и Мартин Фаулер.

Преимущество этого разделения состоит в том, что оно абстрагирует постоянный слой. Вы можете переключиться с JPA на JDBC (или наоборот), и никакая бизнес-логика не должна будет измениться. Это зависит только от DTO, не важно, как эти DTO заполняются.

Чтобы немного изменить тему, необходимо учитывать тот факт, что базы данных SQL не являются объектно-ориентированными. Но ORM обычно имеют сущность - которая является объектом - для каждой таблицы. Итак, с самого начала вы уже проиграли битву. По моему опыту, вы никогда не можете представлять сущность в точности так, как вы хотите, объектно-ориентированным способом.

Что касается « на службе», Боб Мартин был бы против того , чтобы класс с именем FooBarService. Это не объектно-ориентированный. Что делает сервис? Все, что связано с FooBars. Это может также быть помечено FooBarUtils. Я думаю, что он будет выступать за уровень обслуживания (более подходящее название - уровень бизнес-логики), но у каждого класса в этом уровне будет значимое имя.


2
Согласитесь с вашей точкой зрения на ORM; они распространяют ложь, что вы отображаете вашу сущность непосредственно в БД вместе с ними, когда в действительности сущность может храниться в нескольких таблицах.
Энди

@ Даниэль Каплан, вы знаете, что за обновленная ссылка на видео Боба Мартина?
Брайан Морарти

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/…
Марк Джучли,

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.
Мой опыт и мнения говорят о том, что любые объекты, имеющие бизнес-логику, должны быть частью модели предметной области. Модель данных, скорее всего, не должна иметь никакой логики. Скорее всего, службы должны связывать их вместе и решать общие проблемы (базы данных, ведение журналов и т. Д.). Однако принятый ответ является наиболее практичным.

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

На практике у вас не должно быть службы, которая вносит изменения в какие-либо доменные объекты; причина этого в том, что у вашей службы, вероятно, будет какой-то метод для каждого свойства вашего объекта, чтобы изменить значение этого свойства. Это проблема, потому что тогда, если у вас есть интерфейс для вашего объекта (или даже если нет), сервис больше не следует принципу Open-Closed; вместо этого, всякий раз, когда вы добавляете больше данных в вашу модель (независимо от домена в сравнении с данными), вам в конечном итоге приходится добавлять дополнительные функции в вашу службу. Есть определенные способы обойти это, но это самая распространенная причина, по которой я видел сбой «корпоративных» приложений, особенно когда эти организации считают, что «корпоративное» означает «иметь интерфейс для каждого объекта в системе». Можете ли вы представить добавление новых методов в интерфейс, затем к двум или трем различным реализациям (встроенная в приложение, фиктивная реализация и отладочная, встроенная в память?), только для одного свойства вашей модели? Звучит как ужасная идея для меня.

Здесь есть более длинная проблема, в которую я не буду вдаваться, но суть в следующем: хардкорное объектно-ориентированное программирование говорит, что никто за пределами релевантного объекта не должен иметь возможности изменять значение свойства внутри объекта или даже " см. «стоимость имущества внутри объекта. Это можно облегчить, сделав данные доступными только для чтения. Вы по-прежнему можете столкнуться с такими проблемами, как, например, когда многие люди используют данные даже для чтения, и вам приходится менять тип этих данных. Возможно, что все потребители должны будут измениться, чтобы приспособиться к этому. Вот почему, когда вы делаете API-интерфейс для использования кем-то и всеми, вам советуют не иметь общедоступных или даже защищенных свойств / данных; это и есть та самая причина, по которой ООП был изобретен.

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

В DDD сервисы предназначены специально для ситуации, когда у вас есть операция, которая должным образом не принадлежит какому-либо агрегированному корню. Здесь нужно быть осторожным, потому что часто потребность в услуге может означать, что вы не использовали правильные корни. Но, предположив, что вы это сделали, сервис используется для координации операций между несколькими корнями, а иногда и для решения проблем, которые вообще не затрагивают модель предметной области ...


1

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

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

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


0

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

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


0

Концепция сервисного уровня может рассматриваться с точки зрения DDD. Аарона упомянул об этом в своем ответе, я просто уточню это немного.

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

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);
    }
}

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

Итак, отвечая на ваши вопросы с этой точки зрения:

Это средство просто извлечь логику из контроллера и поместить ее в службу вместо этого?

Нет.

Предполагается ли сформировать договор между контроллером и доменом?

Ну, это можно так назвать.

Должен ли быть слой между доменом и уровнем сервиса?

Нет.


Существует радикально другой подход, который полностью отрицает использование любого вида услуг. Например, Дэвид Уэст в своей книге « Мышление объекта» утверждает, что любой объект должен иметь все необходимые ресурсы для выполнения своей работы. Этот подход приводит, например, к отказу от любого ORM .


-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);

О предлагаемом решении

а) Мы не должны бояться оставлять конечного разработчика, чтобы написать пару, а не заключать их в функцию.

б) Что касается интерфейса, то некоторые разработчики любят интерфейс, и они в порядке, но в некоторых случаях они вообще не нужны.

c) Цель службы - создать службу без атрибутов, главным образом потому, что мы можем использовать функции Shared / Static. Это также легко для модульного тестирования.


как это отвечает на заданный вопрос: насколько точной должна быть «бизнес-логика в сервисе, а не в модели»?
комнат

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.