? ? ? 在2009年4月硝训,我在MSDN發(fā)表了(“持久化模式”)文章,描述了一些當(dāng)你使用ORM技術(shù)持久化業(yè)務(wù)對象時將會碰到的一些基本模式。我認(rèn)為你和你的團隊可能不會根據(jù)提綱實現(xiàn)自己的ORM工具窖梁,但是這些模式對于高效的使用(或者僅僅是選擇)已存在的工具很重要赘风。
? ? ? 在這篇文章中,我想用工作單元(Unit of Work)繼續(xù)討論持久化模式和審視圍繞隱式持久化的問題纵刘。貫穿整篇文章的大部分邀窃,我使用一個泛化的發(fā)票系統(tǒng)作為問題域的例子。
工作單元(Unit of Work)模式
? ? ? 在企業(yè)軟件設(shè)計中最常用的一個設(shè)計模式是工作單元假哎。按照Martin Fowler的說法瞬捕,工作單元模式是“維護一個被業(yè)務(wù)事務(wù)影響的對象列表,協(xié)調(diào)變化的寫入和并發(fā)問題的解決舵抹。
? ? ? 工作單元模式并不需要你自己明確地構(gòu)建肪虎,但是我注意到幾乎每一個持久化工具中它都出現(xiàn)過。NHibernate中的ITransaction接口惧蛹,LINQ to SQL中的DataContext類扇救,還有Entity Framework中的ObjectContext類都是工作單元模式的例子。正是因為這個香嗓,古老的DataSet(venerable dataset)可以作為一個工作單元使用迅腔。
? ? ? 有時,你可能想根據(jù)使用的持久化工具寫自己特定應(yīng)用的工作單元接口或者類用于包裝工作單元的內(nèi)部邏輯靠娱〔琢遥可能有很多原因這樣做。你可能想為事物管理添加特定應(yīng)用的日志像云,跟蹤锌雀,或者錯誤處理∩环眩可能你想根據(jù)應(yīng)用的剩余部分封裝你的持久化工具的特定部分汤锨,想用這個額外的封裝使以后替換持久化技術(shù)更加容易“倏颍或者你想促進你的系統(tǒng)的可測試程度闲礼。很多常用持久化工具的內(nèi)部工作單元實現(xiàn)方式在自動化單元測試場景下很難處理。
? ? ? 如果你構(gòu)建過你自己的工作單元實現(xiàn)铐维,它可能與下面的接口相似:
public interface IUnitOfWork
{
void MarkDirty(object entity);
void MarkNew(object entity);
void MarkDeleted(object entity);
void Commit();
void Rollback();
}
? ? ? 你的工作單元類有一些方法能夠標(biāo)記對象是改變的柬泽,新的,或是刪除的嫁蛇。(在很多實現(xiàn)中锨并,MarkDirty方法不是必須的因為工作單元自身一些自動決定哪些實體被改變的方法。)工作單元也有一些方法用于提交或是回滾所有的改變睬棚。
? ? ? 在某種程度上第煮,你可以認(rèn)為工作單元是一個存放所有管理事務(wù)代碼的地方解幼。工作單元的職責(zé)有:
? ? ? *管理事務(wù)。
? ? ? *命令數(shù)據(jù)庫插入包警,刪除和更新撵摆。
? ? ? *組織重復(fù)更新。在工作單元對象的單個用法中害晦,代碼的不同部分可能標(biāo)記相同的發(fā)票 ? 為已改變狀態(tài)特铝,但是工作單元類會只發(fā)送一個更新命令到數(shù)據(jù)庫。
? ? ? ?使用工作單元模式的價值在于其它代碼不需要關(guān)心這些內(nèi)容壹瘟,因此你可以全神貫注業(yè)務(wù)邏輯鲫剿。
使用工作單元
? ? ? 使用工作單元的一個最好方式是允許完全不同的類和服務(wù)參與到單一的邏輯事務(wù)中。這里的關(guān)鍵點是你要使完全不同的類和服務(wù)仍然彼此不感知稻轨,同時每一個在單個事務(wù)中有支持事務(wù)的能力灵莲。對于傳統(tǒng)方式,你可能使用過事務(wù)協(xié)調(diào)器例如MTS/COM+澄者,或者System.Transaction命名空間笆呆。就我個人而言,我更喜歡使用工作單元模式使不相關(guān)的類和服務(wù)參與到一個邏輯事務(wù)中粱挡,因為我認(rèn)為這樣使代碼更清晰,更容易理解俄精,并且更容易單元測試询筏。
? ? ? 讓我們假設(shè)你的新的發(fā)票系統(tǒng)在發(fā)票的生命周期里面的任何時間都會在已存在的發(fā)票上面執(zhí)行各種離散的動作。業(yè)務(wù)也相當(dāng)頻繁的改變這些動作竖慧,因此你要頻繁地添加或者刪除新的發(fā)票動作嫌套,所以我們可以應(yīng)用命令模式(”Simply Distributed System Design Using the Command Pattern, MSMQ, and .NET”)創(chuàng)建一個IInvoiceCommand的接口,它描述了一個作用于發(fā)票上的動作:
public interface IInvoiceCommand
{
void Execute(Invoice invoice, IUnitOfWork unitOfWork);
}
? ? ? IInvoiceCommand接口有一個簡單的Execute方法圾旨,它使用Invoice和IUnitOfWork執(zhí)行不同類型的動作踱讨。任何實現(xiàn)IInvoiceCommand的對象都應(yīng)該使用IUnitOfWork參數(shù)在在這個邏輯事務(wù)中持久化任何變化到數(shù)據(jù)庫中。
? ? ? 足夠簡單砍的,但是命令模式加工作單元模式并不會獲得好處直到你把多個IInvoiceCommand對象放在一起(看圖1)
圖1 使用IInvoiceCommand
public class InvoiceCommandProcessor
{
private readonly IInvoiceCommand[] _commands;
private readonly IUnitOfWorkFactory _unitOfWorkFactory;
public InvoiceCommandProcessor(IInvoiceCommand[] commands, IUnitOfWorkFactory unitOfWorkFactory)
{
_commands = commands;
?_unitOfWorkFactory = unitOfWorkFactory;
}
public void RunCommands(Invoice invoice)
{
IUnitOfWork unitOfWork = _unitOfWorkFactory.StartNew();
try
{
// Each command will potentially add new objects
// to the Unit of Work for insert, update, or delete
foreach (IInvoiceCommand command in _commands)
{
command.Execute(invoice, unitOfWork);
}
unitOfWork.Commit();
}
catch (Exception)
{
unitOfWork.Rollback();
}
}
}
? ? ? 通過工作單元的這種方法痹筛,你可以愉快地在你的發(fā)票系統(tǒng)中通過添加和刪除業(yè)務(wù)規(guī)則混合和組合不同的IIncoiceCommand的實現(xiàn),同時任然維持事務(wù)的完整性廓鞠。
? ? ? 在我的經(jīng)驗中帚稠,業(yè)務(wù)人員似乎更關(guān)心晚的,未支付的發(fā)票床佳,你可能將要必須添加新的IInvoiceCommand類滋早,在一個發(fā)票被認(rèn)為晚了的時候通過公司的代理商。下面是這個規(guī)則的可能實現(xiàn)方法:
public class LateInvoiceAlertCommand : IInvoiceCommand
{
public void Execute(Invoice invoice, IUnitOfWork unitOfWork)
{
bool isLate = isTheInvoiceLate(invoice);
if (!isLate) return;
AgentAlert alert = createLateAlertFor(invoice);?
?unitOfWork.MarkNew(alert);
}
}
? ? ? 這個設(shè)計的優(yōu)美之處是LateInvoiceAlertCommand完全可以不依賴數(shù)據(jù)庫進行開發(fā)和測試砌们,甚至不依賴同一個事務(wù)中的其它IInvoiceCommand對象杆麸。首先搁进,為了測試使用IUnitOfWork的IInvoiceCommand對象的交互,我可以創(chuàng)建一個IUnitOfWork的嚴(yán)格地偽實現(xiàn)保證測試的精確性昔头,我可以調(diào)用StubUnitOfWork拷获,一個記錄stub。
public class StubUnitOfWork : IUnitOfWork
{
public bool WasCommitted;
public bool WasRolledback;
public void MarkDirty(object entity)
{
throw new System.NotImplementedException();
}
public ArrayList NewObjects = new ArrayList();
public void MarkNew(object entity)
{
NewObjects.Add(entity);
}
}
? ? ?現(xiàn)在你得到一個獨立于數(shù)據(jù)庫可以運行的工作單元的偽實現(xiàn)减细,LaterInvoiceAlertCommand的測試設(shè)置可能類似于圖2的代碼:
圖2 LaterInvoiceAlertCommand的測試固定設(shè)置(Test Fixture)
[TestFixture]?
public class when_creating_an_alert_for_an_invoice_that_is_more_than_45_days_old?
{
?private StubUnitOfWork theUnitOfWork;?
private Invoice theLateInvoice;
?[SetUp]?
public void SetUp()?
{??
?// We're going to test against a "Fake" IUnitOfWork that?
?// just records what is done to it??
theUnitOfWork = new StubUnitOfWork();?
?// If we have an Invoice that is older than 45 days and NOT completed theLateInvoice = new Invoice?
{?
InvoiceDate = DateTime.Today.AddDays(-50),? Completed = false
};?
// Exercise the LateInvoiceAlertCommand against the test Invoice new LateInvoiceAlertCommand().Execute(theLateInvoice, theUnitOfWork);?
}??
[Test]
public void the_command_should_create_a_new_AgentAlert_with_the_UnitOfWork()?
?{?
// just verify that there is a new AgentAlert object?
// registered with the Unit of Work? ? theUnitOfWork.NewObjects[0].ShouldBeOfType();?
}?
[Test]?
public void the_new_AgentAlert_should_have_XXXXXXXXXXXXX()?
{?
var alert = theUnitOfWork.NewObjects[0].ShouldBeOfType();
// verify the actual properties of the new AgentAlert object
// for correctness
}
}
原文鏈接:Patterns in Practice - The Unit Of Work Pattern And Persistence Ignorance