65.9K
CodeProject 正在变化。 阅读更多。
Home

Tidy Spring - Services

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (3投票s)

2016 年 5 月 13 日

CPOL

6分钟阅读

viewsIcon

10776

一份关于如何在 Spring 应用程序中高效工作的实践列表。本部分侧重于服务。

Spring 已成为 Java 开发人员中非常受欢迎的选择。毕竟,它是一个拥有海量有用功能的出色项目。我决定与您分享我用于在 Spring 应用程序中高效工作、避免不必要的框架耦合并实现整洁应用程序架构的实践列表。一套完整的实践内容在一篇文章中篇幅过长,因此我将其分成了几个小部分。在这篇文章中,我将重点介绍服务

什么不是(好的)服务?

如果您读过我其他的文章,您可能会注意到我主要针对的是许多应用程序中存在的糟糕实践。这篇也不例外。在 Spring 应用程序中出现像这样的类并不少见

@Service
public class MyObjectService {
    
    @Autowired
    private MyObjectRepository myObjectRepository;

    public MyObject findOne(Long id) {
        return myObjectRepository.findOne(id);
    }

    public List<MyObject> findAll() {
        return myObjectRepository.findAll();
    }

    public MyObject save(MyObject myObject) {
        return myObjectRepository.save(myObject);
    }

    public void delete(MyObject myObject) {
        myObjectRepository.delete(myObject);
    }
}

然后,我们可以想到的与 MyObject 相关的所有其他方法都进入了这个服务。当然,每种业务对象类型都有自己的服务。一旦应用程序的增长超出了基本 CRUD,人们就会尝试利用他们“好用”的服务,并将它们混杂在一起,形成意大利面条式代码。

Spaghetti

我可以写很多关于为什么这是个坏主意的文章,但我会把这个留给您,而是专注于正确的方法。

什么是(好的)服务?

服务是一个类,代表一个单一的应用程序用例或其中的一部分。因此,我们说它包含应用程序特定的业务规则(可以参考《整洁架构》中的 Interactors)。在大多数情况下,它会创建并协调其他对象以满足用例的要求。服务类属于应用程序的“业务部分”。这意味着它们不包含 Spring 依赖(但仍然是 Spring 应用程序中非常重要的一部分)。

Service

如何命名服务?

当然,用用例的名称来命名!如果您正在实现网上商店的下单流程,那么就将其命名为 PlaceOrderOrderPlacement。任何描述用例背后过程的名称,并且仅限于此!

有一篇关于在 Ruby 中命名服务对象的很棒的 gist,同样适用于 Spring 服务,在此处(认真阅读!)。

此外,还有一些词我们想要避免,比如“Service”或“Manager”。这些词没有增加任何价值,而且像磁铁一样吸引问题。想象一下,如果我们给下单服务命名为 OrderService,会发生什么?您认为取消订单会放在哪里?查看订单详情又会放在哪里?OrderServiceOrderManager 告诉类的唯一信息是它包含与 order 相关的*某些*内容。名称必须具有描述性和精确性。

输入和输出

由于服务靠近“业务部分”的边界,因此它们应该使用简单的数据结构作为输入和输出。一个 String 的映射、一个带有 public 字段的类或带有访问器的 private 字段可能有效。原因是我们要将所有业务规则都封闭在业务组件内部。因此,领域对象不应该泄露到外部。服务负责将数据结构转换为领域对象并反之(当然,如果需要,它可以为此使用一个辅助类)。这个想法源于六边形架构整洁架构

有状态还是无状态?

这取决于。在大多数情况下,无状态单例 bean 就足够了。如果不够,将其设置为有状态也没有问题。我见过一些完全围绕无状态服务/ Bean 构建的应用程序,在某些情况下,效果非常糟糕。如果服务的显式实现是无状态的,那就这样。但一旦它需要一些状态,例如,很多参数通过服务的方法传递下来,您就应该使其有状态。顺便说一下,这适用于所有 Bean,不仅仅是服务。

如何创建服务实例?

因为我假设业务类不了解框架,那么显然我不能使用任何 Spring 注解,比如 @Service@Component。但还有其他一些选择

  • 控制器中的 new - 看起来可能有些糟糕的耦合,但在某些情况下可能足够了,例如,一个没有依赖项的简单服务
  • 主组件中的 @Configuration 类内的 @Bean - 适用于无状态服务
  • 由控制器内部使用的、带有 @Component 注解的工厂类 - 适用于有状态服务

创建服务时,我们通过构造函数或一系列 setter 方法注入依赖项。与许多人所说的不同,我认为使用构造函数注入而不是 setter 并没有太大的优势,尤其是在您有很多依赖项的情况下。另一方面,大量的依赖项可能表明设计存在问题。

示例

让我们更详细地看一下下单的例子。我们将假设处理新订单包含几个步骤

  1. 保存订单
  2. 创建与订单关联的发票
  3. 将发票发送给客户
  4. 将包裹发送给客户

当然,我们的服务不必自己实现所有这些。相反,它将协调其协作者

Orchestration

这张图可能(让它)看起来很复杂,但代码可能非常简单

public class OrderPlacement {
    // collaborators

    public void execute(OrderData orderData) {
        Order order = orderRepository.save(toOrder(orderData));
        Invoice invoice = invoiceRepository.save(invoiceFactory.make(order));
        mailer.send(invoice);
        parcelSender.send(parcelFactory.make(order));
    }

    // toOrder, setters
}

服务似乎适合无状态,因此我们可以将其创建为 @Configuration 类中的单例 Bean

@Configuration
public class OrderServices {

    @Bean
    public OrderPlacement orderPlacement() {
        return new OrderPlacement(...);
    }

    // other services
}

最后一步,我们在控制器中自动装配 Bean

@Controller
public class OrderController {
    private OrderPlacement orderPlacement;

    @Autowired
    public OrderController(OrderPlacement orderPlacement) {
        this.orderPlacement = orderPlacement;
    }

    // @RequestMapping etc.
    public void placeOrder(PlaceOrderRequest request) {
        // validate the request etc.
        orderPlacement.execute(toOrderData(request));
    }

    // toOrderData(PlaceOrderRequest request)
}

请注意,控制器*可以*,但*不一定*需要与服务具有相同的输入。

分区 (Partitioning)

我们可以想象,下单过程比这几个步骤要复杂得多。当然,其中任何一个步骤都可能更加复杂,例如,可能需要更多的操作、日志记录或更新业务指标。在这种情况下,在一个服务中实现所有这些将过于冗长(或者并非如此,您已经阅读了我之前链接的 gist?)。我们可以通过将过程拆分成更小的服务来解决这个问题,这些服务将成为原始服务的直接协作者

Orchestration 2

进行这种划分的一个明显好处(也是执行这种划分的另一个原因)是,较小的服务很可能具有可重用性。

结论

服务关乎用例。它们协调其他对象/服务以满足用例背后的需求。服务的好名称是其所代表的(部分)用例背后过程的名称。服务消费和生成简单的数据结构,从而保护业务逻辑不泄露到边界之外。它可以是有状态的,也可以是无状态的,这会影响我们创建它的方式。一旦我们发现我们的服务太大了,或者我们想重用它的某一部分,我们就可以将其拆分成更小的服务。

系列的其他部分

Tidy Spring - 启动项目

Tidy Spring - 配置属性

© . All rights reserved.