服务模型

这里探讨服务模型,指的是面向当前应用外部客户的远程服务,在分层架构中,属于扮演了“北向网关”角色的基础设施层。

  • 客户位于当前应用之外,意味着通信模式需要采用分布式通信,传递的对象也需要视通信协议与框架而选择支持序列化和反序列化的对象协议,如 XML、JSON 或 ProtocolBuffer 等。
  • 远程服务的消费者包括所有需要发起跨进程调用的前端 UI、下游服务或其他第三方消费者。

服务模型驱动设计实际上是以外部远程服务为建模视角进行的设计过程。
基础设施层 —— 将这些组件都视为是网关(Gateway)
对下,例如,针对数据库、消息队列或硬件设备,可以认为是一个南向网关,对于当前限界上下文是一种输出的依赖(打通应用业务逻辑与外层的框架和驱动器,实现逻辑的适配以访问外部资源);
对上,例如,针对 Web 和 UI,可以认为是一个北向网关,对于当前限界上下文是一种输入的依赖(打通内部业务与外部资源的一个通道,对外的接口展现应用逻辑)。
数据模型驱动设计 —— 自下而上的设计过程;服务模型驱动设计 —— 是自上而下(自外而内,站在外部消费者的角度去思考服务)的设计过程。

服务分析模型

从服务视角建立服务分析模型:

  • 服务资源模型:将服务视为一种资源,即 REST 架构风格的设计模式。资源对象一般认为是基础设施层“北向网关”的内容,通常被定义为资源(Resource)类或控制器(Controller)类
  • 服务行为模型:将服务视为一种行为,体现了客户端与远程服务之间的行为协作。客户端需要什么样的操作,然后将该操作转换为职责,服务就是职责的履行者。关键在于如何确定消费请求,从而确定对应的服务契约。

    服务资源模型

    REST(REpresentational State Transfer,表述性状态迁移)架构风格起源于 Web 的架构体系,URI 和资源扮演了主要的角色。
    Web 经常被称作是“面向资源的”。一个资源可以是我们暴露给 Web 的任何东西,从一个文档或视频片段,到一个业务过程或设备。从消费者的观点看,资源可以是消费者能够与之交互以达成某种目标的任何东西。
    从识别资源开始,确认客户访问的资源。例如:

  • 查询我的订单:Order 就是客户要访问的资源

  • 执行一次统计分析:我们会习惯于从行为的角度去分析,例如将服务建模为 AnalysisService;但在 REST 架构风格的语境中,更应该识别出资源对象:执行一次统计分析,就是创建一个分析结果,由此获得资源对象 AnalysisResult。

    HATEOAS

    REST 架构风格的核心原则 —— HATEOAS(Hypermedia As The Engine Of Application State),超媒体作为应用状态的引擎
    引擎,驱动机动车向前走,或者向后走。
    客户端与服务器端的交互代表的是一种状态的迁移
    服务和客户端之间交换的并非应用的状态,而是资源状态的表述,这个表述通过链接指向下一个迁移的应用状态,链接的值就是另一个资源的 URI。例如:
    当订单(Order)资源被成功创建后,假设订单的订单号为 1111,那么返回的资源表述中,就应该包含支付(Payment)资源的 URI,即 http://practiceddd.com/payments/orders/1111
    一个内嵌了链接的资源就是一个超媒体(Hypermedia),通过它可以改变应用的状态,这正是 HATEOAS 的含义。
    HATEOAS 可以通过应用状态的迁移来表达一个业务流程 —— 封装状态迁移规则 —— 松耦合服务协议。
    服务模型应包含资源以及超媒体,如下图所示:
    image.png
    重点把握业务流程中资源状态的变化。
    状态的变更是针对资源的一个操作(Action)触发的,在满足某个业务规则之后,当前资源就会因为状态变更而链接到另外一个资源
    为了体现资源状态的变化,以及资源与操作及链接资源之间的关系,我们可以针对业务流程绘制状态机。

    状态机

    咖啡店下单。
    顾客:
    image.png
    咖啡师:
    image.png
    状态机里的每一个状态迁移,都代表着与 Web 资源的一次交互(操作资源)。
    状态图中的状态与触发状态迁移的操作可以驱动出资源的定义:

  • Order Placed => Orders

  • Order Paid => Payments
  • Drink Made => Drinks

image.png

建立服务资源模型

