上下文
Grails使将应用程序的任何逻辑放入服务中变得非常容易。 只需grails create-service
,您一切顺利。 默认情况下有一个实例,可以在任何地方注入。 功能强大的功能,可轻松快速启动并运行!
遵循此类博客中所谓的“最佳实践”以及文档和教程中描述的“惯用Grails方式”,一开始便会创建一个新的应用程序,但是总会有一个转折点-应用程序在不断发展壮大。合理的规模-应该开始遵循一种不同的,也许是少圣杯的策略。
那么,在应用程序中创建服务会出错吗?
在先前有关动态查找器的反模式文章中,我试图解释从项目的第一天起可能发生的情况。
一个团队确实采纳了这个建议,并开始将他们的Album查询集中在AlbumService
,将他们的Product查询集中在ProductService
,等等。
这就是我所看到的。
冲刺1:生活是美好的
这个团队起步非常敏锐:他们几乎在控制器中实现了类似于业务的逻辑,但是可以及时将它们引入服务中。 grails create-service
命令甚至可以立即创建一个空的单元测试-准备实施。
生产力无与伦比 。 团队再也不需要使用其IDE 手动创建类 ,对于下一个冲刺,团队像积knife黄油的热刀一样,烧毁了积压的工作。
快进6冲刺。
冲刺6
查看他们的代码,看来他们的services
文件夹包含数十个类:
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-*
命令:
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项目中,类似于虚构的�� 上面的代码中, create-service
命令已被过度使用 ,因为它似乎是在“服务层中创建业务逻辑”的惯用方式。
是的,此命令确实创建了一个不错的,空的单元测试,并且自动将其注入到控制器,标记库和其他Grails工件中,非常方便。
是的,在博客文章,演示和教程中使用*Service
效果很好。
但是,似乎我们基本上已经忘记了所有东西都是对他人的“服务”,但是我们不一定需要像这样对每个类都进行后缀(“服务”) 。
似乎人们通常理解什么时候需要将某个东西用作控制器(“让我们create-controller
”)或标签库(“让我们create-taglib
”)等等,以及其他所有事情: 繁荣! ,“让我们create-service
”。
在任何其他非Grails项目中,我们习惯将构建器简称为“ PersonBuilder”,而在Grails项目中,它突然变成“ PersonBuilderService”。 在任何其他项目中,工厂都是“ PersonFactory”,而在Grails项目中,工厂是“ PersonFactoryService”。
如果“ PersonReadService”负责招募或寻找人员该怎么办? 多年来,人们一直在使用存储库模式 ,这可以通过“存储库”后缀(例如“ PersonRepository”)反映出来。
即使在Grails中,构建者也可以是构建者 ,工厂, 工厂 ,映射器 ,映射器 ,存储库 ,存储库 ,doodle, Doodle以及任何以Whatever结尾的内容-您可以按自己的方式命名每个类。
我们能对此做什么?
停止将一切称为服务
首先,下一次您要按照著名的设计模式之一创建类,例如Builder,Factory,Strategy,Template,Adapter,Decorator(有关详细信息 ,请参见sourcemaking.com ),或其他“知名的” ” Java(EE)模式(例如Producer或Mapper等)会问自己一个问题:
可以在src/main/groovy
作为常规类吗?
移动并选择一个更好的名字
- 是的,只需在
src/main/groovy
创建该类。 也许选择一个不错的包,例如example
。 如果您不想在一个包中包含532个类,则可以始终引入子包,例如example.accounting
。 给它起一个不以*Service
结尾的名称 。 不要忘记在src\test\groovy
手动添加一个关联的*Spec
。
您是否仍然想享受Spring和Dependency Injection的好处?
换句话说,您是否需要一个类的实例,以便能够像习惯使用下面的ProductReadService
这样的服务那样,将其注入到任何Grails类(例如,控制器)中?
// 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框架创建的。
- 关于Grails和Spring的文档中有一章非常出色。 该框架将实例化应用程序中的一个
SecurityService
,并在创建ProductReadService
一个实例时将其注入到“ securityService”属性中,然后将其注入到HomepageController
,依此类推。 - 示例中的
SecurityService
(可能来自Security插件和您自己的应用程序源中的*Service
类)都由Spring容器自动拾取和管理,并注入需要它的其他所有托管类中。 - 并不是将
grails-app/services/example
移到src/main/groovy/example
文件夹,而是通过将类重命名为不再以“ Service”结尾的东西 ,那就是当您失去自动管理功能时到春天。 当我们按照建议在移动之后将类ProductReadService
重命名为ProductRepository
类时,就会发生这种情况。
是的,它希望它们成为Spring bean!
用Grails方式宣告Spring bean
当然,我们可以自己做。 Grails的idomatic方法是在resources.groovy
指定bean。
// grails-app/conf/spring/resources.groovy
import example.*
beans = {
productRepository(ProductRepository) {
securityService = ref('securityService')
}
}
我们已经定义了一个名为ProductRepository
类的“ productRepository”的bean,并表明需要注入SecurityService
。
这是怎样的原代码已经改变,但行为所没有的:它现在使用ProductRepository
代替。
// 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方式声明Spring Bean
我们以Grails方式声明了Spring bean,但是我们也可以以Spring方式声明了bean。
好的,不仅有“ Spring之道”,还有许多方法,从旧的XML声明,类路径扫描到Java样式的配置。
在resources.groovy
拥有532个类(的子集)可能并不比Spring早期使用的XML配置更好。
即使通过Beans DSL,这里的功能也比XML强大得多(因为:Groovy),我认为我们并没有转换自动选择的服务Bean来找回人工。
它是这样的:
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
类
// 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")
更明确。
您说这些“组成部分”是什么? 所有用Spring的@Component
型注释@Component
注释的@Component
。 或@Service
或@Repository
只是专长。
- 注释我们的类 ,使它们成为自动检测的候选对象。
import org.springframework.stereotype.Component
@Component
// or @Repository in this particular case
class ProductRepository {
SecurityService securityService
Product findByName(String name) { .. }
}
- 此刻,当我们重新启动应用程序时,当我们尝试在
securityService
上调用任何内容时,我们将遇到NullPointerException
不再认识到它应该对该属性做些什么-现在仅仅是一个属性。 为了使SecurityService
由Spring注入,我们需要使用Spring的@Autowired
对该属性进行注释,但这与我们一开始就已经拥有的setter注入基本相同。@Autowired
是我们不需要的样板。
在此过程中,我建议使用构造函数注入 ,这意味着我们创建(或让IDE创建)构造函数。
*我们使ProductRepository
的依赖关系显式化 。
*只要我们只有一个,Spring就会自动“自动装配”我们的构造函数 ,并注入构造函数的所有参数
import org.springframework.stereotype.Component
@Component
class ProductRepository {
final SecurityService securityService
ProductRepository(SecurityService securityService) {
this.securityService = securityService
}
就是这个。
BTW具有所有强制性依赖项的显式构造函数始终是一个好习惯,无论它是否为Spring bean。
- 最后, 将
resources.groovy
还原为初始的空状态 -我们不再使用它。
命名很重要
现在,如果我们要对原始的532个类执行此操作,则最终可能会得到更有趣的文件树。 例如
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
类cal仍然驻留在grails-app/services
,其余的可以清晰地命名为类,它们被整齐地放置在src/main/groovy
,同时您仍然可以将它们用作Spring bean。
如果您和团队在此过程的早期就决定了正确的命名约定(包,类前缀等),则不必像我们刚才那样对所有内容进行重新排序。 与团队一起,在有组织的地方创建和命名您的课程 。
翻译自: https://www.javacodegeeks.com/2017/04/grails-anti-pattern-everything-service.html