Контекст
Grails позволяет очень легко разместить любую логику вашего приложения в сервисе. Просто grails create-service
и вы готовы идти. По умолчанию существует один экземпляр, который можно вводить в любом месте. Мощный материал и позволяет легко вставать и бегать очень быстро!
Создание нового приложения, следуя так называемым «лучшим практикам» из блогов, подобных этим, и «идиоматический путь Grails», описанный в документах и учебных пособиях, работают в начале, но всегда есть переломный момент — когда приложение выросло разумного размера — где следует начинать следовать другой, может быть, менее Grailsey, стратегии.
Так что может пойти не так, создав сервисы в вашем приложении?
В предыдущем посте о динамических поисках я попытался объяснить, что может произойти с первого дня вашего проекта.
Команда действительно приняла этот совет близко к сердцу и начала централизовать свои запросы Альбомов в AlbumService
, свои запросы Product в ProductService
и так далее.
Вот что я видел, что происходит.
Спринт 1: Жизнь прекрасна
Эта команда начинала очень остро: они почти внедряли бизнес-логику в контроллеры, но могли вовремя внедрить ее в сервисы. Команда grails create-service
даже сразу создаст пустой модульный тест — готовый к реализации.
Производительность была беспрецедентной . Команде больше никогда не приходилось вручную создавать класс с помощью своих IDE, и для следующих спринтов команда сгорела в отставании, как горячий нож в масле.
Перемотка вперед 6 спринтов.
Спринт 6
Глядя на их код, кажется, что их папка services
состоит из десятков классов:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
grails-app /services/ └── example ├── AnotherProductService.groovy ├── ... ├── OrderService.groovy ├── OrderingService.groovy ├── ... ├── Product2Service.groovy ├── ProductAccountingService.groovy ├── ProductBuilderService.groovy ├── ProductCatalogService.groovy ├── ProductCreateService.groovy ├── ProductFinderService.groovy ├── ProductLineFileConverterDoodleService.groovy ├── ProductLineMakerService.groovy ├── ProductLineReaderService.groovy ├── ProductLineService.groovy ├── ProductLineTaglibHelperService.groovy ├── ProductLineUtilService.groovy ├── ProductManagementService.groovy ├── ProductManagerService.groovy ├── ProductMapperService.groovy ├── ProductOrderService.groovy ├── ProductReadService.groovy ├── ProductSaverService.groovy ├── ProductService.groovy ├── ProductTemplateOrderBuilderService.groovy ├── ProductUtilService.groovy ├── ProductWriterService.groovy ├── ProductsReadService.groovy ├── ProductsService.groovy └── ... 1 directory, 532 files |
Шаблон
Это случалось со мной несколько раз. Я и команда ценим простоту и мощь Grails. Следовательно, довольно легко начать использовать команды Grails в полном объеме , такие как все команды create-*
:
1
2
3
4
5
6
7
|
grails> create - create -command create -controller create -domain-class create -functional-test create -integration-test create -interceptor create -scaffold-controller create -script create -service create -taglib create -unit-test |
Во многих проектах Grails, аналогичных вымышленному & # 55357; & # 56898; выше, команда create-service
была чрезмерно использована , потому что она кажется идиоматическим способом создания «бизнес-логики на уровне сервиса».
Да, эта команда создает хороший пустой тестовый модуль и автоматически представляет собой удобный bean-компонент Spring, вставляемый в контроллеры, библиотеки тегов и другие артефакты Grails.
Да, использование *Service
хорошо работает в блогах, демонстрациях и обучающих программах.
Тем не менее, кажется, что мы забыли, что в основном все является «службой» для кого-то другого, но нам не обязательно добавлять постфикс («Служба») к каждому классу как таковому .
Кажется, что люди обычно понимают, когда что-то должно быть контроллером («давайте сделаем create-controller
») или библиотекой тегов («давайте сделаем create-taglib
») и так далее, и для всего остального: boom! «Давайте create-service
».
В любом другом проекте , не связанном с Grails, мы привыкли называть конструктор просто «PersonBuilder», в проектах Grails это вдруг «PersonBuilderService». В любом другом проекте фабрика — это «PersonFactory», в проекте Grails — странная «PersonFactoryService».
Что если «PersonReadService» отвечает за поиск или поиск людей? В течение многих лет люди использовали шаблон Repository для этого, и это может быть отражено с помощью постфикса «Repository», например «PersonRepository».
Даже в Grails строителем может быть Строитель , фабрика, фабрика , маппер, маппер, репозиторий, репозиторий , каракули, каракули и все, что может закончиться чем угодно — вы можете назвать каждый класс так, как хотите.
Что мы можем с этим поделать?
Хватит называть все Сервисом
Во-первых, в следующий раз, когда вы собираетесь создать класс, следуя одному из известных шаблонов проектирования, например, Builder, Factory, Strategy, Template, Adapter, Decorator (хороший обзор см. На sourcemaking.com ) или другим «хорошо известным» Шаблоны Java (EE), такие как Producer, Mapper или что-то, задают себе вопрос:
Это может быть обычный класс в src/main/groovy
?
Переместить и выбрать лучшее имя
- Да, тогда просто создайте класс в
src/main/groovy
. Может быть, выбрать хороший пакет, например,example
. Если вам не нужно 532 класса в одном пакете, вы всегда можете ввести подпакеты, такие какexample.accounting
. Дайте ему имя, которое не заканчивается на*Service
. Не забудьте вручную добавить связанный*Spec
вsrc\test\groovy
.
Вы все еще хотите получить выгоду от Spring и Dependency Injection?
Другими словами, нужен ли вам экземпляр вашего класса, чтобы можно было внедрить его в какие-либо классы Grails, такие как контроллер, как вы привыкли к сервису, как, например, ProductReadService
ниже?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
// grails-app/controllers/example/HomepageController.groovy class HomepageController { ProductReadService productReadService def index() { ... } } // grails-app/services/example/ProductReadService.groovy class ProductReadService { SecurityService securityService Product findByName(String name) { assert securityService.isLoggedIn() Product.findByName(name) } } |
Базовый контейнер создается Spring Framework.
- В документах есть отличная глава о Граалях и Весне . Именно эта инфраструктура будет создавать, например, один
SecurityService
в приложении, внедрять его в свойство «securityService», когда он создает один экземплярProductReadService
который он внедряет вHomepageController
и так далее. -
SecurityService
в этом примере — который может исходить от плагина Security и классов*Service
в ваших собственных источниках приложений — все они автоматически выбираются и управляются контейнером Spring и внедряются в любой другой управляемый класс, который в этом нуждается. - Это не столько перемещение
grails-app/services/example
в папкуsrc/main/groovy/example
, сколько переименование класса во что-то, что больше не заканчивается в «Service» , — тогда вы теряете автоматическое управление к весне. Это происходит, когда мы, как и предполагалось, после перемещения переименовываем классProductReadService
в классProductRepository
.
Да, он хочет, чтобы они были бобом Spring!
Объявление весенних бобов способом Грааля
Конечно, мы можем сделать это сами. Идейным способом Grails является указание bean-компонентов в resources.groovy
.
1
2
3
4
5
6
7
|
// grails-app/conf/spring/resources.groovy import example.* beans = { productRepository(ProductRepository) { securityService = ref( 'securityService' ) } } |
Мы определили bean-компонент с именем «productRepository» класса ProductRepository
и указали, что SecurityService
необходимо ProductRepository
.
Так изменился исходный код, но поведение не изменилось: теперь вместо него используется ProductRepository
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
// grails-app/controllers/example/HomepageController.groovy class HomepageController { ProductRepository productRepository def index() { ... } } // src/main/groovy/example/ProductRepository.groovy class ProductRepository { SecurityService securityService Product findByName(String name) { assert securityService.isLoggedIn() Person.findByName(name) } } |
Это не единственный способ объявить Spring bean .
Объявление Весенние бобы Весенний путь
Мы объявили Spring bean как Grails, но мы также можем объявить bean как Spring.
Хорошо, есть не просто «весенний путь», есть много способов, от старых объявлений XML, сканирования путей к классам до конфигурации в стиле Java.
Наличие (подмножество) ваших 532 классов в resources.groovy
может рассматриваться не так уж и лучше, чем конфигурация XML, используемая Spring в первые дни.
Даже через Beans DSL здесь намного мощнее, чем когда-либо был XML (потому что: Groovy), мы не переводим наши автоматически собранные служебные бины для возврата ручного труда, по моему мнению.
Вот как это будет выглядеть:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
beans = { anotherProductRepository(AnotherProductRepository) { securityService = ref( 'securityService' ) orderingService = ref( 'orderingService' ) } productLineReader(ProductLineReader) productFinder(ProductFinder) { productRepository = ref( 'productRepository' ) productLineService = ref( 'productLineService' ) } productRepository(ProductRepository) { securityService = ref( 'securityService' ) productReader = ref( 'productReader' ) productWriter = ref( 'productWriter' ) } orderingService(OrderingService) { securityService = ref( 'securityService' ) productRepository = ref( 'productRepository' ) } ... |
Есть случаи, когда resources.groovy
прекрасно работает, но почему бы не избавиться от котельной и не использовать современные возможности Spring, которые есть в нашем распоряжении?
Попробуйте вместо этого компонентное сканирование .
- Только один раз установите аннотацию Spring
@ComponentScan
нашего классаApplication.groovy
01
02
03
04
05
06
07
08
09
10
11
12
13
|
// grails-app/init/example/Application.groovy package example import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration import org.springframework.context.annotation.ComponentScan @ComponentScan class Application extends GrailsAutoConfiguration { static void main(String[] args) { GrailsApp.run(Application, args) } } |
Это заставляет Spring при запуске приложения сканировать все компоненты на пути к классам в пакете «example» и регистрировать их как Spring bean-компоненты. Или укажите @ComponentScan("example")
чтобы быть более явным.
Каковы эти «компоненты» вы говорите? Все классы @Component
аннотацией Spring стереотипа @Component
. Или @Service
или @Repository
которые являются просто специализациями.
- Аннотируйте наши классы, чтобы сделать их кандидатами на автоопределение
1
2
3
4
5
6
7
8
9
|
import org.springframework.stereotype.Component @Component // or @Repository in this particular case class ProductRepository { SecurityService securityService Product findByName(String name) { .. } } |
- В тот момент, когда мы перезапускаем наше приложение, мы получим
NullPointerException
когда попытаемся вызвать что-либо наsecurityService
.Spring больше не признает, что он должен что-то делать со свойством — теперь это просто свойство. Чтобы@Autowired
SecurityService
, нам нужно аннотировать свойство с помощью@Autowired
, но это в основном то же самое, что инъекция сеттера, которую мы уже@Autowired
в начале. А@Autowired
— это плита, которая нам не нужна.
Пока мы на этом, я рекомендую использовать конструктор-инъекцию , что означает, что мы создаем (или позволяем IDE создавать) конструктор.
* Мы делаем зависимости ProductRepository
явными .
* Spring автоматически «связывает» наш конструктор, если он у нас ровно один, и вводит все параметры конструктора
1
2
3
4
5
6
7
8
9
|
import org.springframework.stereotype.Component @Component class ProductRepository { final SecurityService securityService ProductRepository(SecurityService securityService) { this .securityService = securityService } |
Это оно.
Кстати, наличие явного конструктора со всеми обязательными зависимостями — это всегда хорошая практика — будь то бин Spring или нет.
- Наконец, верните
resources.groovy
в его исходное пустое состояние — мы его больше не используем.
Наименование важно
Теперь, если бы мы сделали это с оригинальными 532 классами, мы могли бы получить более приятное дерево файлов. Например
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
grails-app /services/ └── example ├── OrderService.groovy ├── ProductService.groovy └── SecurityService.groovy src /main/groovy/ └── example ├── order │ ├── OrderBuilder.groovy │ └── OrderRepository.groovy └── product ├── ProductBuilder.groovy ├── ProductFinder.groovy ├── ProductLineReader.groovy ├── ProductLineTaglibHelper.groovy ├── ProductMapper.groovy ├── ProductRepository.groovy ├── ProductUtils.groovy └── ProductWriter.groovy |
Некоторые фактические *Service
классы *Service
cal по-прежнему находятся в grails-app/services
а остальные могут стать классами с четкими именами, аккуратно помещенными в src/main/groovy
, в то время как вы все еще наслаждаетесь преимуществом их использования в качестве бинов Spring.
Если вы и команда на ранних этапах процесса примете решение о надлежащих соглашениях об именах (пакеты, префиксы классов и т. Д.), Вам не нужно переупорядочивать все, как мы это делали сейчас. Вместе с командой создайте и назовите ваши занятия в организованном месте.
Ссылка: | Anti-Pattern Grails: все это услуга от нашего партнера JCG Теда Винке в блоге Теда Винке . |