一个 REST 服务实际上是对客户端与资源之间交互协作的抽象,它利用了关注点分离原则分离了资源、访问资源的动作及表示资源的形式:
image.png

  • 资源作为名词,是“到一组实体的概念上的映射”;
  • 动词是在资源上执行的动作;
  • 表示形式则用来“捕获资源的当前或预期的状态,并在组件之间传递这种表示形式”。

Roy Fielding 推导 REST 风格时的一种架构约束 —— 为了保证客户端与服务器端之间的松散耦合,对动词提炼了统一的接口。
使 REST 架构风格区别于其他基于网络的架构风格的核心特征是,它强调组件之间要有一个统一的接口。通过在组件接口上应用通用性的软件工程原则,整体的系统架构得到了简化,交互的可见性也得到了改善。实现与它们所提供的服务是解耦的,这促进了独立的可进化性。然而,付出的代价是,统一接口降低了效率,因为信息都使用标准化的形式来转移,而不能使用特定于应用的需求的形式。 —— Roy Fielding

HTTP

REST 采用标准的 HTTP 协议语义来描述客户端和服务器端的交互,即 GET、POST、PUT、DELETE、PATCH、HEAD、OPTION、TRACE 八种不同类型的 HTTP 动词。

  • POST、PUT、DELETE 与 PATCH 对资源的操作都会导致资源状态的迁移;
  • GET、HEAD、OPTION 和 TRACE 用于查看资源的当前状态,并不会引起状态迁移。

统一接口的架构约束 —— 针对资源进行操作的服务行为都是统一的,这就抹去了服务的业务语义。
区分服务行为 —— 结合 HTTP 动词、由资源组成的 URI 及请求和响应信息来共同分辨,例如:

  • URI 为 https://cafe.org/orders/,服务 A 的 HTTP 动词是 GET(查询订单),服务 B 的 HTTP 动词是 POST(创建订单)。
  • URI 为 https://cafe.org/orders/12345,且 HTTP 动词皆为 PUT,需要再结合服务的客户端请求或响应。服务 C 是更新订单: {
    “additions”: “shot”,
    “cost”: 28.00
    }服务 D 是确认订单: {
    “status”: “Confirmed”
    }

设计 REST 服务的 API 自有其规范,例如:
image.png
组成 REST 服务 API 的请求和响应可认为是消息对象,请求消息分为命令消息和查询消息。

可视化服务资源模型

用圆形代表服务,而用包含了三角形标记的圆形代表 REST 服务资源:
image.png
REST 风格的设计约束,服务 API 的主要构成:资源、HTTP 动词与 URI。
服务模型驱动设计关注由服务资源开始由外向内的设计驱动力,即将远程服务作为设计的起点,逐步从接口到实现向内层层推进。
因此,在服务模型驱动设计过程中,需要明确在接口内部的实现中需要哪些对象进行协作,以支持远程服务提供给客户端的功能。

服务行为模型

为了服务消费者(Service Consumer)能够发现服务,还需要提供者(Service Provider)发布已经公开的服务,因此需要引入服务注册(Service Registry),从而满足 SOA 的概念模型:

以服务行为来驱动服务的定义,需要从消费者与提供者之间的协作关系来确定服务接口。消费者发起服务请求,提供者履行职责并返回结果,这就构成了所谓的“服务契约(Service Contract)”。契约是义务与权利的一种规范。
对于一个大型系统来说,光保证它的各组成部分的质量是不够的。最有价值的是确保在任何两个组成部分的交接处设计明晰的彼此义务和权利规范,即所谓契约。 —— Bertrand Meyer
在面向外部远程服务进行设计时,契约的设计尤为重要,它直接影响了整个系统的稳定性与性能。

Contract

引入商业中的契约 —— 对消费者和提供者两方的协作进行约束。

  • 作为请求方的消费者,需要定义发起请求的必要条件,这就是服务行为的输入参数,在契约式设计中被称之为前置条件(pre-condition)
  • 作为响应方的提供者,需要阐明服务必须对消费者做出保证的条件,在契约式设计中被称之为后置条件(post-condition)

