你還記得windows workflow foundation嗎


很多年前,windows workflow foundation還叫WWF,而直譯過來的名稱讓很多人以為它就是用來開發工作流或者干脆就是審批流的。

博主當年還是個懵懂的少年,卻也知道微軟不會大力推一個面向如此具象的業務場景的技術,於是特地找了一本《WF本質論》,當看到“程序即數據”這個論斷時,被深深震撼了。可能這只是作者的隨意一寫,但當時正是泛型方法、lamda表達式、匿名委托啥的開始出現的時候,作者的這一說法在某種程度上暗合了博主平常的編程思想。於是邏輯與數據,算法與結構,它們之間的界限在我眼里,在我心里,開始以更詭異的方式模糊了起來。

然而之后並未在工作上使用過WF,因此博主也就不再關注此項技術。如今重新翻看,突然發現官方的Workflow Team blog最近的更新都是兩年前了,網上的資料都是N年前的,而且大多數是教大家怎么用。為何要用,何處要用,基本上沒有太多介紹。到了現如今,似乎也被微軟拋棄了,為此我還專門在博問里提了下,也沒人知道具體詳情。

很少有看到專門使用wf進行開發的場景,好用與否,在小眾的群體里抽樣得出結論也就顯得不怎么可靠了。似乎wf在sharepoint環境中用起來比較協調,由於博主對sharepoint沒有研究過,所以對此不好評論。如若在純代碼環境下開發,用wf自帶的一套activity能畫出看似清晰的流程圖,然而關鍵的和外部交互的環節卻仍然避免不了,需要考慮的因素大部分仍然需要代碼去完成;而各種條件下流程的走向,使用activity的條件判斷,又讓人覺得沒有直接寫代碼來的方便,比如ifelse,用代碼可能只要在一個方法體中就能完成,而在流程圖中卻要使用兩個activity,如果其中再有交互跳轉的話,流程圖的復雜程度未必在代碼之下,而后期維護也難說容易。

不妨非惡意的揣測,經過幾年時間,微軟慢慢覺得,將代碼間原本隱晦的邏輯關系抽離出來變成看得見的“流程”,對程序員來說,未必有多大的意義,於是就減少了這方面的投入。當然你也可以認為wf已經很成熟了,不過即使如此,博主還是傾向認為此種所謂成熟是因為找不到改進的方向,雖然“成熟”,但不怎么好用。

but,wf並非一無是處,wf的bookmark(書簽機制)是最區別於傳統開發的特性,個人認為也是最重要的特性。傳統開發時,我們時不常的會遇到這種情形:判斷流程是否走到特定環節以便決定下一步操作,其實就是判斷A\B\C\D\...等排列組合變數,它們之間可能也是相互關聯的,只要流程沒有繼續流轉,那么每次啟動都要進行同樣的判斷;是否可以在各變數滿足條件時生成一個標簽,當我們發現這個標簽存在時,就可以去找標簽對應的環節,就能馬上進行后續操作了。實際開發中,我們常設置一個冗余的標識字段起這個作用。但單個字段未必能完全標識一個環節,而且也顯得不夠直觀。wf中的bookmark與上述的標簽類似,本質上,它是對委托異步回調爐火純青的應用,更自然的描述是在變數尚未符合條件時的一種“等待”,常用於與外部交互,在流程設計時,也不會像傳統代碼操作(查詢、判斷、更新)標簽那樣顯得突兀(這些操作wf都在更底層幫我們處理了)。

開頭說到微軟推出wf的本意並非用於開發業務上的工作流,我們甚至可以將任意有邏輯順序的一段代碼“wf化”,然而從業務場景來講,wf的很多概念能映射其中,畢竟抽象和具象,都逃不離流程二字。因此項目前期或工期緊張,需要快速開發的時候,wf也能幫助程序員梳理流程——在[對]業務流程尚不清晰的時候,你會發現這非常有用——就算日后擯棄wf,大部分代碼都可以復用,而由於各環節明確的流轉關系,代碼重構也較為容易。博主就是因為這個原因,在目前的一個項目中使用了wf,下面我會簡單介紹相關要點和個人理解。

