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

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

在设计服务中心的过程中,对服务中心内服务接口和数据模型的设计非常重要,良好的设计原则和方法可以最大化地保障服务中心的可扩展性。


强烈建议读者学习著名建模专家Eric Evans最具影响力的著作Domain-Driven Design-Tackling Complexity in the Heart of Software(《领域驱动设计:软件核心复杂性应对之道》)以及Thomas Erl的著作SOA principles of Service Design(《SOA服务设计原则》),在实际的服务中心设计过程中,大多数情况下都可参考这两本书中的原则和方法。本书的目的是从全局的视野介绍如何更好地进行数字化企业的建设,所以不会深入探讨这个技术。


1)Façade(外观)模式。

接下来在介绍服务化设计原则时,会多次出现Facade模式。

外观模式的使用原理如图4-11所示。



image.png


外观模式的优点如下:

松散耦合:外观模式使得前台应用与中台服务中心可以进行松散耦合,让服务中心内部的模块能更容易地扩展和维护。

简单易用:外观模式让服务中心的服务更加易用,前台应用不再需要了解服务中心内部的实现,也不需要跟服务中心内部众多的功能模块进行交互,只需跟外观类交互就可以了。

更好地划分访问层次:通过合理使用外观模式,可以更好地划分访问的层次,有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到外观中,这样既方便客户端使用,也很好地隐藏了内部的细节。


2)DTO的使用。

DTO可以将服务中心复杂或易变的数据对象对前台应用屏蔽,让前台具备更好的稳定性。DTO是系统分层设计和服务化架构中经常使用的技术,概念本身也容易理解,如图4-12所示。



image.png


业务中台架构的核心是各个业务领域的设计建模以及服务接口的设计,笔者结合业界优秀的设计原则以及自己的实践,将服务接口典型的设计原则整理如下,供各位读者参考。


(1)契约先行

服务间的交互类似于不同组织之间的合作,按照正常逻辑,两个组织之间合作的首要任务就是先签订明确的契约,详细规定双方合作的内容、合作的形式,等等,这样才能对双方形成强有力的约束和保障,同时工作也能够并行不悖,不用相互等待。因此服务化架构中最佳的实践方式也是服务契约先行,即先做服务契约的设计。在进行服务接口设计时需要有业务、产品和技术等不同方面的人员共同参与,并定义出相应的契约,然后再实现具体的代码。


在实际的中台架构设计阶段,当在企业不同的业务部门收集到业务需求,形成产品需求调研文档后,需要从全局的视角对服务中心的服务接口进行统筹设计,即不是按照单一应用场景,如仅从电商或仅从CRM系统的角度,进行服务接口设计。虽然这些前台系统都是按照步骤逐步建设起来的,但服务中心的接口设计首先需要在全局的业务视角下进行规划和设计,有了清晰的接口设计,前台和服务中心就有了清晰而相对稳定的交互边界,就能大大降低后期实现和运营期的协作成本,总体效率更高。


由于服务的用户范围很广,在服务契约公开发布之后就要保证良好的稳定性,不能随便重构,即使升级也要考虑尽可能地向下兼容性。


(2)服务功能内聚

服务功能内聚几乎是任何服务化设计中最基本的要求。要创建功能内聚的服务接口,应该使功能相关的一组操作聚合到一起,同时必须将可能影响到业务正确性的逻辑在对应的服务中提供,而不能依赖服务调用方遵循正确逻辑。比如,用户注册的服务,其中包含了对于用户邮箱格式、用户名称以及密码强度的校验逻辑,虽然这些逻辑在前台应用的Web页面或者App中都进行了相关的校验,但前台应用最终调用用户中心的用户注册服务时,依然要在该服务中实现对这些用户属性的校验工作,而不能寄希望于前台应用做这些校验工作,这样才能避免因为前台应用遗漏校验而导致不合规则的用户能成功进行注册。一个典型的服务功能内聚的例子如

图4-13所示。


image.png


(3)服务粗粒度