前置条件是消费者的义务,同时就是提供者的权利;后置条件是提供者的义务,同时就是消费者的权利。
例如转账业务功能,契约式服务接口:
public interface TransferService {
TransferResult transfer(SourceAccount from, DestinationAccount to, Money amount);
}
契约的前置条件为源账户、目标账户和转账金额。

  • 服务消费者发起转账请求时,义务是保证三个信息合法,否则服务提供者就有权利拒绝请求。
  • 服务提供者响应了转账请求,义务是返回转账操作是否成功的结果,否则可能导致消费者会为这笔交易感到惴惴不安,甚而会因为缺乏足够的返回信息而发起额外的服务,例如再次发起转账请求,又或者要求查询交易历史记录。契约关系遭到破坏

    意图导向编程

    “顾客是上帝” —— 在权利上适当向消费者倾斜,努力让消费者更加舒适地调用服务。
    “最小知识法则” —— 让消费者对提供者尽可能少地了解,从而消除掉用户一切不需要知道的复杂度。
    当服务行为设计的驱动者转向服务消费者时,设计思路就可以按照“意图导向编程(Programming by Intention)”的设计轨迹。
    先假设当前这个对象中,已经有了一个理想方法,它可以准确无误地完成你想做的事情,而不是直接盯着每一点要求来编写代码。先问问自己:“假如这个理想的方法已经存在,它应该具有什么样的输入参数,返回什么值?还有,对我来说,什么样的名字最符合它的意义?” —— Alan Shalloway 《敏捷技能修炼》
    在定义服务行为模型时,也可以尝试采用这种方式进行思考:

  • 假如服务行为已经存在,它的前置条件与后置条件应该是什么?

  • 服务消费者应该承担的最小义务包括哪些?
  • 而它又应该享有什么样的权利?
  • 该用什么样的名字才能表达服务行为的价值?

在识别业务场景时,需要保证业务场景的合理粒度。划分场景粒度时可以参考如下两个特征:

  • 为消费者提供了业务价值:服务对消费者有价值,就是能解决消费者的问题,达成消费者的目标,例如下订单对于买家就是有价值的,而验证订单有效性对于买家就没有价值,应作为下订单内部的子功能。
  • 具有完整时序的线上操作过程,不能中断:当消费者发起对服务的请求时,执行过程具有明显的时序性,如果中间存在时序上的中断,就应该划分为两个不同的场景。例如投保服务,在录入投保信息后,会发起一个工作流,由核保人对录入的投保信息进行审核。由于核保人是一个人工处理的过程,就会导致录入投保信息到审核之间存在一个明显的时序中断。这时就应该分为两个不同的场景,对应两个不同的服务——投保服务与核保服务。

在确定服务契约时,还需要考虑作为前置条件和后置条件的输入参数与返回值应该定义成什么样的类型?我们需要考虑两方面的因素:

  • 参数与返回值的序列化:通常需要定义 POJO 对象,通过 getter 和 setter 来表示属性。正因为此,一般不建议将服务参数与返回值定义为接口,因为在网络通信的背景下,对数据模型的抽象并无意义。
  • 是否需要解除客户端对服务接口定义类型的依赖:RPC 方式不同于 REST 服务,服务消费者在调用远程服务时,需要在客户端获得远程服务的一个引用(相当于远程代理)。如果服务参数与返回值为自定义类型,就需要为客户端提供定义了这些类型的 JAR 包。一种方式是将服务契约的定义与实现分开,使得服务契约的 JAR 包可以单独部署在客户端;另一种方式则是采用泛化调用,即直接使用 Map 对象而非 POJO 对象来装配对象;但是,采用泛化调用会牺牲自定义类型的封装性,无法表达业务含义

    CQS

    与服务资源模型一样,在确定服务行为时,需要先确定该行为的类别究竟是命令(Command)还是查询(Query),并遵循 Meyer 提出的命令查询分离(Command-Query Separation,CQS)原则。命令操作和查询操作具有迥然不同的特质。

  • 命令操作通常会带来副作用,因此它往往意味着状态的修改,可能不符合操作的安全性与幂等性。

  • 纯粹的查询操作则不然,因为它天然就是安全和幂等的。

