Весна и модель анемичной области - PullRequest
64 голосов
/ 20 августа 2009

Итак, я заметил, что у меня определенно есть тенденция к созданию шаблонов моих стековых объектов Spring / Hibernate, например:

  • Контроллер Foo звонит в "FooService"
  • FooService вызывает метод FooRepository.getById () для получения некоторых Foos.
  • FooRepository делает несколько вызовов Hibernate для загрузки объектов Foo.
  • FooService взаимодействует с Foos. Он может использовать связанный TransactionalFooService для обработки того, что необходимо сделать вместе в транзакции.
  • FooService просит FooRepository сохранить Foos.

Проблема здесь в том, что у Фоос нет никакой реальной логики. Например, если электронное письмо необходимо отправлять каждый раз, когда истекает срок действия Foo, вызов Foo.expire () отсутствует. Есть вызов FooService.expireFoo (fooId). Это по ряду причин:

  • Раздражает получение других услуг и объектов от Foo. Это не бин Spring, и он был загружен Hibernate.
  • Раздражает, что Foo делает несколько транзакций.
  • Трудно решить, должен ли Foo отвечать за выбор времени сохранения. Если вы вызываете foo.setName (), должно ли foo сохранить изменения? Должно ли это ждать, пока вы не вызовете foo.save ()? Должен ли foo.save () просто вызывать FooRepository.save (this)?

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

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

Ответы [ 4 ]

12 голосов
/ 20 августа 2009

Вы можете заставить Spring внедрять ваши сервисы в ваши экземпляры Hibernate, используя AOP. Вы также можете заставить Hibernate делать то же самое, используя перехватчики.

См. http://www.jblewitt.com/blog/?p=129

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

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

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

9 голосов
/ 20 августа 2009

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

Возможно, что у Foo нет поведения, которым он управляет. Также допустимо, чтобы не использовал шаблон Domain Model , если ваша бизнес-логика минимальна. Шаблон Transaction Script иногда просто имеет смысл.

Проблема возникает, когда эта логика начинает расти. Реорганизация сценария транзакции в модель предметной области не самая простая вещь, но, конечно, не самая сложная. Если у вас есть тонны логики, связанные с Foo, я бы рекомендовал перейти к модели доменной модели. Преимущества инкапсуляции позволяют очень легко понять, что происходит и кто с чем связан.

Если вы хотите иметь Foo.Expire(), создайте событие в вашем классе Foo, например OnExpiration. Подключите ваш foo.OnExpiration += FooService.ExpireFoo(foo.Id) к созданию объекта, возможно, через фабрику, используемую FooRepository.

Действительно подумай первым. очень возможно, что все уже на своем месте ... пока.

Удачи!

4 голосов
/ 14 октября 2011

Я думаю, что существует простой шаблон рефакторинга, который решит вашу проблему.

  1. Добавьте ваш сервис в ваш репозиторий.
  2. Перед возвратом Foo установите его 'FooService
  3. Теперь попросите ваш FooController запросить соответствующий Foo из FooRepository
  4. Теперь назовите методы, которые вы хотите на вас Foo. Если он не может реализовать их сам, попросите его вызвать соответствующий метод на FooService.
  5. Теперь удалите все вызовы FooService через то, что мне нравится называть методами "моста моста" в Foo (он просто передает параметры службе).
  6. Теперь, когда вы хотите добавить метод, добавьте его в Foo.
  7. Добавляйте материал в сервис только тогда, когда вам действительно нужно по соображениям производительности. Как всегда, эти методы должны вызываться через объект модели.

Это поможет развить вас к более богатой доменной модели. Он также сохраняет принцип единой ответственности, поскольку весь ваш DB-зависимый код остается в имплементациях FooService и помогает вам перенести бизнес-логику с FooService на Foo. Если вы хотите переключить свой сервер на другую БД или в оперативную память или макет (для тестирования), вам не нужно ничего менять, кроме уровня FooService.

^ Я предполагаю, что FooService выполняет вызовы БД, которые будут слишком медленными для ORM, например, выбор самого последнего Foo, который разделяет свойство X с данным Foo. Вот как больше всего я видел работу.


* +1025 * Пример

Вместо:

class Controller{
    public Response getBestStudentForSchool( Request req ){
        Student bestStudent = StudentService.findBestPupilForSchool( req.getParam( "schlId" ).asInt() );
        ...
    }
}

Вы будете двигаться к чему-то вроде этого:

class Controller{
    public Response getBestStudentForSchool( Request req ){
        School school = repo.get( School.class, req.getParam( "schlId" ).asInt() ); 
        Student bestStudent = school.getBestStudent();
        ...
    }
}

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

0 голосов
/ 19 февраля 2019

Я рекомендую вам книгу Моделирование объектов на основе сценариев с использованием UML Дуга Розенберга и Мэтта Стивенса. В нем рассказывается о процессе ICONIX - методологии разработки программного обеспечения, в которой также говорится об анемичной модели предметной области. Это также тема, разработанная Мартином Фаулером на своем веб-сайте https://www.martinfowler.com/bliki/AnemicDomainModel.html. Но чего мы добиваемся при использовании Spring Framework и / или Spring Boot, я тоже пытаюсь выяснить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...