Salesforce 架构篇总结(二)理解 Service 层业务遵循的规则

主要目标

理解 Martin Fowler 为了做企业级架构而分离出 Service 层的缘由
理解为什么 Apex 代码要属于 Service 层
在应用和平台的开发中如何很好的契合 Service 层的代码
在 salesforce 平台上用代码去实现 Service 的架构

简单概述

上一篇 Blog 只是简单的介绍了 SOC 思想,将软件的结构抽象到层级应用的逻辑方面,本篇 Blog 主要将目标集中在如何去定义和实现 Service 层,它也是别的层级或者 API 调用的关键步骤。

层级架构之间的关系图:

ServiceLayerSketch up-w250

Service 层对实现的业务层、运算层和一些执行业务的逻辑 Process 有一个清晰和严格的封装,同时 service 层必须保证足够的功能专一和抽象,它能够适应后期功能的多次迭代和灵活的可扩展性。下面的内容是如何使用 Apex 代码来实现 Service 层的业务逻辑,同时说明如何在 Force.com 资源有限的情况的下合理的利用资源去实现它。

使用 Service 层的对象

客户端(client)执行 Service 层的业务逻辑,比如像 UI controller 或者 Batch Apex 等。

值得考虑使用 Service 层的一些方向:

Service 层的

1
2
3
4
5
6
注意:

上图中没有提到 Apex Trigger,因为 Trigger 的逻辑属于 Domain layer,它和 Object 联系非常
紧密,在 Domain 层的业务逻辑通过 platform UI 或者 APIs 或多或少的会和 Service 层联系到一


对 Salesforce 平台的创新和适应性的思考

想象一下有这么一个场景,当写了一段 feature 代码,需求只要一变就得重新对代码进行重构,或者更糟糕的是由于害怕之前写的逻辑被破坏,索性不敢动之前的逻辑,然后出现了大量的重复的代码,真是很糟糕的体验。

设计上的一些考究

  • 命名规范:Service 层应该足够抽象而且其意义,同时应该很广泛来涵盖客户端的很多情况,这些方面可以表现在对类、方法、参数名的命名规范上,什么时候用动词(verbs),什么时候用名词(nouns)都要有所思考;确保名字所表达的是种一般情况而非特殊情况,举个例子,这个方法名是以业务操作来命名的 InvoiceService.calculateTax(…) ,而第二个方法名是以特定的业务操作来命名的 InvoiceService.handleTaxCodeForACME(…) 应该避像第二种这样的的命名方式

  • 平台 / 协调一致:设计一个签名的算法和 Salesforce 平台进行交互是一个很好的实践,尤其要使用 Bulkificaiton 的时候,一个主要需要考虑的因素是,在所有运行在 Force.com 上的代码都是 Bulkificaiton,要考虑调用服务器端的代码参数数组化,而不是一个单一的参数集,举个例子,这个方法就可以使用 Bulkificaiton => InvoiceService.calculateTax(List taxCalulations),而这个方法 => InvoiceService.calculateTax(Invoice invoice, TaxInfo taxCodeInfo),由于参数是单一参数集,也应该尽量去避免

  • SOC 方面的考究:在应用当中 Service 层的代码利用很多 Objects 封装一些任务或者 Process 业务逻辑,与之相对应的是,有些代码关联着特定的 validationfield values 或者 calculations,通过插入、更新、删除触发Trigger 来影响其相对应 Object, 这些代码一般存放在 Trigger 当中,并且可以保留在那里

  • 安全性:Service 层的代码和那些被调用的代码应该确保用户安全,要确保这一点使用 with sharing(with Security Settings enforced)修饰符,尤其需要注意的是如果使用 global 修饰符暴露了太多关键代码,就需要引起注意。如果一个 Apex 类的逻辑必须通过一些 Recodes 让外部的用户可见,那么代码必须提炼它的执行环境,越简略越好,一个好的办法是使用私有的 Apex 内部类,使用 without sharing
    关键字

  • 编组:简言之,避免指定如何处理与 Service 层交互的方面错误信息,因为某些方面最好留给服务的调用者,只管原样抛出异常就好(it is typically best to leverage the default error-handling semantics of Apex by throwing exceptions. )

  • 整合服务:虽然客户端可以一个接一个的执行多次 callout,但这么做会很低效,也会造成一些数据库事务方面的问题,最好的办法是建一个整合服务( compound services)(compound services),让一次 callout 涵盖多个客户端的请求。同样很重要的是尽可能优化 Service 层的 SOQL 和 DML 操作。当然了这并不意味着不能 callout 更细节的逻辑单元;如果需要的话,可以开发特定的单元去以供客户端 callout

  • 事务管理和无状态: Service 层的客户端经常有一些拥有长存活时间且不同的 Process 请求和一些消息用来执行和处理,举个例子,一个单一的请求和多个请求分隔成单独的作用范围到服务器端:管理状态(比如 Batch Apex)或者复杂的 UI 都是通过它们自己的页面状态来接受很多请求的,在状态管理上最好的方式是在做一次 callout 到 Service 层时,封装数据库操作和服务状态。换句话说,使 Service 端保持无状态,以使调用的环境灵活地使用它们自己的状态管理解决方案。例如,一个在数据库事务的作用域同样应该被包括在每一个 Service 层的方法当中,以便于调用者不用去考虑和其相关的 SavePoints

  • 配置:在 Service 层中,可能有常见的配置或行为被覆盖,合理使用方法重载,接受一个共享的选项参数,类似于 Apex 中的 DML 方法