一旦将命令与查询分离,就意味着服务行为会按照各自的契约行事,只要明确了契约的前置条件,在调用时就可以推测程序的状态。如果二者合二为一,就无法分辨状态的变更究竟是因为调用端调用还是服务内部自身引起,从而带来难以预知的结果。
远程服务的行为更需要遵循命令查询分离原则。因为远程服务采用的网络通信是不稳定的,这就增加了对结果预判的复杂性。
服务行为的失败或错误并不一定意味着服务行为执行失败,有可能是返回响应消息时的通信故障。由于调用者不知道服务失败的具体原因,为了保证服务行为执行成功,就需要发起对服务的重试(Retry)。为避免重试带来不可预知的后果,通常要求服务行为是幂等的。倘若遵循命令查询分离原则来设计服务行为,就只需要针对命令操作考虑幂等设计即可。
例如转账:
public interface TransferService {
TransferResult transfer(Source sourceAccount, Destination destAccount, Money amount, String transactionId);
}
transactionId 作为交易 ID 可以唯一标识每一次发起的转账交易。当服务提供者接收到转账请求时,首先通过 transactionId 到交易明细表中查询该 ID 是否已经存在,若存在,则认为该交易已经执行,就可以忽略本次请求,实现服务的幂等性。显然,服务的幂等性在一定程度上影响了服务 API 的设计。

同步异步

在定义服务的 API 时,还应该考虑该服务行为究竟是同步操作还是异步操作。
例如,Dubbo 框架就允许我们在服务消费者的配置中,将 async 属性设置为 true,自动实现消费者对服务发起的异步调用,接口并不需要做任何调整。
RestaurantService service = (RestaurantService)context.getBean(“restaurantService”);
Future rFuture = RpcContext.getContext().getFuture();
Restaurant restaurant = rFuture.get();

区别

服务资源模型与服务行为模型的区分在于建模的驱动力。服务资源模型是名词驱动的,首先得到的是资源;服务行为模型是动词驱动的,首先得到的是服务行为。仍然采用 Thomas Erl 建议的服务契约模型对 Restaurant 服务进行建模,二者在图示上的区别如下所示:
image.png
显然,服务资源模型提供的服务行为无法做到服务行为模型那样通过方法名直接表达“获得最近餐馆”的业务语义,但它却通过 URI 保证了接口的统一性,满足了服务版本的可扩展性
服务契约的定义与设计还需要遵循远程服务的设计原则,例如考虑服务的版本、兼容性、序列化、异常等。同时,我们也可以结合 SOA 与微服务的服务设计原则来指导服务设计,例如保证服务契约的标准化,满足服务松耦合原则、抽象原则、可重用原则、自治原则等。

服务设计模型

服务资源模型、服务行为模型 —— 服务契约 —— 面向外部调用者的一个门面(Facade)。
基于分层架构的单一职责原则与关注点分离原则,我们应该尽量保证服务契约的职责单一,即接收调用者发送的请求,并在处理完业务逻辑之后返回响应消息。
远程服务中真正的业务逻辑则应该委派给领域层。因此,一旦确定了服务契约,就应该从实现服务的角度向内推进。这种推进的过程可以认为是服务模型驱动设计的设计活动。

ICONIX

ICONIX 裁取了用例驱动设计与 UML 的核心子集,力求以最少步骤实现从需求、分析、设计到最后的代码实现。
ICONIX是尽早进入编码阶段,缩短分析设计周期的软件开发方法。
合理的简化统一过程(RUP), 基于极限编程(XP)和 敏捷软件开发的思想,ICONIX过程与 UML 和 RUP 相比,是轻量级的过程。
通过 GUI 原型(Prototype)与用例分析(Use Case Model)获取需求,然后通过绘制健壮性图(Robustness Diagram)进行健壮性分析。健壮性图包括:

  • 边界对象(Boundary Objects)
  • 控制对象(Control Objects)
  • 实体对象(Entity Objects)
  1. 通过这种可视化图例与用例文本相对应来对需求进行健康检查,这个过程是领域分析和建模的过程(动态过程):

通过 ICONIX 健壮性分析,发现参与业务协作的对象
通过时序图表现对象之间的协作关系,确定职责的分配,定义对象的行为

  1. 通过这个动态分析的过程又可以帮助我们建立静态的领域类模型,最终编写单元测试用例和实现代码。image.png

image.pngimage.png
ICONIX 是一种领域建模的方法,其中健壮性分析是连接需求用例与领域模型的重要工具,并由此获得健壮性图作为初步的分析模型。
在服务模型驱动设计中,健壮性图定义的边界对象、控制对象与实体对象恰好可以对应为远程服务、应用服务与领域模型对象,这三种类型的对象从外向内参与了用例代表的业务场景。因而,我们可以参考 ICONIX 引入这三种对象类型的图示:
image.png

对象之间的协作关系

健壮性分析规定了边界对象、控制对象与实体对象之间协作的约束关系,这种约束关系在服务模型中同样存在。例如,以下对象类型之间的协作关系是允许的:
image.png
协作关系:

  • 服务消费者只需要了解提供服务能力的远程服务对象,即前面提及的服务资源或服务提供者。
  • 远程服务接收到服务消费者的请求后,在完成通信、路由和消息验证与转换等职责后,可以将请求委派给应用服务。
  • 应用服务在接收到远程服务请求后,可以结合业务场景调用领域对象对业务逻辑进行编制,并完成一些与业务无直接关系的通用工作,如事务、认证授权、异常处理等。
  • 如果一个应用服务提供的功能无法满足远程服务的业务场景需求,可以引入应用服务之间的协作。

不允许:
image.png

  • 不允许服务消费者直接与应用服务和领域对象协作,事实上,应用服务与领域对象自身也不提供远程通信和消息序列化的能力。
  • 远程服务之间也不支持直接协作,既可以隔离因为上游远程服务变化带来的影响,又可以避免在远程服务中混入太多的实现逻辑。
  • 同理,也不允许远程服务与领域对象直接协作,原则上,需要将领域对象转换为服务对象。

    订阅课程用例

    用例描述:
    用例:订阅课程
    参与者:学生(注册会员)
    前置条件
    订阅者已经登录
    事件流
    基本流
    选定需要订阅的课程
    选定具体的课程排期
    确认该排期的课程可以订阅
    确认该排期的课程未被同一学生订阅
    订阅课程
    成功订阅课程后,删除该学生期望列表中的课程
    发送订阅成功消息给管理员与订阅学生
    备选流
    当该排期的课程无法订阅时,给出提示信息
    当该排期的课程已被该学生订阅时,给出提示信息
    若订阅失败,给出失败原因
    后置条件:
    课程被成功订阅,并生成学生的培训记录
    期望列表中若存在该课程,则该课程被成功移除
    管理员与订阅学生收到订阅成功的通知
    从服务消费者以及作为边界对象的远程服务 CourseProvider(如果采用 REST 风格,则为 CourseResource)开始进行健壮性分析。这里的服务消费者是前端 UI 的课程订阅页面,它会向 CourseProvider 服务发送请求。根据健壮性分析的约束规则,远程服务对象只能与应用服务协作。由于课程订阅的逻辑与课程 Course 有关,因此需要定义 CourseAppService 应用服务与其协作。
    应用服务承担了多个领域对象之间的协作,进行健壮性分析时,可以通过识别用例描述中的名词帮助寻找到对应的领域对象,包括课程 Course、排期 Calendar、期望列表 WishList,以及订阅成功后的培训记录 Training。它们之间的关系如健壮图所示:
    image.png
    不足:没有清晰地说明领域对象 SubscriptionValidator 与 TrainingRepository 之间的差别,以及它们是如何协作的。
    引入时序图来弥补这些信息的缺失,展现用例中的事件流,确认对象之间的协作关系:
    image.png

    服务模型与领域模型

    从确定了服务分析模型之后,服务模型驱动设计以识别出来的服务契约对象为设计的起点,开始了由外向内的模型设计过程。
    在引入 ICONIX 进行健壮性分析及运用时序图表达对象之间的协作关系时,除了设计的驱动力有所不同之外,设计的方法、原则与目标都是在针对领域进行建模:

  • 在设计阶段,服务模型驱动设计与领域模型驱动设计就开始走向了过程与方法的合并;

  • 服务模型驱动设计成为了领域模型驱动设计的一个有力补充,它提供了从外部观察远程服务的视角。

这是一种契约式设计与意图导向编程融合的设计思想,而 ICONIX 的引入则让我们有章可循,整个模型驱动设计的轨迹变得更加的清晰,每一个设计步骤都有着明确的方法和交付目标。

小结