服务的使用者对特定业务流程的了解一般比不上服务中心内部的人,所以服务的接口设计通常需要粗粒度,一个操作有可能对应一个完整的业务用例或者业务流程,这样既能减少远程调用次数,又能降低学习成本和耦合度。

例如,文档服务要给前台应用提供批量删除文章的支持,已有接口中提供deleteArticle(long id)方法,可以供用户自己做循环调用来实现批量删除文章的目的。此时,服务中心最好提供deleteArticles(Set<Long> ids)方法供前台应用调用,将N次远程调用减少为一次。

再例如,用户下订单的用例,要有一系列操作:

addItem(累计商品)→addTax(计算税)→calculateTotalPrice(计算总价)→ placeOrder (创建订单)

交易中心当然可以将这些服务以单个接口方法的方式提供给前台应用,这样不仅需要前台应用对于订单创建流程和逻辑有更高的要求,而且会增加出现服务调用错误的概率,最好封装一个粗粒度的方法供用户做一次性远程调用,同时也隐藏了内部业务的很多复杂性。服务调用方也从依赖4个方法变成了依赖1个方法,从而大大降低了程序耦合度。

另外,从服务和接口方法的数量角度来看,服务将通常作为测试和发布的单位,如果粒度过粗,将大量操作分组到单个服务中,则可能增加单个服务的使用者,这样就为服务使用者快速找到正确的操作带来了挑战,从而导致服务使用体验不佳。要更改服务,势必需要重新发布整个服务,从而影响较多使用者。

所以要避免服务粒度的两个极端:

提供仅有几个方法的很多服务。

数十或数百个操作均集中在几个服务中。

应考虑多个因素,如可维护性、可操作性和易用性,并进行折中。

还有一种划分服务粒度的方法是,创建反映业务对象生命周期的状态的服务接口。例如,费用申领中,每笔费用申领的生命周期都包含四个状态,如图4-14所示。



image.png


由于业务对象状态常常能同时反映业务和技术两方面的内容,因此完全可以将ExpenseClaimService(费用申领服务)拆分为适应每个状态的多个服务:ClaimEntryService(费用构建服务)、ClaimApprovalService(费用审批服务)、ClaimPaymentService(费用支付服务),得到如下所示的服务代码:

ClaimEntryService {

   createClaim(String userId);

   ClaimItemDetails[] getClaimItems(int );

   ClaimErrors[] validateClaim(int claimId);

   void removeClaimItem(int claimId, int itemId);

   int addClaimItem(int claimId, ClaimItemDetails details)

   int submitClaim(int claimId);

}


ClaimApprovalService {

   int approveClaimItem(int claimId, int itemId, String comment);

   void approveClaim(claimId)

   void returnClaim(claimId)

   ClaimItemDetails[] getClaimItems(int );

   ClaimErrors[] validateClaim(int claimId);

}


ClaimPaymentService {

   void payClaim(int claimId);

}

通过这种方式,能更方便地理解每个服务。而且,将接口这样划分非常适合服务的开发、部署、维护和使用方式。总结来说,通过将划分逻辑放在对象生命周期上,我们就可以建立具有恰当粒度的服务。


(4)消除冗余数据

由于服务的远程调用需要网络开销,特别是在并发量很大的场景下,这样的开销就不是一个可以忽略的因素了。所以在服务的输入参数和返回结果中,要尽量避免携带当前业务场景不需要的冗余字段,来减少序列化和传输的开销。同时,去掉冗余字段也可以简化接口,避免给外部用户带来不必要的困惑。

比如“文档服务”中有个返回文章列表的方法:

List<Article> getArticles(...)

如果业务需求仅仅是要列出文章的标题,那么在返回的文章对象中就要避免携带它的内容等字段。

这里有一个经典解决方案,就是引入前面提到的DTO模式,专门针对前台业务应用定制要传输的数据字段,这里需要添加一个AriticleSummary(文章概要)的额外数据传输对象:

List<ArticleSummary> getArticleSummaries(...)

ArticleSummary能很好地避免服务中心与前台应用间的冗余数据传输。


(5)通用契约

