数字化转型:服务化设计原则(下)

简介: 数字化转型:服务化设计原则(下)

(6)隔离变化原则

当服务中心核心领域模型的对象进入前台应用中,要避免服务中心内部的重构或者模型变更导致前台应用也跟着变化。

比如前面描述的“文档服务”,其中Article对象在服务中心内部可能作为核心建模的领域模型,甚至作为对象和数据库映射(O/R mapping)等。如果文档服务给服务消费者直接返回Article,即使没有前面所说的冗余字段、复杂类型等问题,也可能让服务外部用户与服务内部系统的核心领域模型产生一定的关联,甚至可能与O/R mapping机制、数据表结构等产生关联,这样一来,内部的重构很可能影响到服务外部的用户。

同样,可采用外观模式和DTO作为中介者和缓冲带,隔离内外系统,把内部系统变化对外部的冲击降到最低。


(7)契约包装

虽然使用了DTO和外观模式将服务生产端的变化与服务消费端进行了隔离,但DTO和外观模式可能被服务消费端的程序到处引用,这样消费端程序就较强地耦合在服务契约上了。一旦契约更改,或者消费端要选择完全不同的服务提供方(有不同的契约),修改时工作量可能就非常大了。在较理想的面向服务设计中,可以考虑包装远程服务访问逻辑,也称为服务代理(Delegate Service)模式,由消费端自己主导定义接口和参数类型,并将服务调用转发给真正的服务客户端,从而让服务使用者完全屏蔽服务契约。

服务代理示例如下:

//ArticlesService是消费端自定义的接口

class ArticlesServiceDelegate implements ArticlesService {

   //假设是某种自动生成的service客户端stub类

   private ArticleFacadeStub stub;


   public void deleteArticles(List<Long> ids) {

       stub.deleteArticles(ids);

   }

}

在此示例的前台应用中,所有有关文档服务调用的地方引用的都是ArticlesService,而不是“文档服务”提供的ArticleFacadeStub,这样就算服务提供端的ArticleFacadeStub发生了变更或者重构,也只需要在ArticlesService类中进行相应的调整,而无须更改更多的代码。


(8)服务无状态原则

为了保证服务中心的服务稳定性以及可扩展性,必须将服务设计为可伸缩的且可部署到高可用的基础结构中。此重要原则的一个推论就是,服务不应为“有状态型”的。即服务不应依赖于服务使用者和服务生产者之间长期存在的关系,服务调用也不应显式或隐式地依赖于前一次调用。为了说明这一点,我们举一个简单的例子,下面是一个电话对话:

问:小明的账号余额是多少?

 答:320元。

问:他的信用额度是多少?

 答:2000元。

此示例演示了典型的有状态模式。第二个问题通过使用“他的”引用第一个问题。这个示例中的操作依赖于转换上下文。现在让我们考虑一下所提供的应答,请注意,回答中没有上下文信息。只有在被询问者知道所询问的问题时,这个回答才有意义。在此示例中,要求使用者维护对话状态,以便解释所得到的应答。

首先,我们考虑一下依赖于前一操作建立的上下文的操作。假如这是一个与呼叫中心的交互,只要与同一个操作人员对话,对话就可以有效地结束。但我们假设呼叫被中断了,如下所示:

问:小明的账号余额是多少?  

 话务员1:320元。


此时通话中断,被转接到另一个话务员:

问:他的信用额度是多少?

 话务员2:谁?

中断导致上下文丢失,因此第二个问题是没有意义的。就这个电话对话而言,我们可以通过重新建立上下文而抵消中断带来的后果:“我在问小明的银行账户的信息,您能告诉我他的信用额度吗?”不过,在可扩展服务调用领域,有状态对话通常更为麻烦,重新建立上下文也许在技术上可行,但很可能带来很大的性能开销。

是否要求使用关联性。即相同的服务使用者发出的连续请求是否必须交付到相同的服务提供者实例,要求使用关联性是一种有状态性与可伸缩性及可靠性冲突的情况。为了保持服务中心各服务能力的服务质量,我们必须优先考虑最终服务架构的可伸缩性和可靠性。所以笔者强烈建议,将服务设计为可避免维护会话上下文的需求。

回到上面电话对话的示例,我们可以通过将服务设计为在响应中包含合适的关联信息,从而避免对会话状态的需求,如下所示:

问:小明的信用额度是多少?

 答:小明的信用额度是2000元。

在响应中包含关联信息是很好的做法,原因很多。首先,它简化了可伸缩解决方案的构造,还能提供更多的诊断帮助,且在不可能向原始请求程序交付错误响应时非常重要。总之,仔细地进行服务设计可以避免对状态的需求,从而简化可靠的、可伸缩服务结构的实现。


(9)服务命名原则

我们在选择服务、操作、数据类型和参数的名称时有一个指导原则:希望最大化服务的易用性。我们希望帮助业务应用开发人员标识实现业务流程所需的服务和操作,因此,强烈建议对服务使用者定义专业领域内有意义的名称,优先选用业务概念而不是技术概念。