综合来看,无论是面向服务行为,还是面向服务资源,服务模型驱动设计的过程都是由外向内对业务知识的逐步细化与分解。
设计的起点为远程服务,但随着设计的层层推进,最终还是会进入到领域层,建立领域模型来支撑服务功能。
进入服务设计活动后,ICONIX 方法开始发挥强大威力,指导着由边界对象向内驱动的领域模型设计。
ICONIX 的健壮性分析可以帮助我们:

  • 得到初步的领域分析模型,
  • 再通过时序图建立分析模型中各种对象之间的协作关系,
  • 然后将其转换为类图表达的设计模型(考虑领域逻辑,此时的服务设计模型属于领域模型的范畴)。
  • 最后,通过编程语言结合时序图和类图进行编码实现,并编写单元测试用例保证代码的正确性与可读性。

    • 实现代码与单元测试既包含服务实现模型的一部分,即服务契约的实现,又包含领域实现模型。

      服务实现模型

      服务模型驱动设计的实现模型其主体是领域实现模型,还有一部分是服务契约的实现,包括服务资源模型与服务行为模型的实现。

      服务契约

      服务契约定义的原则与规范:
  • 服务资源契约需要遵循 REST 风格的 API 设计规范;

  • 服务行为契约则需要从契约精神中的权利与义务来思考服务的接口定义。

在实现这些服务契约时,我们需要遵循 Postel 原则(Postel’s Law),即“对接收的内容要宽容,对发送的内容要严格”。
例如:
{
“id”: “d290f1ee-6c54-4b01-90e6-d701748f0851”,
“name”: “Widget Adapter”,
“releaseDate”: “2016-08-29T09:12:33.001Z”,
“manufacturer”: {
“name”: “ACME Corporation”,
“homePage”: “https://www.acme-corp.com“,
“phone”: “408-867-5309”
}
}

  • 服务契约的前置条件不能规定太死板,要允许接收与标准不一致的输入,如输入参数的顺序、是否必填、非法请求 404 响应等。
  • 服务契约的实现在处理完服务功能后,返回的响应消息严格按照标准定义,如 HTTP 状态码、状态迁移下一资源链接、JSON 格式正确属性内容等。

    服务模型驱动设计的过程

    服务分析模型起点 —— 设计服务契约的驱动力 —— 服务契约规范不同。

  • 以资源为中心的服务契约与领域模型之间存在一定的对应关系,因为它们都是表达领域概念的名词描述;

  • 以行为为中心的服务契约更倾向于权利与义务的分配,从而形成消费者与服务之间的良好协作,但最终还是要落实到远程服务的主体之上,该主体同样表达了领域概念。

因此,服务模型驱动设计获得的服务分析模型可以作为领域驱动设计的重要输入。

服务模型驱动设计

ICONIX 方法通过建立以远程服务、应用服务与领域对象之间的健壮性图,顺理成章地推导出这些角色对象参与协作的时序图,从而奠定了设计模型中重要的领域概念与领域行为。
也可以直接沿用领域驱动设计的理念与模式,这时就自然而然地从服务模型驱动设计转换到领域模型驱动设计。
至于服务实现模型中对服务契约的定义,实际上弥补了领域驱动设计中关于如何实现开放主机服务(OHS)的这部分知识。
因此,一个典型的服务模型驱动设计过程如下图所示:
image.png
服务模型驱动设计还可以与领域驱动设计的战略设计阶段结合起来,例如限界上下文提供的开放主机服务实则就是一个开放的微服务。既然我们已经确定了限界上下文的边界,自然应该识别出属于该边界范围内的所有微服务,并确定微服务的 APIs 与协作方式,进而推进到领域层中,确定当前限界上下文中领域对象的协作时序,以及可能参与协作的第三方服务。

微服务设计

Chris Richardson 将微服务的设计分为三个步骤:

  • 识别系统操作(System Operations):从业务场景角度识别客户端的调用需求,并确定系统与客户端协作的方式,例如确定是命令(Command)还是查询(Query)。
  • 识别服务:通过系统操作明确提供业务能力的职责,识别出应该履行职责的服务。
  • 定义服务的 APIs 和协作方式:确定对外公开的接口,同时确定内部各个服务之间是如何协作的。

Chris Richardson 以一个虚拟 FTGO 系统的订单场景为例展现了整个设计过程:
image.png
在识别服务的过程中,Chris 建议使用名词动词法对业务场景进行分析,将用户故事中的名词映射为领域对象,建立高层领域模型,然后从调用者角度驱动出服务能够处理的请求获得系统操作,再根据系统操作的两种类型(命令与查询)确定操作的职责。
Chris 认为,系统操作是一种抽象,它与具体的分布式通信方式无关。在识别微服务时,应该忘记服务的实现机制,仅仅从交互的业务场景判断参与者、协作方式、API 及其需要履行的职责。这种方式其实就是建立在服务行为模型之上的服务模型驱动设计过程。