版本控制也是它的一個亮點,這個就不細說了。

由於本人對wf鑽研不深,所述難免有誤,請各位同學批評指正。另,本文並不展示任何項目代碼,只以行業一般流程,展示博主在開發過程中的思索。


本項目主干是一手房置購流程,涉及到認籌、認購、成交、租賃四個環節,需求簡化后如下(圖是用visio畫的,畫得很挫,一直沒找到實線弧形箭頭):

1、環節流轉

2、流轉時需審核(可跨級審核,若申請人本身是最后環節審核人則直接通過)

3、審核不通過時將退回發起環節;審核過程中,申請人也可撤消此次申請

4、單個環節也有不同狀態,比如認購有已認購退認購兩種狀態,此類狀態轉換也需要審核;而內容變更也需要審核

5、同時至多只能存在一個審核中事項。比如認購內容變更審核中時,就不能發起轉成交申請。

這是一個比較典型的工作流場景。剛接手這個項目時,我考慮用傳統方式開發,不多久我發現自己在各種環節、狀態、標識、互斥項間暈頭轉向,一團漿糊。即使流程圖已經很清晰明白地擺在面前,將之賦予代碼和數據,並使看似毫無關聯的各塊內容按照預期邏輯運行,似乎顯得相當困難。特別是產品有時會一臉無奈地跟你說,認籌也可以轉租賃,業務說的;隔天又說,不需要了。要維系代碼之間“隱秘”的邏輯關系並快速應對需求變更,隨着業務復雜度的提高,難度也更快的升高。

當然,對於熟練工來說,這點難度不算什么,畢竟我們每天都在做這種工作——將業務流程轉化成層層調用的代碼——我們還可以祭出設計模式、AOP、IOC、ORM啥的讓代碼看起來更清(fu)晰(za)。我們一直在接受面向對象、面向架構、面向服務的訓練,而缺少真正面向流程編程的經驗,我想這才是為什么微軟當年會推廣wf的原因(此為病句,意為強調)。

wf有三種內置流程:順序流、Flowchart、StateMachine。本項目可考慮后兩種,博主選擇的是StateMachine,整出來的主流程如下:

看上去很凌亂,只能怪vs自帶的wf設計器沒有visio好用,其實上圖就是第1幅流程圖的wf版本。

再來看下每條連線的邏輯,以認購環節出去的連線為例,觸發器如下:

可以看到,流程會依據外部信號決定下一步驟,流到下一個環節(轉成交、轉租賃),或者是退認購,這里還有變更的情況圖中沒放出來。最終會根據流程中各變量和參數值選擇某個路線:

任何類型的審核不通過or申請人撤銷就回到當前環節,和之前我們用visio畫的流程圖一樣直觀。不過,這里有個問題。博主剛接觸statemachine時,選擇entry還是trigger放置業務邏輯比較困惑,當時認為兩者皆可,畢竟最終只是通過condition來指定狀態的轉變方向,用不着care condition在哪里生成;博主當時認為微軟設計statemachine時,是將trigger當做bookmark activity的容器,以便於與外部決策進行交互,當然我們也可以將bookmark放在entry中,說到底這就是一個規范問題,不必深究。然而博主發現,若流程從一個state流向同一個state——即state指向自己——其實前后兩個環節已不是同一個“實例”了 ,因此原state的變量等狀態不會保存,entry會再次執行。為了避免這種狀況,有停留在當前狀態的情形,應該將業務邏輯寫在entry中,不進入到觸發后的流轉,因為一旦流轉,就算流轉回當前狀態,也是先出再進。當然,如果當前狀態沒有需要保留的信息,寫在trigger亦可。

再來看看審核到底發生了什么。

這個更不用我多說了,不過我還是要多說兩句。雖然上圖很清晰地還原了對應的需求,但未必是最合適的做法,我最后還是將這些邏輯統一到一個activity里。前面說過,我們能將絕大多數多行代碼wf化,but,復雜的流程需要借助wf,一個簡單方法就能搞定的邏輯如果還硬要畫出幾道條條理理來,那就是偏執了。(話說,寫程序的大多數人都是偏執的,非黑即白,我沒說錯吧)

