由于事件溯源(Event Sourcing)的需要,领域事件需要被保存到外部的存储系统中。由于事件本身描述了在特定对象上所发生的事情,因此,为了能够跟踪对象状态的变化过程以获得Event Audit的能力,我们总是将事件的数据保存在存储系统中,而从来不去删除它们。或许你会认为,这样做有点极端,时间长了,存储系统中的数据量将变得非常庞大。遇到这种情况,你需要引入备份和归档策略,而不是直接将过期的数据删除,因为,存储成本是便宜的,但数据却是有价值的。
对于一些生命周期比较长的领域对象而言,发生在它们身上的事件数量会随着时间的推移而增大,甚至会变得巨大。于是,通过使用这大量的事件数据来重建领域模型将变得非常耗时。因此,我们需要引入“快照”的概念。当领域对象满足一个特定的条件时(快照策略),系统就会为之生成一个“快照”。比如,系统架构师可以设置:当有n个事件发生在某个领域对象上时,将会为这个对象产生“快照”。下次当系统需要重建这个对象时,只需要从“快照”存储中获得最近一次的快照数据,通过快照数据产生对象,进而再逐个地将发生在“快照”之后的事件逐个地“重现”在这个对象上,于是,对象就可以被快速地还原到最后一个事件发生时的状态。
就Apworks框架而言,当前所支持的快照策略非常简单。它由Apworks.Events.Storage.IDomainEventStorage.CanCreateOrUpdateSnapshot方法定义,而Apworks.Events.Storage.SqlDomainEventStorage类则用这样一种快照策略实现了这个接口:每当第1000个事件发生时,系统就会为相应的领域对象做一次快照。因此,如果你打算在你的应用程序中继续采用SQL Server作为事件溯源的存储系统,并且打算定义自己的快照策略的话,你可以创建一个继承于Apworks.Events.Storage.SqlDomainEventStorage的类,并重写CanCreateOrUpdateSnapshot方法。如果你打算选用其它的存储系统(比如MySQL,Oracle,NoSQL方案或者内存数据库等),那么你就需要创建一个实现Apworks.Events.Storage.IDomainEventStorage接口的类,然后实现CanCreateOrUpdateSnapshot方法。
现在让我们返回到TinyLibraryCQRS解决方案,在前面的章节中,我们创建了两个聚合根:Reader和Book,在这两个聚合根里,有两个未实现的方法。现在我们来实现这两个方法以便让我们的应用程序支持快照功能。
1: using System;
2: using Apworks.Snapshots;
3:
4: namespace TinyLibrary.Domain.Snapshots
5: {
6: [Serializable]
7: public class BookSnapshot : Snapshot
8: {
9: public string Title { get; set; }
10: public string Publisher { get; set; }
11: public DateTime PubDate { get; set; }
12: public string ISBN { get; set; }
13: public int Pages { get; set; }
14: public bool Lent { get; set; }
15: }
16: }
1: using System;
2: using System.Collections.Generic;
3: using Apworks.Snapshots;
4:
5: namespace TinyLibrary.Domain.Snapshots
6: {
7: [Serializable]
8: public class ReaderSnapshot : Snapshot
9: {
10: public string LoginName { get; set; }
11: public string Name { get; set; }
12:
13: public List<BookSnapshot> Books { get; set; }
14: }
15: }
1: protected override void DoBuildFromSnapshot(ISnapshot snapshot)
2: {
3: BookSnapshot bs = (BookSnapshot)snapshot;
4: this.Title = bs.Title;
5: this.Publisher = bs.Publisher;
6: this.PubDate = bs.PubDate;
7: this.ISBN = bs.ISBN;
8: this.Pages = bs.Pages;
9: this.Lent = bs.Lent;
10: }
11:
12: protected override ISnapshot DoCreateSnapshot()
13: {
14: BookSnapshot bs = new BookSnapshot();
15: bs.ISBN = this.ISBN;
16: bs.Lent = this.Lent;
17: bs.Pages = this.Pages;
18: bs.PubDate = this.PubDate;
19: bs.Publisher = this.Publisher;
20: bs.Title = this.Title;
21: return bs;
22: }
1: protected override void DoBuildFromSnapshot(ISnapshot snapshot)
2: {
3: ReaderSnapshot rs = (ReaderSnapshot)snapshot;
4: this.Books.Clear();
5: foreach (var bk in rs.Books)
6: {
7: this.Books.Add(Book.Create(bk.AggregateRootId,
8: bk.Title,
9: bk.Publisher,
10: bk.PubDate,
11: bk.ISBN,
12: bk.Pages,
13: bk.Lent));
14: }
15: this.LoginName = rs.LoginName;
16: this.Name = rs.Name;
17: }
18:
19: protected override ISnapshot DoCreateSnapshot()
20: {
21: ReaderSnapshot rs = new ReaderSnapshot();
22: rs.Books = new List<BookSnapshot>();
23: foreach (var bk in this.Books)
24: {
25: rs.Books.Add((BookSnapshot)bk.CreateSnapshot());
26: }
27: rs.LoginName = this.LoginName;
28: rs.Name = this.Name;
29: return rs;
30: }
1: using System;
2: using Apworks;
3: using Apworks.Snapshots;
4:
5: namespace TinyLibrary.Domain
6: {
7: public class Book : SourcedAggregateRoot
8: {
9: public string Title { get; private set; }
10: public string Publisher { get; private set; }
11: public DateTime PubDate { get; private set; }
12: public string ISBN { get; private set; }
13: public int Pages { get; private set; }
14: public bool Lent { get; private set; }
15:
16: public Book() : base() { }
17: public Book(long id) : base(id) { }
18:
19: protected override void DoBuildFromSnapshot(ISnapshot snapshot)
20: {
21: BookSnapshot bs = (BookSnapshot)snapshot;
22: this.Title = bs.Title;
23: this.Publisher = bs.Publisher;
24: this.PubDate = bs.PubDate;
25: this.ISBN = bs.ISBN;
26: this.Pages = bs.Pages;
27: this.Lent = bs.Lent;
28: }
29:
30: protected override ISnapshot DoCreateSnapshot()
31: {
32: BookSnapshot bs = new BookSnapshot();
33: bs.ISBN = this.ISBN;
34: bs.Lent = this.Lent;
35: bs.Pages = this.Pages;
36: bs.PubDate = this.PubDate;
37: bs.Publisher = this.Publisher;
38: bs.Title = this.Title;
39: return bs;
40: }
41: }
42:
43: public class Reader : SourcedAggregateRoot
44: {
45: public string LoginName { get; private set; }
46: public string Name { get; private set; }
47: public List<Book> Books { get; private set; }
48:
49: public Reader() : base() { Books = new List<Book>(); }
50: public Reader(long id) : base(id) { Books = new List<Book>(); }
51:
52: protected override void DoBuildFromSnapshot(ISnapshot snapshot)
53: {
54: ReaderSnapshot rs = (ReaderSnapshot)snapshot;
55: this.Books.Clear();
56: foreach (var bk in rs.Books)
57: {
58: this.Books.Add(Book.Create(bk.AggregateRootId,
59: bk.Title,
60: bk.Publisher,
61: bk.PubDate,
62: bk.ISBN,
63: bk.Pages,
64: bk.Lent));
65: }
66: this.LoginName = rs.LoginName;
67: this.Name = rs.Name;
68: }
69:
70: protected override ISnapshot DoCreateSnapshot()
71: {
72: ReaderSnapshot rs = new ReaderSnapshot();
73: rs.Books = new List<BookSnapshot>();
74: foreach (var bk in this.Books)
75: {
76: rs.Books.Add((BookSnapshot)bk.CreateSnapshot());
77: }
78: rs.LoginName = this.LoginName;
79: rs.Name = this.Name;
80: return rs;
81: }
82: }
83: }
在下一篇文章中,我们将引入领域事件,并逐步向对象中加入领域逻辑。