您的位置:首页 > 运维架构 > 网站架构

使用Apworks开发基于CQRS架构的应用程序(四):领域事件

2011-01-26 16:10 351 查看
根据wikipedia中关于“事件”的描述,“事件”可以被看成是“状态的一次变化”。例如:当一个客户购买了一台汽车,汽车的状态就从“待售”转变为“已售”。汽车销售系统则把这种状态的改变看成是一次事件的产生、发布、检测以及被更多其它应用程序所使用的过程。

对于CQRS架构的应用程序而言,事件产生于领域模型,并由领域模型发布事件同时由领域模型首次捕获并处理,因此,我们称之为领域事件(DomainEvents)。在Apworks开发框架中,与领域事件相关的代码都被定义在Apworks.Events命名空间下。

请使用下面的步骤为TinyLibraryCQRS解决方案添加领域事件。

SolutionExplorerTinyLibraryCQRS解决方案上,右键单击并选择Add|NewProject…菜单,这将打开AddNewProject对话框

InstalledTemplates选项卡下,选择VisualC#|Windows,然后选择ClassLibrary,确保所选的.NETFramework版本是.NETFramework4,然后在Name文本框中,输入TinyLibrary.Events,并单击OK按钮

SolutionExplorer下,右键单击TinyLibrary.Events项目的References节点,然后选择AddReference…菜单,这将打开AddReference对话框

.NET选项卡下,选择Apworks,然后单击OK按钮

TinyLibrary.Events项目添加下面的类代码,以实现领域事件

[code]1:usingSystem;


2:usingApworks.Events;


3:


4:namespaceTinyLibrary.Events


5:{


6:[Serializable]


7:publicclassBookBorrowedEvent:DomainEvent


8:{


9:publiclongBookId{get;set;}


10:publiclongReaderId{get;set;}


11:publicstringReaderName{get;set;}


12:publicstringReaderLoginName{get;set;}


13:publicstringBookTitle{get;set;}


14:publicstringBookPublisher{get;set;}


15:publicDateTimeBookPubDate{get;set;}


16:publicstringBookISBN{get;set;}


17:publicintBookPages{get;set;}


18:publicboolBookLent{get;set;}


19:


20:publicDateTimeRegistrationDate{get;set;}


21:publicDateTimeDueDate{get;set;}


22:}


23:


24:[Serializable]


25:publicclassBookCreatedEvent:DomainEvent


26:{


27:publicstringTitle{get;set;}


28:publicstringPublisher{get;set;}


29:publicDateTimePubDate{get;set;}


30:publicstringISBN{get;set;}


31:publicintPages{get;set;}


32:publicboolLent{get;set;}


33:}


34:


35:[Serializable]


36:publicclassBookGetReturnedEvent:DomainEvent


37:{


38:publiclongReaderId{get;set;}


39:publicDateTimeReturnedDate{get;set;}


40:}


41:


42:[Serializable]


43:publicclassBookLentEvent:DomainEvent


44:{


45:publiclongReaderId{get;set;}


46:publicDateTimeLentDate{get;set;}


47:}


48:


49:[Serializable]


50:publicclassBookReturnedEvent:DomainEvent


51:{


52:publiclongReaderId{get;set;}


53:publiclongBookId{get;set;}


54:}


55:


56:[Serializable]


57:publicclassReaderCreatedEvent:DomainEvent


58:{


59:publicstringLoginName{get;set;}


60:publicstringName{get;set;}


61:}


62:}

[/code]

从上面的代码我们可以看到,所有的领域事件都继承于Apworks.Events.DomainEvent类。同时,在每个领域事件上都是用了System.SerializableAttribute特性,以便系统能够序列化/反序列化这些领域事件,进而使其能够在网络上传输,或能够将其保存到存储系统中。