對於前述需求的第5條,單條wf流程天生就能滿足,不需要我們做額外的coding。思考一下,若幾個審核可以並行,或流程流轉到下一環節后,上一環節需要變更了,怎么辦?用多流程或子流程試試吧。


代碼活動中普通的成員變量,持久化在bookmark恢復后,值丟失不可用;若需要在持久化前后保持變量值,應該使用Variable,或應用Serializable特性,兩者都是博主推測沒試過。

有些事務不能失敗了就全部回退重來,比如單據狀態從a->b,經過多位領導審批同意后,結果因為最后一步提交不成功,讓單據回退到a重審?這種情況,只能將最后一步重新提交,實際開發中,可將此類“臟提交”定時再提交。

一個activity里可create多個bookmark,當所有bookmark都恢復執行后,流程才繼續往下。

wf在流程流轉過程中,並不能返回數據給調用者,比如發起認籌轉認購的申請,調用ResumeBookmark方法,並不能知曉是直接通過還是等待審核,需要另外查數據庫得到狀態結果。當然可以使用WorkflowApplication.Extensions與外部程序交互,但不能滿足某些場景,比如客戶端調用webapi,服務端action發起流程,action自然需要知道流程跑完后(暫停or完結)的結果是什么並返回給客戶端;這時候WorkflowApplication.Extensions就然並卵了,除非改寫底層,比如讓wf能取消action的后續執行並將結果數據寫入http連接。

流程改版or外部依賴項變化,wf下,要么新舊版本並行,要么研究如何將舊版本遷移到新版本,很多情況下沒有純代碼控制來得方便。

博主觀點:同步SaaS並非wf較適用的場景,wf適用於異步消息推送場景,比如外賣點餐狀態、快遞狀態、銀行資金流轉等,客戶端並不等待馬上的結果,而是事項狀態改變時接收服務端消息即可。當然硬要在同步環境下使用也可以,此時需要更松散的設計和底層框架的配合,以適應wf“封閉式流程”的特點。


需繼續研究的點:

1、有些流程大同小異,能否封裝成一個可配置的流程,在設計時進行簡單的參數配置就能顯示不同的流程步驟;比如a狀態可轉為b、c狀態,而b只能轉c狀態,那么a,b在轉換到下一個環節的判斷邏輯就有少許差異了。這貌似只能通過元數據實現。

2、InstanceOwner,網上實在找不到更多關於它的介紹,目前可知是用於多宿主環境下,宿主對wf實例的lock。沒找到給實例賦值InstanceOwner的直接途徑,網上找到的基本上是下面兩行代碼:

InstanceView view = instore.Execute(instore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30)); instore.DefaultInstanceOwner = view.InstanceOwner;

不知是基於什么因素考慮,總是覺得不太理解是什么意思,為什么要以及以這種方式設置DefaultInstanceOwner,如果不設置的話,那么下面這行代碼運行時就會報錯:

WorkflowApplicationInstance instance = WorkflowApplication.GetInstance(Guid.Parse(flowinstance.InstanceID), instore);

框架應該完全可以在底層就自動給我們處理InstanceOwner相關的東東,比如下面這句在沒設置DefaultInstanceOwner的時候就不會報錯:

wfApp.Load(Guid.Parse(flowinstance.InstanceID));

如何能設置自己的InstanceOwner呢,maybe可以通過InstanceOwnerMetadata。

CreateWorkflowOwnerCommand createCommand = new CreateWorkflowOwnerCommand() { InstanceOwnerMetadata = { { WorkflowHostTypeName, new InstanceValue(WFInstanceScopeName) }, } };

不過設置完了,怎么獲取又是一個問題,有興趣的朋友可以研究下這個文件,也可以在官方WF_WCF_示例里找到。總之持久化這趟子水夠深。

 

其它參考資料:

Loading persisted workflow instances with WorkflowApplication


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM