如何设计一个基础服务?看完这篇文章别再说不会、不懂、不知道

最新互联网大厂面试真题、Java程序员面试策略(面试前的准备、面试中的技巧)请移步GitHub


咱们知道,落地一个微服务其实并不困难,但要实现一个可以高度复用的共享服务并不容易,在落地过程当中,常常会有一系列的问题困扰着咱们。git

咱们事先对服务的边界没有进行很好的划分,结果在落地的过程当中,你们反复争论具体功能的归属。程序员

因为对业务的了解不够深刻,咱们要么设计不足,致使同一个服务有不少版本;要么服务过分设计,实现了一堆永远用不上的功能。github

对于落地一个共享服务来讲,服务边界的划分和功能的抽象设计是核心。服务边界肯定了这个服务应该“作什么”,抽象设计肯定了这个服务应该“怎么作”。面试

接下来,我就以一个实际的订单服务例子,为你详细讲解一下要如何重点解决这两个问题。这样你能够经过具体的案例,去深刻地理解如何落地共享服务,实现业务能力的复用。小程序

订单业务架构

不一样企业的订单业务是不同的,因此这里我先介绍下这个订单的业务场景。架构

这是个 O2O(Online To Offline,线上到线下)的交易业务,订单的来源有两个,一个是自有小程序或 App 过来的订单,还有一个是外卖平台过来的订单,而后这些线上的订单会同步到门店的收银系统进行接单和进一步处理。这里我放了一张订单的业务架构图,你能够到文稿中看下:异步

如何设计一个基础服务?看完这篇文章别再说不会、不懂、不知道

在这里,订单服务是和 4 个应用直接打交道的:ide

  • 小程序服务端调用订单服务落地自有线上订单;
  • 外卖同步程序接收三方外卖平台的订单,而后调用订单服务落地订单;
  • POS 同步程序经过订单服务拉取订单,并推送给商户内部的收银系统;
  • 最后还有一个订单管理后台,经过订单服务查询和修改订单。

OK,接下来,咱们就具体看下,如何从头开始落地这个订单服务。微服务

订单服务边界划分

首先,咱们要肯定这个服务的边界,这是进行服务内部设计的前提。划分边界时,你须要对相关的业务场景有充分了解,而且在必定程度上,可以预测潜在的需求。性能

根据业务场景的分析,这个订单服务须要负责三个方面的功能。

1. 基本信息管理

首先是订单基本信息管理,主要提供订单基础信息的增删改查功能,包括下单用户、下单商品、收货人、收货地址、收货时间、堂食或外卖、订单状态、取餐码等。

另外,你须要注意的是,这里有多个下单渠道,除了通用的订单信息,每一个渠道还有特定的渠道相关信息,好比堂食的订单要有取餐码、外卖的订单要有收货人和收货地址等等,这个都须要在咱们的数据模型里给出定义。

2. 订单优惠管理

而后是订单优惠管理功能,这对应的是订单的小票信息,从最开始的商品金额,到最后须要用户实际支付的金额,中间会有一系列的折扣和减免,这些都是属于订单信息的一部分。这些信息咱们须要展现给用户看,若是后续要进行订单成本的分摊,也须要用到它。

3. 订单生命周期管理

最后是订单的生命周期管理功能,主要负责管理订单的状态变化。咱们知道,从不一样下单渠道过来的订单,它的状态变化过程是不同的;不一样行业的订单,它的状态变化过程也是不一样的,因此订单服务的状态要作到通用,可以支持各类可能的状态定义和状态转换过程。这个也是订单服务设计的难点,我在后面会重点介绍。

好了,如今咱们已经给出了订单服务的功能。为了更好地定义边界,在实践中,你还须要澄清哪些功能不属于服务,这样能够避免后续的不少争论。因此在这里,我会进一步给出订单

服务不包括的功能,你在划分本身的服务边界时最好也可以明确给出。

第一,做为基础服务,订单服务不主动调用其余服务

好比说,你想了解订单的用户详情、商品详情等等,这应该由上层应用经过调用相应的服务来实现,而后和订单信息组装在一块儿,而不是在订单服务内部直接调用其余服务,不然会致使基础服务之间相互依赖,职责模糊。

若是说这个信息整合的场景很是通用,咱们能够建立一个在基础服务之上的聚合服务来实现,把订单信息、用户信息、商品信息整合在一块儿。

第二,订单服务不负责和第三方系统的集成

在这里,订单须要在咱们的订单服务和三方外卖平台,以及收银系统之间进行同步,这些同步功能都是针对第三方系统定制的,不具备通用性。而咱们的订单服务做为基础服务,须要具有通用性,所以这些和外部系统对接的功能不会在订单服务的内部实现,而是由额外的同步程序实现。

小提示:这些同步程序能够主动调用订单服务,而后再和第三方对接,若是想实时获取订单信息的变化,同步程序能够订阅订单服务的消息通知,第一时间了解订单变化。

第三,订单服务不提供优惠计算或成本分摊逻辑

订单服务不负责具体的优惠计算,只提供优惠结果的存储和查询,用于还原订单的费用组成。优惠的具体计算过程通常由专门的促销系统负责,成本的分摊通常由后续的财务系统负责。这个咱们在上一讲中已经说过,这里就不详细解释了。

最后,该服务不提供履单详情,不负责详细物流信息的存储

好比说,订单已经发送至上海、订单已经到达某某快递站等等这些信息,订单服务不负责提供这些详细信息,这些都是属于后续履单系统的职责。订单服务能够存储一些外部系统的单据号码,好比配送单号,这样能方便上层应用经过订单记录和配送系统进行关联,获取配送的详细信息。但订单服务只负责存储,不负责数据的进一步解释。

到这里,你能够看到,经过从正反两个方面说明订单服务的职责,咱们就获得了一个边界很清晰、职责很聚焦的订单服务边界,全部人对它的职责认识是一致的,尽量地避免了后续的争论。

订单服务内部设计

好,肯定了这个订单服务要作什么以后,接下来,咱们要解决的就是服务内部怎么作的问题了。

做为共享服务,咱们要保证订单服务功能上的通用性,就须要同时对内部数据模型和外部接口进行良好的抽象设计。

1. 订单状态通用化

对于数据模型来讲,订单要存储哪些信息,已经比较明确了,具体你能够看下这个图。

如何设计一个基础服务?看完这篇文章别再说不会、不懂、不知道

但对于如何管理订单的状态,状况就比较复杂了。

咱们知道,若是针对一个具体的项目,不管它的订单状态有多么的复杂,咱们均可以事先精确地定义出来。但不一样的行业甚至不一样的企业,他们对于订单状态管理都是不同的,订单服务做为一个共享服务,它必需要知足不一样项目的订单状态管理。因此对于如何解决这个问题,这里我有两个思路供你参考。

一个是开放订单状态定义

在这里,订单服务事先不限定订单有哪些状态,每一个项目均可以本身定义有哪些订单状态。服务的调用方能够在接口里传递任意的状态值;订单服务只负责保存状态数据,不负责解释具体的状态,也不负责任何的规则校验,它容许订单从一个状态转换为其余任意的状态。

这样的设计,在理论上能够知足各类状态的定义,知足各类状态之间的变化,但这样作其实有很大的问题。在这里,订单状态是彻底由外部负责管理的,上层应用的负担会很重,不但要负责定义有哪些状态,并且还要维护状态的转换规则,一不当心,订单可能从状态 A 非法地变成状态 B,致使业务出问题。

另一个是应用和服务共同管理状态

对于订单状态管理,应用和服务各自承担一部分职责,咱们看下具体如何实现。

咱们知道,不管订单的状态变化是如何的复杂,咱们老是能够定义一个订单有哪些基本的状态,包括这些基本状态之间是如何变化的。好比,订单一开始都是用户下单后待支付,支付完成后变成一个有效的订单,而后由商家进行接单,制做完成后进行发货配送等等,订单最终的状态要么是完成,要么是取消。

这些订单的基本状态,咱们称之为“主状态”,它们由订单服务负责定义,包括这些主状态之间的转换规则,好比已完成的订单不能变为已取消的订单。主状态的数量是比较有限的,状态之间的变化关系也是比较明确的。

这个主状态,咱们对大量现有的业务场景进行总结和抽象,是彻底能够定义出来的。在这个订单服务例子里,咱们定义了以下图所示的订单状态机,包括有哪些主状态,以及它们的转化关系。

如何设计一个基础服务?看完这篇文章别再说不会、不懂、不知道

订单除了“主状态”,还有“子状态”

好比,一个订单处于配送中,实际状况多是“仓库已发货”,“货已到配送站”,或者是“快递员正在送货中”等等,那么在这些状况中,订单的主状态都是“配送中”,它的子状态就是细化的这几种状况。子状态有哪些具体的取值,不一样的项目是不同的,这个就开放给各个应用来定义

因此,订单服务数据模型里有两个字段,其中的主状态由订单服务负责管理,包括主状态之间的变化规则;而子状态由上层应用来定义,管理子状态的变化规则,好比一个配送中的订单,它的子状态能够由“仓库已发货”,变为“快递员正在送货中”。

如今,咱们就能够总结下这两种订单状态的设计思路。

第一种方案,咱们不对订单状态进行管理,而是把订单的状态做为一个简单的属性存储,只支持订单状态简单的增删改查功能。咱们知道,订单状态是订单业务规则的核心体现,这样的订单服务是没有灵魂的,也失去了大部分业务复用的价值。

第二种方案,应用和服务共同管理订单的状态,订单服务抓大放小,经过主状态管理把控住了订单的核心业务规则,同时把子状态开放给应用进行管理,为具体的业务场景提供了灵活性。经过主状态和子状态的结合,订单服务就知足了不一样行业、不一样企业的订单状态管理需求。

2. 订单服务接口定义

说完了订单的状态管理,接下来,咱们从调用方怎么使用服务的角度,来看下订单服务外部接口是如何设计的。

外部系统和服务的交互有两种方式,包括同步的服务接口调用和异步的消息通知。

首先是同步的服务接口调用

为了方便外部调用方,咱们在服务接口命名时,必定要规范和统一,接口名字要可以望文生义,方便调用者快速找到所须要的接口。而且,咱们还要提供接口具体的请求和响应样例帮助说明。

具体的接口设计规范,我就不具体展开了,每一个公司都要有明确的规范要求,这里我就说下常见的查询接口是如何设计的。

一个订单有不少字段,每次调用方要查询的信息可能都不相同,不一样字段之间的组合方式有不少,咱们不可能一一支持。

那么,咱们怎么设计查询接口,来知足各类场景需求呢?通常来讲,咱们能够根据返回字段数量的不一样,提供三个不一样粒度的查询接口来知足多样化的需求。

第一个是粗粒度接口,只返回订单最基本的 7-8 个字段,好比订单编号、订单状态、订单金额、下单用户、下单时间等等;第二个是中粒度接口,返回订单比较经常使用的十几个字段;第三个是细粒度接口,返回订单的详细信息。

这样,不一样的查询需求,就能够根据要返回信息的详细程度,来选择合适的接口,经过这种方式,咱们兼顾了要定义的接口数量和查询的性能。

其次是异步的消息通知

订单服务除了提供同步的接口调用,还针对每次订单信息的变化,提供异步的消息通知,感兴趣的外部系统能够经过接收消息,第一时间感知订单的变化。

按照消息详细程度的不一样,订单消息能够分为“胖消息”和“瘦消息”。

顾名思义,胖消息包含了尽量多的字段,但传输效率低;瘦消息只包含最基本的字段,传输效率高。若是外部系统须要更多的信息,它们能够经过进一步调用订单服务的接口来获取。

在这个订单服务的例子里,若是是订单状态的变化,咱们只需提供订单号、变化先后的状态便可,所以主要以瘦消息为主;若是是新订单的建立,因为订单的字段比较多,因此使用胖消息,避免外部系统进一步调用订单服务接口。你在实践中,能够根据实际状况,在消息的数据量和消费者处理消息的复杂度之间作平衡。

前面咱们说了,订单服务不会主动调用外部系统的接口,这里的异步消息通知,就能够很好地保证外部系统及时感知订单的任何变化,同时避免订单服务和外部系统直接耦合。

总结

要想打造一个可高度复用的共享服务,你须要掌握最核心的两点:清晰的边界划分、内部的抽象设计

今天,我经过一个实际的订单服务例子,帮助你理解如何清晰地定义服务的边界,以及如何经过抽象设计保证服务的通用性。你在实践中,必定要深刻分析业务场景,识别真正的挑战在哪里,避免设计的简单化或过分复杂化。

相关文章
相关标签/搜索