每当一次操作发生在我们的领域对象上(比如Book和Reader)时,这种操作有可能会改变对象的状态。例如:一个用来改变用户姓名的实例方法ChangeName会产生“用户姓名变更”的效果,于是,领域事件就由此产生了,最先被通知到该事件的就是领域对象本身,它会将这个事件记录下来,然后根据事件中所包含的数据来改变相应的状态。下面让我们看看,如何在Apworks框架的支持下,实现事件的产生和捕获。

现在让我们为之前已经定义好的Book和Reader实体添加一些业务逻辑。根据上面的分析我们不难得出,应用程序的用户可以创建读者和图书,不仅如此,读者可以从图书馆借书,也可以向图书馆还书。而对于书而言呢?它会被从图书馆借出,也会被归还回图书馆。于是,我们可以向Reader实体添加BorrowBook和ReturnBook方法,向Book实体添加LendTo和ReturnBy方法。

回到TinyLibrary.Domain项目,右键单击References节点,选择AddReference…菜单,这将打开AddReference对话框

Projects选项卡下,选择TinyLibrary.Events,然后单击OK按钮

Book类添加下面的代码

[code]1:publicvoidLendTo(Readerreader)


2:{


3:this.RaiseEvent<BookLentEvent>(newBookLentEvent


4:{


5:ReaderId=reader.Id,


6:LentDate=DateTime.Now


7:});


8:}


9:


10:publicvoidReturnBy(Readerreader)


11:{


12:this.RaiseEvent<BookGetReturnedEvent>(newBookGetReturnedEvent


13:{


14:ReaderId=reader.Id,


15:ReturnedDate=DateTime.Now


16:});


17:}


18:


19:[Handles(typeof(BookLentEvent))]


20:privatevoidOnBookLent(BookLentEventevnt)


21:{


22:this.Lent=true;


23:}


24:


25:[Handles(typeof(BookGetReturnedEvent))]


26:privatevoidOnBookReturnedBack(BookGetReturnedEventevnt)


27:{


28:this.Lent=false;


29:}

[/code]

Reader类添加下面的代码

[code]1:publicvoidBorrowBook(Bookbook)


2:{


3:if(book.Lent)


4:thrownewDomainException();


5:book.LendTo(this);


6:this.RaiseEvent<BookBorrowedEvent>(newBookBorrowedEvent


7:{


8:BookId=book.Id,


9:ReaderId=Id,


10:ReaderLoginName=LoginName,


11:ReaderName=Name,


12:BookISBN=book.ISBN,


13:BookPages=book.Pages,


14:BookPubDate=book.PubDate,


15:BookPublisher=book.Publisher,


16:BookTitle=book.Title,


17:BookLent=book.Lent,


18:RegistrationDate=DateTime.Now,


19:DueDate=DateTime.Now.AddMonths(2)


20:});


21:}


22:


23:publicvoidReturnBook(Bookbook)


24:{


25:if(!book.Lent)


26:thrownewDomainException();


27:if(!HasBorrowed(book))


28:thrownewDomainException();


29:book.ReturnBy(this);


30:this.RaiseEvent<BookReturnedEvent>(newBookReturnedEvent


31:{


32:ReaderId=this.Id,


33:BookId=book.Id


34:});


35:}


36:


37:privateboolHasBorrowed(Bookbook)


38:{


39:returnBooks.Any(p=>p.Id.Equals(book.Id));


40:}


41:


42:[Handles(typeof(BookBorrowedEvent))]


43:privatevoidOnBookBorrowed(BookBorrowedEventevnt)


44:{


45:this.Books.Add(Book.Create(evnt.BookId,


46:evnt.BookTitle,


47:evnt.BookPublisher,


48:evnt.BookPubDate,


49:evnt.BookISBN,


50:evnt.BookPages,


51:evnt.BookLent));


52:


53:}


54:


55:[Handles(typeof(BookReturnedEvent))]


56:privatevoidOnBookReturned(BookReturnedEventevnt)


57:{


58:this.Books.RemoveAll(p=>p.Id.Equals(evnt.BookId));


59:}