建议就是:应使用名词对服务进行命名,使用动词对操作进行命名。例如,以下是使用动词短语和IT构造的服务定义:

ManageCustomerData {

   insertCustomerRecord();

   updateCustomerRecord();

   //etc ... }

接下来是使用名词和动词短语及业务概念的服务定义:

CustomerService {

   createNewCustomer();

   changeCustomerAddress();

   correctCustomerAddress();

   // etc ... }

比较明显,第二个示例的易用性更好一些。在第二个示例中,服务的业务用途非常清楚,而不仅仅指示其输出。因此,建议不要使用“update-CustomerRecord”(可以为出于任何原因进行的任何更新),而使用“enable-OverdraftFacility(启用透支能力)”。与此类似,在客户搬迁时,我们使用“changeCustomerAddress”方法更改客户地址;而在希望更正无效数据时使用“correctCustomerAddress”更正客户地址,因为这样很容易看出这两个操作采用了不同的服务逻辑。


(10)服务操作设计原则

这是对于服务操作命名设计原则的进一步深化:应当使用具体的业务含义而不是泛型操作对操作进行定义。例如,不要使用泛泛的update-CustomerDetails操作,而要创建changeCustomerAddress、recordCustomer-Marriage和addAlternativeCustomerContactNumber之类的操作。此方法具有以下好处:

操作与具体业务场景对应。此类场景可能不仅是简单地更新数据库中的记录。例如,更改地址或婚姻状况可能需要更改其他业务模块中的相关信息,比如婚姻状况的修改可能会引起会员权益的改变。如果使用不太具体的操作(如UpdateCustomerDetails),则不适合实现此类业务场景。

各个操作接口将非常简单,且易于理解,从而提高易用性。

每个操作的更新单元有清楚的定义(在我们的示例中为地址、婚姻状况和电话号码)。在实现具有高并发性要求的系统时,我们可以基于操作的要求采用更细粒度的锁定策略,从而减少资源争用。

针对操作中参数的设计,应采用粗粒度和灵活性强的参数,目的是尽量减少因为需求变更带来的参数结构变化。以CreateNewCustomer操作的两个接口为例:

采用细粒度参数的CreateNewCustomer操作接口如下:

int CreateNewCustomer(String familyName,String givenName,

   String initials, int age,String address1,

   String address2, String postcode    // ...  )

采用单个粗粒度参数的CreateNewCustomer操作接口如下:

int CreateNewCustomer( CustomerDetails newDetails)

以上两段示例代码显示了一个具有很多细粒度参数的操作和采用结构化类型作为单个粗粒度参数的操作。之所以建议使用粗粒度参数,是因为这样能够在很大程度上避免因为细粒度参数变化带来服务整体版本升级。

从参数灵活性的角度看,要考虑服务需求的多样性和灵活性。比如,在查询商品信息时,商品定义的字段很多,不同的业务关注的字段不一样,所以在定义接口时,可通过传入业务方需要返回的商品的字段,将这些字段保存在List对象中,服务获取对应字段的值后封装成对应的Map对象返回。这样通过一个商品查询的操作方法就能满足不同应用系统对商品字段的信息获取需求。


(11)重要的服务不能依赖非重要的服务

中台建设是以服务为中心,即整个体系间的交互均以服务的形式进行。不仅前台应用和中台的各服务中心会以服务的方式进行交互,而且各服务中心之间也会这样交互。在有些情况下,前台应用在业务复杂度发展到一定程度后,也会建立起在该应用系统内部的服务体系。比如,天猫和淘宝这样的业务前端应用已经非常复杂,其内部就构建起了一个多层的服务体系。业务中台的各服务中心为这个服务体系的最下层,之上的各前端业务系统中又会按照自身业务的特点建立起自己的服务层级。

在整个服务体系中,有交易、商品、订单相关等这一类非常核心和重要的服务,也有相对不重要的服务,如运费计算或者前端应用中所创建的服务。从服务对业务的影响程度、服务范围就会体现出服务重要性不同,而且服务重要性的不同也直接决定了能得到的支持和保障资源会有差异,从而最终会体现在服务的稳定和可靠性方面。所以越在下层的服务会越稳定,越往上层的服务则不管是稳定性还是业务兼容性方面都不如下层服务。


“重要的服务不能依赖非重要的服务”这一原则可以更加细化,如下所示:

  • 上可依赖下。越上层的服务实现可以依赖下层的服务,也可跨级依赖。
  • 下不可依赖上。下层的服务实现和运行一定不能依赖上层的服务,否则就会出现因为上层服务质量问题和不稳定的表现影响到下层的重要服务,而下层服务的故障将会影响到依赖这一服务的所有平级服务中心和前台应用的情况,会出现严重的“雪崩”效应。
  • 平级可依赖,避免循环依赖。这一原则最典型的体现是业务中台的各服务中心在服务层级中均属于平级,它们均有同级别的服务运营要求,是可以互相依赖的。
  • 高级别不可依赖低级别。业务重要性明显高的服务不能依赖业务重要性低的服务,应做好相应的服务降级,或者通过前台业务隔离这种情况的服务依赖。

总结:简单就是美,过多的原则可能会让整体的设计变得臃肿,在什么情况下采用什么样的原则,需要建立在对业务理解的基础上,而且需要在实践过程中不断练习,从而能更从容地应对服务设计相关的问题。


本文由机械工业出版社独家授权发布,中台圣经——《企业IT架构转型之道》作者钟华新作!《数字化转型的道与术:以平台思维为核心支撑企业战略可持续发展》。


十余年数字化实战经验再升华!开创性提出数字化转型中平台思维的十大要素。来自实践,并能指导实践。系统化介绍数字化转型的思路与方法,以及产业互联网平台的建设思路,为各种业务模式的数字化转型提供高价值参考。

相关文章
|
Linux Docker 异构计算
ModelScope问题之下载了官方镜像 但是启动不了如何解决
ModelScope镜像是指用于在ModelScope平台上创建和管理的容器镜像,这些镜像包含用于模型训练和推理的环境和依赖;本合集将说明如何使用ModelScope镜像以及管理镜像的技巧和注意事项。
561 0
|
机器学习/深度学习 人工智能 并行计算
《解锁 Eigen 库在 C++人工智能项目中的潜能与优化之道》
Eigen 库是 C++ 人工智能项目的得力助手,专注于线性代数运算,广泛应用于神经网络、数据预处理和优化算法等领域。其高效的内存布局、表达式模板和多线程并行计算等优化技巧,显著提升了项目性能,助力开发者构建高效的人工智能系统。
408 20
|
11月前
|
UED
利唐i人事、北森、Moka大比拼:谁才是HR数字化转型的最佳拍档?
在HR数字化转型中,选择合适的SaaS平台至关重要。利唐i人事、北森和Moka是市场上的热门选项。利唐i人事作为一站式HR管理平台,功能涵盖招聘、考勤、薪酬等多环节,操作便捷且性价比高,适合各规模企业;北森专注于人才管理,专业性强但学习成本高;Moka擅长招聘管理,但功能相对单一。综合来看,利唐i人事凭借全面性、友好体验和良好口碑,成为HR数字化转型的优选拍档。
|
12月前
|
云安全 弹性计算 监控
云产品安全体检报告
### 云产品安全体检报告简介 开发工程师在日常维护Web应用及数据库时,使用阿里云「安全中心-安全体检」功能,快速定位配置漏洞和合规风险。主要发现ECS实例SSH端口开放、OSS未开启访问日志记录、RAM用户未启用MFA等问题,并提出通过使用VPN或跳板机、限制IP范围等解决方案。报告还推荐了暴露面分析和合规基线检查项目,指出冗余安全组规则检测误报率高和漏洞扫描深度不足的优化点。对比AWS,阿里云在检测响应速度和中文界面友好性上有优势,但在自动化修复方面有待提升。建议增加风险修复模拟、日志服务集成等功能,优化用户体验。
|
11月前
|
SQL Unix API
夏令时的坑:你的数据库真的能正确处理时间跳变吗?
时区是地球上使用相同标准时间的区域。由于地球的自转,为了保证各地的时间与当地的日出日落相协调,全球划分为多个时区。
533 0
|
存储 前端开发 安全
JavaScript进阶 - 浏览器存储:localStorage, sessionStorage, cookies
【7月更文挑战第2天】探索Web存储:localStorage持久化,sessionStorage会话限定,cookies则伴随HTTP请求。了解它们的特性和限制,如localStorage的5MB容量限制、跨域问题,sessionStorage的生命周期,及cookies的安全与带宽消耗。使用时需权衡安全、效率与应用场景。示例代码展示存储与检索方法。
1157 2
|
数据采集 数据可视化 小程序
vue3+echarts可视化——记录我的2023编程之旅
vue3+echarts可视化——记录我的2023编程之旅
405 1
|
移动开发 资源调度 IDE
鸿蒙Taro实战:01-搭建开发环境
本文介绍了如何使用 Taro 4.x 框架搭建鸿蒙应用开发环境。主要内容包括:下载并配置 DevEco IDE,创建鸿蒙项目,安装 Taro 4.x,初始化 Taro 项目,配置鸿蒙插件和编译配置,修改 `package.json`,运行 Taro 和鸿蒙项目。通过本文,读者可以快速上手鸿蒙应用开发。
|
安全
BurpSuite进阶篇--自动识别Token值
BurpSuite进阶篇--自动识别Token值
1119 3
BurpSuite进阶篇--自动识别Token值
|
JavaScript
uniapp实现时间选择器
uniapp实现时间选择器