實戰項目:設計實現一個流程編排框架(分析)

最近幾篇文章,我會帶大家一起設計一個流程編排框架,從項目的分析、設計、實現、重構、測試方面去了解整個編排框架,也會用到一些設計開發原則及設計模式,話不多說,我們先來看下編排框架的一個背景。

背景

對於交易這樣複雜的業務場景,隨着時間增加、功能逐漸增多,代碼越來越多,所以系統就會考慮使用微服務框架;但是使用微服務框架之後,原有的業務並沒有發生變化,與傳統架構相比,微服務架構下會更依賴通過各微服務之間的協調來實現業務流程,這種協作就是流程編排。編排設計到方法節點、服務調用、條件選擇、串行、並行、子流程等;當然在這個過程中也需要考慮通訊層、分佈式事務;需要有一個完善的編排框架來支撐。

一般常見2種編排方式

第一種:面向可執行的流程,通過一個可執行的流程來協同內部及外部的服務交互,通過流程來控制總體的目標、涉及的操作、服務調用順序。首先要有一個流程控制服務,該服務接收請求,依照業務邏輯規則,依次調用各個微服務,並最終完成處理邏輯。流程控制服務時時刻刻都知道每一筆業務究竟進行到了什麼地步,監控業務成了相對簡單的事情。

第二種:API網關,API網關可以看作一種簡單的接口聚合/拆分的方式:每筆業務到來後先到達網關,網關調用各微服務,並最終聚合/拆分需反饋的結果。如果有開放接口文檔還可以用編排定製產品,直接達到商用的效果。

項目背景

我們常規的項目是怎麼做這種編排的呢?我們列舉一個例子,A->B->C:這種場景是比較簡單,對於開發人員來說就是新建一個類先執行A邏輯同步等待執行,在執行B邏輯同步等待結果在執行C;然後對所有結果進行處理返回;但是這個邏輯不可能是不變的,可能第二天又說在B->C加一個D邏輯,有需要在之前的業務代碼裏面做業務流程新增邏輯,而且需要負責的測試;當然後面可能還有更多的需求提供來,比如其它邏輯要應用這個編排,或者在執行B時需要加條件執行C和D,對於開發人員來說是遲早奔潰的事情。

在這個例子中我們可以看到幾個問題:

  • 流程擴展性不強,需要對流程做變更的時候需要在原有邏輯修改代碼,對於流程的風險也是比較大的;

  • 流程可讀性不行,簡單流程邏輯我們還可以理解,但是節點多了之後,就是產生各種邏輯、各種設計模式,不管對於老員工或者新員工來說,都是一筆不小的負擔;

  • 代碼冗餘,在項目裏面有過多的我是看到的只是一堆代碼類似,但是功能實現不一樣的邏輯;某一段邏輯出現問題,需要改涉及到的所有流程,還有可能改漏的風險性;

  • 缺少開發規範,一萬個人就有一萬個哈姆雷特,只要能實現功能,只要能完成任務就行,根本考慮不到誰來接手的問題;

  • 缺少監控手段,不知道這個流程定義了幾個節點,每個節點耗時,是那個節點出現的問題,對於運維來說也是大問題;

針對上面的這些問題,我們有什麼建議呢?我先來說說的我的;

我們可以把被調用的服務或者是簡單封裝成一個個的基礎組件,規範輸入輸出,把調用條件封裝成一個個要素;通過文件或者是外部接口做服務編排;比如我定義一個test.flow.yml文件對我的基礎組件就行編排,完全不用再代碼裏面去做業務邏輯實現。

我們希望我們開發出來的東西有一定的影響能力,即使做不到在行業裏面有影響力,起碼也要做到在公司範圍內有影響力。所以,從一開始,我們就不想把這個編排框架,做成只有我們項目可以,而是一個通用框架,可以集成到各個系統,甚至集成到微服務治理平臺中。實際上,這也體現了業務開發中要具備的抽象意識、框架意識。我們要善於識別出通用的功能模塊,將它抽象成通用的框架、組件、類庫等。

需求分析

剛剛我們花了一些篇幅項目背景和背景需求,接下來,我們再對需求進行更加詳細的分析和整理。

功能性需求

首先我們需要定義編排規則,但是沒有可視化操作的界面的前提下,我們會把編排定義在yml、properties、xml中,我們先看一個demo示例:

name: openAccount
id: test
desc: 條件執行
input: com.service.flow.sample.common.model.TestInput
output: com.service.flow.sample.common.model.TestOutput
temp: com.service.flow.sample.common.model.TestTemp
startNode: node1
nodes:
  - node:
      id: node1
      name: methodInvoke
      component: com.service.flow.sample.common.component.TestComponent:test1
      desc: 單節點執行
      input: com.service.flow.sample.common.model.TestInput
      type: method
      next: node2
  - node:
      id: node2
      name: beanInvoke
      component: testComponent:test2
      desc: 單節點執行
      input: com.service.flow.sample.common.model.TestInput
      type: bean
      next: node3
  - node:
      id: node3
      name: conditionByAge
      component: temp.getAge>20:node4,temp.getAge<20:node5
      desc: 單節點執行
      input: com.service.flow.sample.common.model.TestInput
      type: condition
  - node:
      id: node4
      name: beanInvoke
      component: testComponent:test2
      desc: 單節點執行
      input: com.service.flow.sample.common.model.TestInput
      type: bean
  - node:
      id: node5
      name: beanInvoke
      component: testComponent:test2
      desc: 單節點執行
      input: com.service.flow.sample.common.model.TestInput
      type: bean
  • 我們需要定義一些元素請求參數、輸出參數、執行ID、流程描述、臨時變量、節點類型,執行條件;

  • 我們需要提供多種編排註冊方式yml、json、xml、properties、text、接口;

  • 我們採用鏈表的形式執行所有的節點,減少內存開銷;

  • 需要支持多種類型節點,方法節點、服務節點、條件節點、Bean節點、子流程節點等;

  • 我們需要對流程上下文做擴展;

  • 我們框架需要基於Spring做基層框架,並提供對應的starter,做可集成框架;

非功能性需求

易用性方面,我們希望提供流程提示,有能力的情況下提供可視化編排,我們將以Spring框架作爲基礎框架,我們還希望編排框架能否非常方便地集成到使用 Spring 框架的項目中。

擴展性、靈活性方面,我們希望能夠自己定義各種組件和元素模型,我們希望編排初始化的時候提供各種擴展接口,將不同類型的文本,轉換成編排模型;

性能方面:我們希望模型在項目啓動的時候就初始化好,只初始化一次;節點執行的時候採用遞歸鏈表的形式,減少不必要的代碼循環;

容錯性方面,不能因爲編排框架而影響的本身的業務邏輯,即使不用這個框架也不至於框架大亂。

功能需求其實沒有多少,但將非功能性需求考慮進去之後,明顯就複雜了很多。還是那句老話,寫出能用的代碼很簡單,寫出好用的代碼很難。對於編排框架來說,非功能性需求是設計與實現的難點。怎麼做到易用、靈活、可擴展、低延遲、高容錯,纔是開發的重點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章