由于服务不假设用户的范围,所以一般要支持不同语言和平台的客户端。但各种语言和平台在功能丰富性上有很大差异,这就决定了服务契约必须取常见语言、平台以及序列化方式的最大公约数,才能保证服务具备广泛兼容性。因此,服务契约中不能有某些语言才具备的高级特性,参数和返回值也必须是被广泛支持的较简单的数据类型(比如不能有对象循环引用)。

例如,原有对象模型如下:

Class Foo {

   private Pattern regex;

}

其中,Pattern是Java特有的预编译,可序列化正则表达式(可提高性能),但在没有特定框架支持的情况下,其他开发语言可能识别不了,所以最好采用DTO的方式改成常用的数据类型,如下所示:

Class FooDto {

   private String regex;

}

相关文章
|
6月前
|
存储 自然语言处理 前端开发
百亿级知识库解决方案:从零带你构建高并发RAG架构(附实践代码)
本文详解构建高效RAG系统的关键技术,涵盖基础架构、高级查询转换、智能路由、索引优化、噪声控制与端到端评估,助你打造稳定、精准的检索增强生成系统。
1414 2
|
10月前
|
人工智能 Rust 自然语言处理
37.1K star!AI模型全能工具箱,这个开源项目让智能体开发更简单!
"Awesome MCP Servers 是当前最全面的模型上下文协议服务器集合,为AI开发者提供开箱即用的工具链支持。通过标准化协议实现AI模型与各类资源的无缝对接,堪称智能体开发的瑞士军刀!"
559 7
|
分布式计算 Kubernetes Hadoop
大数据-82 Spark 集群模式启动、集群架构、集群管理器 Spark的HelloWorld + Hadoop + HDFS
大数据-82 Spark 集群模式启动、集群架构、集群管理器 Spark的HelloWorld + Hadoop + HDFS
641 6
|
机器学习/深度学习 TensorFlow 算法框架/工具
深度学习中的正则化技术及其对模型性能的影响
【8月更文挑战第26天】本文将深入探讨深度学习领域中的正则化技术,并分析其如何塑造模型性能。我们将从理论出发,逐步引导读者理解不同正则化方法背后的原理,并通过实例展示它们在实际问题中的应用效果。文章旨在启发读者思考如何在特定的深度学习任务中选择合适的正则化策略,以优化模型的表现。
|
供应链 搜索推荐 API
深度解析1688 API对电商的影响与实战应用
在全球电子商务迅猛发展的背景下,1688作为知名的B2B电商平台,为中小企业提供商品批发、分销、供应链管理等一站式服务,并通过开放的API接口,为开发者和电商企业提供数据资源和功能支持。本文将深入解析1688 API的功能(如商品搜索、详情、订单管理等)、应用场景(如商品展示、搜索优化、交易管理和用户行为分析)、收益分析(如流量增长、销售提升、库存优化和成本降低)及实际案例,帮助电商从业者提升运营效率和商业收益。
533 20
|
算法 安全 定位技术
地图一共有多少个坐标系?有什么区别?如何选择?
地图一共有多少个坐标系?有什么区别?如何选择?
2292 11
|
人工智能 Cloud Native 大数据
构建高性能云原生大数据处理平台:融合人工智能优化数据分析流程
构建高性能云原生大数据处理平台:融合人工智能优化数据分析流程
982 0
|
Nacos 数据中心 Docker
Docker 部署 Nacos 集群
Docker 部署 Nacos 集群
|
Linux Shell API
深入探索 `dbus-run-session`:Linux下的D-Bus会话管理工具
`dbus-run-session`是Linux下管理D-Bus会话的工具,它确保桌面环境和应用间的通信。当登录图形桌面时,D-Bus会话自动创建,支持应用和服务间的消息传递。通常不需要直接使用,但在特定情况下,可以通过`dbus-run-session bash`启动shell会话运行D-Bus相关命令。注意避免重复启动会话,勿以root运行,确保环境变量正确,可使用`--verbose`选项进行调试。了解其工作原理有助于解决D-Bus相关问题。
|
NoSQL Nacos Redis
Seata 配置
Seata
990 2