Service 层在 Apex 中的应用

情景介绍:假设在 Opportunity 页面有个自定义 Button 当点击 Button 会出现一个 Visualforce 页面,提示用户对 Opportunity Amount(或相关联的 Opportunity Line)项目应用一个折扣百分比。
实例展示如何将 OpportunitiesService.applyDiscounts 方法应用到比如 Visualforce、Batch Apex、 或者 JavaScript Remoting 这些地方上。

下面的 Code 处理的是通过 StandardController 选择的单个 Opportunity,注意:controller 的错误信息是 controller 自己来处理的,而非 service,因为 visualforce 有其自身的错误表现形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public PageReference applyDiscount()
{
try
{
// Apply discount entered to the current
Opportunity OpportunitiesService.applyDiscounts(new Set<ID>{ standardController.getId() }, DiscountPercentage);
}
catch (Exception e)
{
ApexPages.addMessages(e);
}

return ApexPages.hasMessages() ? null : standardController.view();
}

下面的 Code 展示了通过 StandardSetController 处理多个 Opportunities

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public PageReference applyDiscounts()
{
try
{
// Apply discount entered to the selected Opportunities
OpportunitiesService.applyDiscounts(
// Tip: Creating a Map from an SObject list gives easy access to the Ids (keys)
new Map<Id,SObject>(standardSetController.getSelected()).keyValues(),
DiscountPercentage
);
}
catch (Exception e)
{
ApexPages.addMessages(e);
}

return ApexPages.hasMessages() ? null : standardController.view();
}

下面的 Code 展示了如何使用 Batch Apex 处理大量的数据,注意和之前代码的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public with sharing class OpportunityApplyDiscountJob implements Database.Batchable<SObject>
{
public Decimal DiscountPercentage { get; private set; }

public OpportunityApplyDiscountJob(Decimal discountPercentage)
{
// Discount to apply in this job
this.DiscountPercentage = discountPercentage;
}

public Database.QueryLocator start(Database.BatchableContext ctx)
{
// Opportunities to discount
return Database.getQueryLocator('select Id from Opportunity where StageName = \'Negotiation/Review\'');
}

public void execute(Database.BatchableContext BC, List<sObject> scope)
{
try
{
// Call the service
OpportunitiesService.applyDiscounts(
new Map<Id,SObject>(scope).keySet(),DiscountPercentage);
}
catch (Exception e)
{
// Email error, log error, chatter error etc..
}
}

public void finish(Database.BatchableContext ctx) { }
}

下面的 Code 将 service 层打包起来,并且通过 JavaScript Remoting 暴露给客户端,供其调用

1
2
3
4
5
6
7
8
9
10
public class OpportunityController
{
@RemoteAction
public static void applyDiscount(Id opportunityId, Decimal discountPercent)
{
// Call service
OpportunitiesService.applyDiscounts(new Set<ID> { opportunityId }, discountPercent);
}
}

总结

在 Service 层提不断投入对更大的重用性和适应性的都带来很大的好处,同时也为应用程序实现 API 提供了一种更干净、更经济的方式之一。
原文:
Investing in a service layer for your application offers the engineering benefits of greater reuse and adaptability, as well as provides a cleaner and more cost effective way of implementing an API for your application, a must in today’s cloud-integrated world. By closely observing the encapsulation and design considerations described above, you start to form a durable core for your application that will endure and remain a solid investment throughout the ever-changing and innovative times ahead!

相关资料

Separation of concerns
Martin Fowler’s Service Layer Pattern
Martin Fowler’s Enterprise Architecture Patterns
Learn Service Layer Principles

知识扩展

With Sharing、Without Sharing 和 non-sharing-specified 修饰符的区别

在这里学到 Salesforce 相关的知识的

The fun way to learn Salesforce

Salesforce 架构篇总结(二)理解 Service 层业务遵循的规则

http://example.com/2018/11/18/技术篇/salesforce/salesforce-knowledge-02/

作者

kakushuu

发布于

2018-11-18

更新于

2022-07-01

许可协议

评论