Grails反模式:一切都是服务

上下文

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值