这个标题其实不是非常准确,应该说是为什么状态机在异步分布式微服务实践中变得更重要。因为如果你的微服务还只是将原来的本地方法调用替换为同步的API调用,那么恭喜你,你将原来的monolithic变成了distributed
monolithic,并成功地给自己找了很多的麻烦。
用self-contained service里提到的例子,如果所有的API调用都是同步的,那么你在开发的时候,你的关注重点会是API调用,异常处理,但是不会想到Order的状态问题。其实不是因为程序员没有这个思维,而是因为整个transaction在这个模式下是一个整体,人们的思维会自然地将其看做一个黑盒子,从这个角度考量整个API的成功与否,以及里面的实现细节(指令式思维),不会考虑Order的状态转换,考虑也没有用,因为这些转换是实现细节,是在黑盒子里面发生的,用户不可知。用户感知的仅仅是这个API是否成功,如果成功,那么这个Order自然应该是一个Created
的状态,如果失败,那么整个transaction应该回滚,要么用户修改请求参数(4XX错误)并重发请求,要么直接重试(500错误).
Client -> OrderService: POST /orders
activate OrderService
OrderService -> AccountService: POST /accounts/:id/validate
AccountService --> OrderService: response
OrderService -> KitchenService: POST /tickets
KitchenService --> OrderService: response
OrderService -> PaymentService: ...
PaymentService --> OrderService: response
OrderService --> Client: response
deactivate OrderService
当你的实现方式变成异步的(Saga模式)之后,系统的交互就变成了下面的这种方式:
Client -> OrderService: POST /orders
activate OrderService
OrderService -> AccountService: validateAccount message
OrderService --> Client: response
deactivate OrderService
AccountService --> OrderService: reply message
activate OrderService
OrderService -> KitchenService: createTicket message
deactivate OrderService
KitchenService --> OrderService: reply message
activate OrderService
OrderService -> PaymentService: ...
deactivate OrderService
PaymentService --> OrderService: reply message
每个service都是一个独立(self-contained)系统,系统之间通过异步消息通讯。在这种架构模式下,原来的那种“黑盒子”思维就被打破了,你就被迫需要考虑更多的细节,比如Order的状态。
hide empty description
[*] -> Pending: sent msg to validate account
Pending --> TicketCreated: Kitchen replied successfully
TicketCreated --> PaymentCollected: Payment processed
PaymentCollected --> Created: Received payment processed msg
Pending --> Invalid: invalid account
Created --> [*]
Invalid --> [*]
因为从client的角度,POST /orders
的response是一个异步的,带一个jobId, 用户之后每次查询order job信息,你都需要告知用户当前的order状态。
有了这些状态之后,对于设计者来说,会帮助你对系统有更加深入的思考。比如,在等待的过程中,如果用户想cancel订单怎么办? 有这个状态机就可以让你可以直观地和PM讨论,在什么情况下我们允许用户cancel订单,什么情况下不允许。
@startuml
hide empty description
skinparam state {
BackgroundColor<<Focus>> LightBlue
}
state Canceled <<Focus>>
[*] -> Pending: sent msg to validate account
Pending --> TicketCreated: Kitchen replied successfully
TicketCreated --> PaymentCollected: Payment processed
PaymentCollected --> Created: Received payment processed msg
Pending --> Invalid: invalid account
Pending --> Canceled: received cancel request
Canceled --> [*]
Created --> [*]
Invalid --> [*]
@enduml