Spring и модель анемической области

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

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

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

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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

Возможно, что Foo не контролирует поведение. Также приемлемо не использовать шаблон Модель домена, если ваша бизнес-логика минимальна. A Пакет транзакций Script иногда имеет смысл.

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

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

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

Удачи!

Ответ 3

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

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

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

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


Пример

Вместо:

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.

Ответ 4

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