圖4. 精簡版本的BankingAccount類
通常,有些PFM應用也允許我們管理支付行為,并且持有一個收款人(Payee)注冊器。在這個場景中,收款人可能與一個或者多個銀行帳戶關聯,但是對于收款人來說,我們既不能獲取其銀行帳戶的內部情況,也不能在該帳戶上觸發任何操作。那么將“收款人帳戶”與剛剛定義的BookingAccount類關聯在一起是否正確呢?
圖5. Payee類與BankingAccount類
恩......這聽上去有些道理:畢竟它們都是相同的概念,在現實世界中,我們的帳戶和收款人的帳戶甚至會處在同一個物理上的銀行里。另一方面,這樣做似乎又不完全正確:因為我們不允許調用收款人銀行帳戶的任何操作,也不能追蹤他們的任何信息。更糟的是,這樣做了后,可能會在我們的程序中埋下一個概念的錯誤。
我們應該如何做?我們應該再一次回到應用程序的兩個不同的上下文里去:這一次我們可以采取兩種不同的方式對同一個領域概念進行建模,因為對領域概念的兩種使用場景明顯不同,每一種都需要一個不同的模型。BankingAccount類仍然允許我們執行(或者跟蹤)特定的操作(比如存款與取款),同時另一個獨立的PayeeAccount類可能有一些和BankingAccount相同的通用數據(比如accountNumber),但是有一個簡化的模型和完全不同的行為(比如我們不能訪問收款人的余額信息)。圖6所示的正是這種場景:盡管“銀行帳戶”有著清晰的含義,其底層概念也是惟一的,但是在應用程序中卻以不同的方式被使用著。
圖6. BankingAccount和PayeeAccount類
這看著似乎挺明顯的,其實不然。當你設計類圖,或者使用UML建模工具時,你可能很自然地讓收款人具有一個bankingAccount屬性,而且會慶幸“我剛好有一個這樣的類”。Pavlovian試圖去除代碼中的重復,有時,它的作用會弊大于利。
如圖7所示的上下文圖,可以用于表述上面討論的示例。注意,只要我們關于環境的知識在增加,就將它反映在圖中。在這個例子中,我們將PFM的應用上下文分成了“銀行”和“開銷跟蹤”。
圖7. 非常簡單的上下文圖:畫上了領域模型區域間的輪廓,可以看出在這些區域內保證了概念的完整性
在這個例子中,兩個上下文擁有一些邏輯上重疊的區域,即“銀行帳戶”的概念,它在應用程序的不用區域中,使用方式也不同,這意味著我們要使用不同的模型。但是兩個模型又可能有非常緊密的交互。上下文圖除了能幫助我們保證模型的概念在不同上下文邊界內的完整性,它還能幫助我們關注在不同上下文之間出現的情況。在這個例子中,假設同一個團隊正在兩個上下文上同時工作,我們就需要讓團隊中的每位成員的明確兩個上下文的區別,并且就兩個上下文中出現的術語和概念,分享同一個轉換的映射關系。
示例3:外部系統
再來考慮一下PFM。很多這種應用程序都需要與某些金融在線服務進行數據交換。在這個例子中,銀行會為家庭銀行服務提供實時的訪問。其他的例子還包括允許用戶下載通用標準格式(比如Money或者Quicken格式)的銀行對帳單。但是從上下文圖的視角來看,無論是交互活動還是通訊的方向(單向或是雙向),并不重要。有一件事是要關注的,我們有了不同的模型。圖8展示了PFM與在線銀行應用程序的交互行為。
圖8. 在上下文圖中,與外部應用的交互行為很自然地需要獨立的界限上下文
即使設計兩個模型之初是讓它們展現相同的數據(至少在一定程度上),但隨著時間的推移,它們還是會受到不同因素的影響,而且它們也會用于不同的目的。因此,分離上下文邊界是必須的。如果假設用戶檔案(User profiling)模塊是由第三方庫實現的,那么示例1也能夠歸入到這一類中。
管理多個上下文
當應用程序跨越了多個上下文后,我們必須管理上下文之間的關聯。不同的界限上下文之間的關系,通常是我們深入觀察項目的線索。
有一件事情非常關鍵,即兩個上下文之間的聯系是有方向的。DDD用兩個專門的術語表述它們:“上游(upstream)”和“下游(downstream)”,一個上游上下文會影響到相應的下游上下文,但是反過來就不一定了。這不僅體現在代碼上(一個庫依賴于另一個),還體現在技術含義較少的因素上,比如進度、對外部請求的響應,比如,當在線銀行服務更改了API或者其他什么原因,我們的PFM銀行應用程序都必須要快速地更新。所以我們的PFM上下文應該是下游的,而在線銀行服務很明顯就是上游的了。圖9演示了這兩種領域上下文的關系。
圖9. 分離的上下文之間的Upstream-downstream關系
如果外部系統發生變化,我們可以接受這種變化,來更新與外部系統通訊的方式。不過我們仍然需要一些保護措施隔離來自上游上下文的變化,保證我們自己的“銀行”的上下文的概念完整性。DDD包含了幾種組織模式,幫助我們描述和管理不同的上下文交互方式。最適合我們在這里使用的是模式叫做反腐化層(Anti-Corruption Layer,ACL),它會在代碼層面上實現顯式的轉換,轉換可以在兩個上下文之間,或者在“銀行”上下文的外部邊界上完成。這不僅局限于技術上的轉換,比如Java轉化為XML,同時也是一個很合適的機會,能夠管理各個模型之間的所有微妙的不同。如下面的圖10所示,我們在上下文圖上添加了ACL。