本文將說明如何讓BPMN流程轉化成可實際運行的系統,只專注在每項活動任務的實作上,每件流程實例運行和邏輯管控則由流程引擎來負責,大幅減少程式開發人員的工作,進而加速系統開發時程及效率,並減少程式邏輯錯誤。
華為創辦人任正非曾經這麼說過:「組織運營是指企業透過流程機制和對人的管理進行價值創造,實現產出。它是企業管理水準的綜合體現,也是決定企業獲利能力的關鍵。如果沒有良好的組織運作體系,再領先的技術也會失敗,再優秀的團隊也會分崩離析。」
正因組織運作跟流程是緊密結合,資訊系統邏輯就像是企業業務規則在鏡子中映射的另一面,如何用程式來實現業務邏輯,便是資訊人員的首要工作,因此之前文章以咖啡店前後台工作範例來說明業務流程的解耦設計,說明可視化BPMN如何加速業務流程設計,讓業務分析或系統分析人員專注在設計流程,協助規劃微服務流程,接著講解如何讓已確認畫押的BPMN流程轉化成可實際運行的系統,只專注在每項活動任務的實作上,每件流程實例運行和邏輯管控則由流程引擎來負責,大幅減少程式開發人員的工作,進而加速系統開發時程及效率,並減少程式邏輯錯誤。
Camunda流程引擎
Camunda是一套專注於工作流和決策自動化的開源平台,其根源可以追溯到BPM(Business Process Management)領域中的關鍵開源專案Activiti BPM。Camunda最初是由Jakob Freund和Bernd Rücker於2008年創立的一家業務流程管理顧問公司,並且是開源軟體Activiti BPM平台的最大貢獻者之一,而Activiti BPM是由Tom Baeyens(他也是JBoss jBPM的創建者)所發起的開源專案,但後來Activiti BPM的維護公司Alfresco對於產品發展路徑出現分歧,Camunda團隊認為Activiti框架的方向和他們對於工作流引擎商業應用的想法有所不同,於是在2013年決定將其版本分支出來,另建了獨立的Camunda BPM,這個分支專注在為企業提供一套更強大、更靈活的BPM解決方案,並且對BPMN 2.0、CMMN(Case Management Model and Notation,7.2之後版本已不支援)和DMN(Decision Model and Notation)規格提供建模的支持。
自從分支之後,Camunda已經發展成為一個完整的工作流自動化和決策管理平台,提供工具來設計、部署、監控和改進商業流程和業務決策。不僅可與現代的微服務架構整合,而且還能與其他系統協同工作,如機器人流程自動化(RPA)、企業資源計畫(ERP)系統、客戶關係管理(CRM)系統和其他資料庫應用系統整合。
時至今日,Camunda已成為BPM產業領域中一個受歡迎和被廣泛使用的開源解決方案,並且擁有一個活躍的開發者和使用者社群。以下是Camunda的優勢及好處:
‧提升流程效率:透過業務流程化及自動化,可減少人工作業,提高流程效率。
‧提升流程透明度:透過儀表板進行監控,可以追蹤流程進度,並且識別流程瓶頸。
‧降低成本:藉由流程優化,可降低企業營運成本。
‧提高客戶滿意度:透過流程改善,可提升客戶服務品質。
基於上述原因,故本文選用Camunda做為設計微服務的流程編排工具,後面將以保險投保流程案例來實作說明。
Camunda Platform Architecture
由於源自Java語言撰寫的Activiti BPM,因此Camunda v7也是使用Java開發,但在v8開始就改用Golang來實作,但本文主要是圍繞在v7的應用。
其依照部署方式來區分,以便適應不同的業務需求和運行環境,共有以下四種常見的部署方式:
1. 嵌入式部署:Camunda流程引擎可以作為一個函式庫嵌入到Java應用程式中,正如Spring Boot中的Tomcat伺服器一樣,這種部署架構讓工作流程引擎成為應用程式的一部分,非常適合需要緊密整合的應用場景。
2. 應用伺服器部署:可以在現有的Java應用伺服器上部署Camunda,與傳統Tomcat、WebSphere架構相同,流程引擎在應用服務器中運行,部署的所有應用程式共用單一流程引擎,屬於單體式架構,雖提供維運管理的簡易性,卻帶來運算資源相互競爭的問題。
3. 獨立流程伺服器部署:應用程式以API方式與流程引擎串接,不同的應用程式可採用微服務設計單獨運行,並以遠端方式呼叫流程引擎,最簡易的方式是使用官方提供的SDK或是REST API來整合流程中的各項工作,除了不會有運算資源相互競爭的問題外,流程伺服器和應用程式皆可各自橫向擴展。
4. 叢集方式部署:基於獨立流程部署架構,多個流程伺服器也可共用單一資料庫,因為流程引擎可透過資料庫機制來控制流程交易的Session狀態,當流程引擎運行一個交易時,整個狀態會被更新到共享資料庫中,讓後續同一流程中的執行工作可分派到不同的叢集節點,此部署方式對流程引擎而言,具備擴展和故障轉移配置。Camunda Cloud的SaaS服務就採用這叢集架構,Camunda Cloud是基於v8 Zeebe流程引擎,這是一套由Camunda所設計的雲原生微服務工作流引擎架構。
以上是官方所提供的四種部署方式,架構示意圖可參考圖1。
Camunda Modeler
在上一篇文章(網管人雜誌213期「運用BPMN流程建模語言‧可視化加速微服務設計」)中,咖啡店流程範例就是使用Camunda Modeler來建模,並動態模擬點餐流程(影片連結:https://youtu.be/CFixLeDd2rg),它是以Electron開發的開源桌面應用程式(https://github.com/camunda/camunda-modeler),用於設計業務邏輯和流程決策,由Camunda公司所維護,是其BPM平台產品的重要關鍵工具,專門用於設計和編輯OMG組織所制定的BPMN(業務流程模型和符號)工作流程和DMN(決策模型和符號)決策表,下面是Camunda Modeler的主要特色:
‧BPMN建模:Camunda Modeler支援BPMN 2.0,這是由OMG制定的業務流程建模規格,已納入ISO/IEC 19510:2013成為國際標準,採用圖形化表示業務流程,使用者可以透過這項工具拖放各種元素來設計和編輯各種層級的複雜工作流程。
‧DMN建模:除了BPMN之外,它還支援DMN,這是一種使用決策表來設計和執行邏輯條件規則。使用DMN可以無須撰寫程式就能夠明確地定義、管理和運行決策邏輯規則。
‧友善的介面及快速部署:Camunda Modeler提供直觀的使用者介面,使用戶能夠輕鬆地建立和修改模型,包括BPMN和DMN,設計完的流程可直接從Modeler部署到Camunda BPM引擎執行,並提供從建模到部署的敏捷業務流程開發方式,這使得從設計到實作過程更加順暢,簡化流程建模和決策建模的工作。
‧整合與擴充性:Camunda Modeler除了可與Camunda BPM v7和v8平台功能整合外,它還支援插件和擴展功能,如Token Simulation(https://github.com/camunda/camunda-modeler-token-simulation-plugin)等,使用者可根據自己的需求利用其開源特性自行設計插件。
‧技術社群支持:作為一個開源專案,Camunda Modeler背後有一個活躍的技術社群和商業公司支援,為使用者提供持續支援服務、外掛程式和功能改進。
基於上述特色,Camunda Modeler(下載網址:https://camunda.com/download/modeler/)是目前設計BPMN最強大且容易使用的建模工具,適合那些希望透過標準化的方法來定義、管理和執行業務流程與決策的BA和SA,以及企業組織。
實作保險投保流程
說明了Camunda BPM的特色與工具,接下來就以保險投保流程示範在繪製BPMN業務流程圖完成之後,如何整合Spring Boot程式及Camunda Java SDK完成整個投保流程實作,但不同保險公司、不同險種有不同的流程,實際的投保流程比這BPM範例更為複雜。
解說投保流程
使用Camunda Modeler設好的完整流程如圖2所示,分別有三位參與角色:保險申請人、保險業務和保險經理,以下流程說明序號,皆標示在圖2上:
1. 系統接收客戶的投保申請訊息,接著進行相對應的核保作業。
2. 首先檢查申請資料的完整性。
3. 如果不完整,就發送通知給客戶,要求補齊資料後再重新申請。
4. 如果資料完整,就進入下一步檢查申請人是否具備保險資格。
5. 如果不合格,就發送拒保通知給客戶,告知審核結果。
6. 如果系統無法自動化確認資格,則需要人工審核。
7. 如果人工審核通過,就計算保費。保費計算使用DMN決策表來實作。
8. 保費計算完成,則建立新保單。
9. 保單建立完成後,發送給客戶,並確認簽收完成,保單生效。
參數及活動設定
1.在「收到投保申請」事件:點擊Message屬性 -> 選擇Create new -> 輸入Name = Message_InsuranceRequest,如圖3所示。
2.在「檢查資料完整性」任務:點擊Implementation屬性 -> Type選擇Delegate expression -> 輸入Delegate expression = #{integrityChecker} 或是Type選擇Java Class -> 輸入Java class = org.camunda.bpm.example.IntegrityCheckerDelegate,如圖4所示。
3.在「發送補齊資料通知」任務:點擊Script屬性 -> Format = javascript -> Type選擇Inline script -> Script = print('已發送必要文件補齊訊息。'),如圖5所示。所以BPMN也可設定腳本程式,執行簡單的程式邏輯判斷。
4.在「檢查保險資格」任務:點擊Implementation屬性 -> Type選擇Delegate expression -> 輸入Delegate expression = #{qualificationChecker},或是Type選擇Java Class -> 輸入Java class = org.camunda.bpm.example.QualificationCheckerDelegate。
5. 在「拒保」子流程:點擊Called element屬性 -> Type選擇BPMN -> 輸入Called element = rejectInsurance,如圖6所示。而在拒保子流程(reject_insurance.bpmn),其ID須為rejectInsurance。
6. 在「審查案件」任務:點擊User assignment屬性 -> 輸入Assignee = manager -> 點擊Forms屬性 -> Type選擇Generated Task Forms -> 新增Form fields -> 輸入ID = isRiskManagable,輸入Label = 是否風險可控?,選擇Type = boolean,如圖7所示。
7. 在「計算保額」任務:點擊Implementation屬性 -> Type選擇DMN -> 輸入Decision reference = calculate-premium,輸入Result variable = premium,輸入Map decision result = singleEntry (TypedValue)。而在保費計算DMN(calculate-premium.dmn),其ID須為calculate-premium。
8. 在「建立保單」任務:點擊Implementation屬性 -> Type選擇Delegate expression -> 輸入Delegate expression = #{insuranceContractProvider} 或是Type選擇Java Class -> 輸入Java class = org.camunda.bpm.example.InsuranceContractProviderDelegate。
而旁邊的「發送保單」任務:點擊Script屬性 -> Format = javascript - > Type選擇Inline script -> Script 內容如下:
print('Ready to send contract to user') message = execution.getVariable ("contractMessage") print('Contract message:', message)
9. 在「確認簽收保單」任務:點擊Message屬性 -> 選擇Create new -> 輸入Name = Message_ReceiveInsuranceContract。
至於拒保子流程和DMN的詳細設定都可參考這範例的GitHub(https://github.com/philipz/camunda_insurance_sample),這裡就省略設定步驟。
程式串接
在上述的流程中,包含了四項活動需要實作Spring Boot程式來串接外部業務系統或邏輯,首先到Camunda Automation Platform 7 Initializr(https://start.camunda.com/)網站建立內建Camunda函式庫的Spring Boot模板專案,下載後,就可利用Camunda Java SDK逐一新增這四項活動。
1. 實作「檢查資料完整性」程式②:在上一節中Task宣告為#{integrityChecker},所以加上@Component("integrityChecker")的Annotation,並在execute方法中實作檢查資料完整性的邏輯,然後將結果寫到isDocumentComplete,供流程引擎判斷,程式碼如下:
@Component("integrityChecker") public class IntegrityChecker Delegate implements JavaDelegate { private final Logger LOGGER = Logger.getLogger(IntegrityCheckerD elegate.class.getName()); @Override public void execute(Delegate Execution execution) throws Exception { LOGGER.info("開始檢查投保申請 資料的完整性"); boolean isDocumentComplete = che ckRequestIntegrity(execution); LOGGER.info("申請資料是完整的嗎?" + isDocumentComplete); execution.setVariable("isDocumen tComplete", isDocumentComplete); if (!isDocumentComplete) { execution.setVariable("decline Message", "投保申請資料不完整,請核 對併補充相關資料。"); } } private boolean checkRequestIntegrit y(DelegateExecution execution) { //TODO: 檢查投保申請資料的完整性 String id = (String) execution. getVariable("id"); String name = (String) execution. getVariable("name"); LOGGER.fine(id + ": " + name); return id != null && !id.isEmpty() && name != null && !name.isEmpty(); } }
2.「完整?」閘道的資料流判斷條件:在上一節「完整?」閘道的Yes資料流,是需要設定邏輯判斷,點擊Condition屬性 -> Type選擇Expression -> 輸入Condition Expression = ${isDocumentComplete},這isDocumentComplete就是上面程式所提供的結果,見圖8。
3.實作「檢查保險資格」程式④:如前面相同,加上@Component("qualificationChecker")的Annotation,並在execute方法中實作檢查保險資格的邏輯,並將結果寫到isQualified,供流程引擎判斷,程式碼如下:
@Component("qualificationChecker") public class Qualification CheckerDelegate implements JavaDelegate { private final Logger LOGGER =Logger. getLogger(QualificationChecker Delegate.class.getName()); @Override public void execute(Delegate Execution execution) throws Exception { LOGGER.info("開始審核申請人的保險 資格"); String isQualified = checkInsuranceQ ualification(execution); execution.setVariable("isQualified", isQualified); } private String checkInsuranceQualific ation(DelegateExecution execution) { Boolean hasSocialSecurity = (Boolean) execution.getVariable("hasSocialSecur ity"); Boolean hasOtherInsurance = (Boolean) execution.getVariable("hasOtherInsura nce"); //TODO: 檢查申請人資格,此處僅是根據申 請人是否有健保和商業保險來進行判斷 String result = ""; if (hasSocialSecurity) { if (hasOtherInsurance) { result = "yes"; LOGGER.info("申請人保險資格查驗結 果:合格"); } else { result = "other"; LOGGER.info("申請人保險資格查驗結 果:需人工審核"); } } else { result = "no"; LOGGER.info("申請人保險資格查驗結 果:不合格"); } return result; } }
4.設定「合格?」閘道的資料流判斷條件:
A. 在「合格?」閘道的Yes資料流,點擊Condition屬性 -> Type選擇Expression -> 輸入Condition Expression = ${isQualified == 'yes'}。 B. 在「合格?」閘道的No資料流,點擊Condition屬性 -> Type選擇Expression -> 輸入Condition Expression = ${isQualified == 'no'}。 C. 在人工審查資料流,點擊Condition屬性 -> Type選擇Expression -> 輸入Condition Expression = ${isQualified == 'other'}。
這個isQualified就是上面程式所提供的三種結果。
5.實作「建立保單」程式⑧:加上@Component("insuranceContractProvider")的Annotation,並在execute方法中實作建立保單的邏輯,並將保費訊息寫到contractMessage,程式碼如下:
@Component("insuranceContractProvider") public class InsuranceContractProvid erDelegate implements JavaDelegate { private final Logger LOGGER = Logger.getLogger(InsuranceContractPr oviderDelegate.class.getName()); @Override public void execute(Delegate Execution execution) throws Exception { LOGGER.info("開始準備保單"); Long premium = (Long) execution. getVariable("premium"); String contractMessage = prepareIn suranceContract(premium); LOGGER.info(contractMessage); execution.setVariable ("contractMessage", contractMessage); } private String prepareInsurance Contract(Long premium) { //TODO: 準備保單 return "恭喜!核保通過,保費為 每年:" + premium + "。"; } }
6. 實作「準備拒保通知」程式:加上@Component("declineMessageProvider")的Annotation,並在execute方法中實作建立保單的邏輯,並將拒保通知訊息寫到declineMessage,程式碼如下:
@Component("declineMessageProvider") public class DeclineMessageProvider Delegate implements JavaDelegate { private final Logger LOGGER = Logger. getLogger(DeclineMessageProviderDeleg ate.class.getName()); @Override public void execute(Delegate Execution execution) throws Exception { LOGGER.info("開始準備拒保通知"); String declineMessage = prepare DeclineMessage(); execution.setVariable("decline Message", declineMessage); } private String prepareDecline Message() { //TODO: 準備拒保通知 return "抱歉!保險申請被拒絕,請 聯繫客服中心以便獲得更詳細資訊。"; } }
完成上述步驟後,就可啟動Spring Boot並測試,輸入mvn spring-boot:run,接著遞送保單申請資料,輸入curl -X POST -H "Content-Type: application/json" http://localhost:8080/engine-rest/message -d @request_message.json,便可在http://localhost:8080/看到Camunda後台畫面。輸入demo/demo登入後,點選Cockpit -> Running Process Instances -> 投保流程,就可看到目前流程現況,如圖9所示。
礙於篇幅,只粗略說明並示範投保流程重要的實作部分,細部內容和說明,請參考Github專案README文件。
流程自動測試
在活躍的Camunda開源社群,提供了針對流程覆蓋率的自動測試套件Camunda Process Test Coverage(https://github.com/camunda-community-hub/camunda-process-test-coverage),而圖10便說明Process Tests與Unit Tests和Integration Tests在系統範圍上的差異,且此工具可整合到CI流水線,當測試覆蓋率到達90%以上才能通過,藉此要求流程的正確性和正確率。
依照這套件撰寫各種情境的流程條件,並啟動流程實例,確認是否到達特定狀態,並檢查相關參數和實例的內容,確保流程設計和實作邏輯相匹配。同樣地,詳細測試撰寫內容,請參閱Github專案,執行mvn clean test後,就可在target/process-test-coverage看到覆蓋率報告,如圖11所示,色底覆蓋的BPMN圖表示都已驗證過,提供每個情境的覆蓋率,方便開發者核對。
結語
Gregor Hohpe在《The Software Architect Elevator》中說道:「當您查看一盒樂高積木的封面時,您看不到裡面每塊積木的圖片。相反地,您看到的是一個令人興奮的、完全組裝好的模型的圖片,例如海盜船。更令人興奮的是,模型並沒有放在客廳的桌子上,而是放置在一個栩栩如生的海盜灣裡,那裡有懸崖和鯊魚。」
BPMN如同樂高積木的封面一樣,可清楚看出一項業務的整體流程和各角色人員的R&R,除了流程可視化,加上可直接部署執行,確保流程圖與程式邏輯的一致性,這大大提高業務分析師或開發人員採用BPMN來設計流程的意願,而不是程式修改後,還需額外時間回頭修改規格文件,導致文件與程式不一致,逐漸變成只看程式碼來確認業務邏輯,這既沒有效率,且見樹不見林,改了A忘了B;當業務單位要求調整邏輯規則時,無法第一時間察覺影響範圍和負責單位,也無法精準評估修改所需時間,當採用Camunda這類新式BPM流程引擎,上述問題都可迎刃而解,而新式流程引擎與其他方案的比較請見表1,可看出新式流程引擎的優異性。
在Google學術搜尋(https://scholar.google.com/),輸入microservices和bpmn就可看到近幾年BPMN在微服務設計的應用研究,這對於微服務架構的領域拆解和決定服務粒度有相當大的幫助,因此筆者深信BPMN將成為微服務設計的主流工具及方法論之一。
<本文作者:鄭淳尹,Docker.Taipei社群共同發起人,國泰金控技術架構師,曾任台北富邦銀行雲端系統部架構師、微軟MVP、momo購物網架構師、臺北榮民總醫院資訊工程師、玉山銀行資訊處專員、宏碁eDC維運工程師。開源技術愛好者,曾在多間大學資工系擔任Docker容器技術講師,並翻譯審閱多本容器技術書籍。>