[/code]

在上面定义的业务方法里,用到了RaiseEvent泛型方法。这个方法会通知Apworks框架已经产生了一个新的事件。领域对象有其自己的方法来处理这些新产生的事件,这些处理方法都被冠以Apworks.Events.HandlesAttribute特性。在这些事件处理方法中,领域对象的状态得到了改变。

目前看来,在这些领域对象中到处充斥着被标以Apworks.Events.HandlesAttribute特性的事件处理方法,当前的Apworks框架仅支持这样的基于反射技术的“事件-处理器”映射。在Apworks的后续版本中,会提供更多的映射策略以供开发人员选择。

总之,领域对象通过方法来表述它们需要处理的业务逻辑,这些方法又通过领域事件来更新领域对象的状态。这就使得CQRS架构能够通过领域事件来跟踪对象状态发生变化的情况。每当RaiseEvent泛型方法被调用时,SourcedAggregateRoot基类会将事件记录到本地的存储中,然后调用相应的标有Apworks.Events.HandlesAttribute特性的事件处理函数。当领域仓储保存聚合时,这些保存在本地的事件将被发布到事件总线,以便于订阅者能够对事件做后续处理。

在Apworks框架中,领域事件是由领域仓储推送到事件总线的【注意:这只是在Apworks的Alpha版本中会这么做,由于这么做会导致TPC(或者称2PC,二次提交)问题,所以在后续版本中会解决这个问题】。而在另一方面,系统会根据事件发布策略,将事件总线中的事件发布到与之相应的事件处理器上。这个过程可以通过异步实现以提高系统的响应度。当事件处理器获得了事件之后,它们会与基础结构层服务打交道以实现更多的功能(比如邮件推送、数据同步等等)。在Apworks系统启动的时候,应用程序就会根据配置文件对事件处理器进行注册。在后续章节中,我将简单介绍ApworksAlpha版本中的配置文件。

就TinyLibraryCQRS而言,我们尽创建一些用于同步“查询数据库”的事件处理器。查询数据库是一个位于基础结构层的存储系统,它用来保存与“查询”相关的数据,比如用于生成报表的数据,或者是用于显示在屏幕上的数据等。在后面的讨论中,你将了解到,TinyLibraryCQRS的查询数据库完全是为了迎合客户端的显示需要而设计的:每张数据表对应的是一个表示层的视图模型(ViewModel)。你会觉得这种设计将造成大量的数据冗余,没错,的确数据冗余很大,但这样做减少了JOIN操作甚至是ORM的引入,它有助于性能的提高【注意:在实际项目中,还是应该根据具体情况来确定查询数据库的设计方案】。

在创建事件处理器之前,我们先讨论一下“查询对象”的概念。“查询对象”可以看成是一种DTO(DataTransferObject),它是查询数据库中数据的一种表现形式,会来回在网络中传输并持久化到存储系统。在Apworks框架的应用中,“查询对象”需要实现Apworks.Queries.IQueryObject接口,并使用System.SerializableAttribute特性来标识这些对象。不仅如此,如果你使用Apworks.Queries.Storage.SqlQueryObjectStorage来访问你的查询数据库(实际上,如果你是在使用关系型数据库系统),那么你就需要事先准备一个XML映射文件。这个XML映射文件告知系统,查询对象将被映射到哪张数据表,以及查询对象的属性将被映射到数据表中的哪些字段。这个XML文件的Schema结构非常简单,在后续章节中会对其进行讨论。

现在,让我们创建几个查询对象。

SolutionExplorer下,右键单击TinyLibraryCQRS解决方案,然后单击Add|NewProject…菜单,这将打开AddNewProject对话框

InstalledTemplates选项卡下,选择VisualC#|Windows,然后选择ClassLibrary,确保所选的.NET版本是.NETFramework4,然后在Name文本框中,输入TinyLibrary.QueryObjects,然后单击OK按钮

右键单击TinyLibrary.QueryObjects项目的References节点,单击AddReference…菜单,这将打开AddReference对话框

.NET选项卡下,选择Apworks然后单击OK按钮

TinyLibrary.QueryObjects项目添加如下代码

[code]usingSystem;


usingSystem.Runtime.Serialization;


usingApworks.Queries;




namespaceTinyLibrary.QueryObjects


{


[Serializable]


[DataContract]


publicclassBookObject:IQueryObject


{


[DataMember]


publiclongId{get;set;}




[DataMember]


publicstringTitle{get;set;}




[DataMember]


publicstringPublisher{get;set;}




[DataMember]


publicDateTimePubDate{get;set;}




[DataMember]


publicstringISBN{get;set;}




[DataMember]


publicintPages{get;set;}




[DataMember]


publicboolLent{get;set;}




[DataMember]




[DataMember]


publicstringLendTo{get;set;}


}




[Serializable]


[DataContract]


publicclassReaderObject:IQueryObject


{


[DataMember]


publiclongId{get;set;}




[DataMember]


publicstringLoginName{get;set;}




[DataMember]


publicstringName{get;set;}




[DataMember]


publiclongAggregateRootId{get;set;}


}




[Serializable]


[DataContract]


publicclassRegistrationObject:IQueryObject


{


[DataMember]


publiclongId{get;set;}




[DataMember]


publiclongBookAggregateRootId{get;set;}




[DataMember]


publiclongReaderAggregateRootId{get;set;}




[DataMember]


publicstringReaderName{get;set;}




[DataMember]


publicstringReaderLoginName{get;set;}




[DataMember]


publicstringBookTitle{get;set;}




[DataMember]


publicstringBookPublisher{get;set;}




[DataMember]


publicDateTimeBookPubDate{get;set;}




[DataMember]


publicstringBookISBN{get;set;}




[DataMember]


publicintBookPages{get;set;}




[DataMember]


publicDateTimeRegistrationDate{get;set;}




[DataMember]


publicDateTimeDueDate{get;set;}




[DataMember]


publicDateTimeReturnedDate{get;set;}




[DataMember]


publicboolReturned{get;set;}


}


}

[/code]

现在可以开始创建事件处理器了。事件处理器会使用捕获的事件来创建查询对象,然后将其保存到查询数据库中。

SolutionExplorer中,右键单击TinyLibraryCQRS解决方案,然后单击Add|NewProject…菜单,这将打开AddNewProject对话框

InstalledTemplates选项卡下,选择VisualC#|Windows,然后选择ClassLibrary,确保所选的.NET版本是.NETFramework4,在Name文本框中,输入TinyLibrary.EventHandlers,然后单击OK按钮

右键单击TinyLibrary.EventHandlers项目的References节点,然后单击AddReference…菜单,这将打开AddReference对话框

.NET选项卡下,选择Apworks然后单击OK按钮

右键单击TinyLibrary.EventHandlers项目的References节点,然后单击AddReference…菜单,这将打开AddReference对话框

Projects选项卡下,选择TinyLibrary.EventsTinyLibrary.QueryObjects,然后单击OK按钮

TinyLibrary.EventHandlers项目加入如下代码:

[code]usingSystem;


usingSystem.Data.SqlTypes;


usingApworks.Queries.Storage;


usingApworks.Storage;


usingTinyLibrary.Events;


usingTinyLibrary.QueryObjects;




namespaceTinyLibrary.EventHandlers


{


publicclassBookBorrowedEventHandler:Apworks.Events.EventHandler<BookBorrowedEvent>


{


publicoverrideboolHandle(BookBorrowedEventtarget)


{


using(IQueryObjectStoragestorage=this.GetQueryObjectStorage())


{


RegistrationObjectregistrationObject=newRegistrationObject


{


BookAggregateRootId=target.BookId,


BookISBN=target.BookISBN,


BookPages=target.BookPages,


BookPubDate=target.BookPubDate,


BookPublisher=target.BookPublisher,


BookTitle=target.BookTitle,


ReaderAggregateRootId=target.ReaderId,


ReaderLoginName=target.ReaderLoginName,


ReaderName=target.ReaderName,


DueDate=target.DueDate,


RegistrationDate=target.RegistrationDate,


Returned=false,


ReturnedDate=(DateTime)SqlDateTime.MinValue


};


storage.Insert<RegistrationObject>(newApworks.Storage.PropertyBag(registrationObject));




PropertyBagpbUpdateFields=newPropertyBag();


pbUpdateFields.Add("Lent",true);


pbUpdateFields.Add("LendTo",target.ReaderName);




PropertyBagpbCriteria=newPropertyBag();


pbCriteria.Add("AggregateRootId",target.BookId);




storage.Update<BookObject>(pbUpdateFields,pbCriteria);


}


returntrue;


}


}




publicclassBookCreatedEventHandler:Apworks.Events.EventHandler<BookCreatedEvent>


{


publicoverrideboolHandle(BookCreatedEventtarget)


{


using(IQueryObjectStoragestorage=this.GetQueryObjectStorage())


{


BookObjectbookData=newBookObject


{


AggregateRootId=target.AggregateRootId,


ISBN=target.ISBN,


Lent=target.Lent,


Pages=target.Pages,


PubDate=target.PubDate,


Publisher=target.Publisher,


Title=target.Title,


LendTo=string.Empty


};


storage.Insert<BookObject>(newApworks.Storage.PropertyBag(bookData));


}


returntrue;


}


}




publicclassBookReturnedEventHandler:Apworks.Events.EventHandler<BookReturnedEvent>


{


publicoverrideboolHandle(BookReturnedEventtarget)


{


using(IQueryObjectStoragequeryStorage=this.GetQueryObjectStorage())


{


PropertyBagpbCriteria=newPropertyBag();


pbCriteria.Add("BookAggregateRootId",target.BookId);


pbCriteria.Add("ReaderAggregateRootId",target.ReaderId);


PropertyBagpbUpdateFields=newPropertyBag();


pbUpdateFields.Add("ReturnedDate",DateTime.Now);


pbUpdateFields.Add("Returned",true);


queryStorage.Update<RegistrationObject>(pbUpdateFields,pbCriteria);




PropertyBagpbUpdateFieldsBooks=newPropertyBag();


pbUpdateFieldsBooks.Add("Lent",false);


pbUpdateFieldsBooks.Add("LendTo",string.Empty);




PropertyBagpbCriteriaBooks=newPropertyBag();


pbCriteriaBooks.Add("AggregateRootId",target.BookId);




queryStorage.Update<BookObject>(pbUpdateFieldsBooks,pbCriteriaBooks);


}


returntrue;


}


}




publicclassReaderCreatedEventHandler:Apworks.Events.EventHandler<ReaderCreatedEvent>


{


publicoverrideboolHandle(ReaderCreatedEventtarget)


{


using(IQueryObjectStoragestorage=this.GetQueryObjectStorage())


{


ReaderObjectreaderObject=newReaderObject


{AggregateRootId=target.AggregateRootId,LoginName=target.LoginName,Name=target.Name};


storage.Insert<ReaderObject>(newPropertyBag(readerObject));


}


returntrue;


}


}


}

[/code]

事件处理器需要继承Apworks.Events.EventHandler类,并实现Handle方法。在Handle方法中,事件处理器将首先通过GetQueryObjectStorage方法获得查询数据库存储对象,然后通过存储对象来保存查询对象。

至此,我们已经创建了聚合、快照、查询对象、领域事件以及事件处理器。在下一步操作中,我们将会创建一些“业务外观对象”来协调业务处理过程,以便更上层的应用组件能够方便地使用我们的领域模型。在CQRS架构中,这种“业务外观对象”就是命令与命令处理器。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