使用Apworks开发基于CQRS架构的应用程序(五):命令
客户端程序通过命令告知系统“应该做什么”。事实上,这是一种单向的交互过程,客户端程序仅仅向领域模型发送命令请求,它们并不会通过领域模型来查询某些数据信息。在CQRS架构的应用程序中,“查询”是另一部分的内容,这将在接下来的章节中单独讨论。当应用服务器端接收到来自客户端的命令请求后,就会将这些命令推送到命令总线。命令处理器会侦听命令总线,并相应地处理命令请求。现在,让我们在TinyLibraryCQRS解决方案中创建命令与命令解释器。
- 右键单击TinyLibraryCQRS解决方案,单击 Add | New Project… 菜单,这将打开Add New Project对话框
- 在Installed Templates选项卡下,选择Visual C# | Windows,然后选择Class Library,确保所选的.NET版本是.NET Framework 4,然后在Name文本框中,输入TinyLibrary.Commands,并单击OK按钮
- 在TinyLibrary.Commands项目上,右键单击References节点,单击Add Reference…菜单,这将打开Add Reference对话框
- 在.NET选项卡下,选择Apworks,然后单击OK按钮
- 添加下面的代码:
1: using System;
2: using Apworks.Commands;
3:
4: namespace TinyLibrary.Commands
5: {
6: [Serializable]
7: public class BorrowBookCommand : Command
8: {
9: public long ReaderId { get; set; }
10: public long BookId { get; set; }
11:
12: public BorrowBookCommand(long readerId, long bookId)
13: {
14: this.ReaderId = readerId;
15: this.BookId = bookId;
16: }
17: }
18:
19: [Serializable]
20: public class CreateBookCommand : Command
21: {
22: public string Title { get; private set; }
23: public string Publisher { get; private set; }
24: public DateTime PubDate { get; private set; }
25: public string ISBN { get; private set; }
26: public int Pages { get; private set; }
27: public bool Lent { get; private set; }
28:
29: public CreateBookCommand(string title,
30: string publisher,
31: DateTime pubDate,
32: string isbn, int pages, bool lent)
33: {
34: this.Title = title;
35: this.PubDate = pubDate;
36: this.Publisher = publisher;
37: this.ISBN = isbn;
38: this.Pages = pages;
39: this.Lent = lent;
40: }
41:
42: public CreateBookCommand(long id,
43: string title,
44: string publisher,
45: DateTime pubDate,
46: string isbn, int pages, bool lent)
47: : base(id)
48: {
49: this.Title = title;
50: this.PubDate = pubDate;
51: this.Publisher = publisher;
52: this.ISBN = isbn;
53: this.Pages = pages;
54: this.Lent = lent;
55: }
56: }
57:
58: [Serializable]
59: public class RegisterReaderCommand : Command
60: {
61: public string LoginName { get; private set; }
62: public string Name { get; private set; }
63:
64: public RegisterReaderCommand(string loginName, string name)
65: {
66: this.LoginName = loginName;
67: this.Name = name;
68: }
69:
70: public RegisterReaderCommand(long id, string loginName, string name):base(id)
71: {
72: this.LoginName = loginName;
73: this.Name = name;
74: }
75: }
76:
77: [Serializable]
78: public class ReturnBookCommand : Command
79: {
80: public long ReaderId { get; set; }
81: public long BookId { get; set; }
82:
83: public ReturnBookCommand(long readerId, long bookId)
84: {
85: this.ReaderId = readerId;
86: this.BookId = bookId;
87: }
88: }
89: }
看上去命令类与领域事件类的结构非常相似,的确如此,它们同样是继承于某个基类,同样都应用了System.SerializableAttribute特性。但事实上,命令与领域事件是两种完全不同的语义,虽然在某些情况下,两者结构相似,但这不是必然结果。
命令处理器用来处理已经定义的命令。当整个系统启动的时候,它会将Apworks配置文件里已经定义的命令处理器注册到系统中。有关这个配置文件的具体内容会在后续章节中描述。现在,我们创建一些命令处理器来处理上面已定义的命令。
- 在Solution Explorer中右键单击TinyLibraryCQRS解决方案,然后单击Add | New Project…菜单,这将打开Add New Project对话框
- 在Installed Templates选项卡下,选择Visual C# | Windows,然后选择Class Library,确保选择的.NET版本是.NET Framework 4,然后在Name文本框中,输入TinyLibrary.CommandHandlers,然后单击OK按钮
- 在Solution Explorer中,右键单击TinyLibrary.CommandHandlers项目的References节点,然后选择Add Reference…菜单,这将打开Add Reference对话框
- 在.NET选项卡下,选择Apworks然后单击OK按钮
- 在Solution Explorer中,右键单击TinyLibrary.CommandHandlers项目的References节点,然后选择Add Reference…菜单,这将打开Add Reference对话框
- 在Projects选项卡下,选择TinyLibrary.Commands和TinyLibrary.Domain项目,然后单击OK按钮
- 向该项目添加以下代码
1: using Apworks.Commands;
2: using Apworks.Repositories;
3: using TinyLibrary.Commands;
4: using TinyLibrary.Domain;
5:
6: namespace TinyLibrary.CommandHandlers
7: {
8: public class BorrowBookCommandHandler : CommandHandler<BorrowBookCommand>
9: {
10: public override bool Handle(BorrowBookCommand command)
11: {
12: using (IDomainRepository repository = this.GetDomainRepository())
13: {
14: Reader reader = repository.Get<Reader>(command.ReaderId);
15: Book book = repository.Get<Book>(command.BookId);
16: reader.BorrowBook(book);
17: repository.Save(reader);
18: repository.Save(book);
19: }
20: return true;
21: }
22: }
23:
24: public class CreateBookCommandHandler : CommandHandler<CreateBookCommand>
25: {
26: public override bool Handle(CreateBookCommand command)
27: {
28: using (IDomainRepository repository = this.GetDomainRepository())
29: {
30: Book book = Book.Create(command.Id,
31: command.Title,
32: command.Publisher,
33: command.PubDate,
34: command.ISBN,
35: command.Pages,
36: command.Lent);
37: repository.Save<Book>(book);
38: }
39: return true;
40: }
41: }
42:
43: public class RegisterReaderCommandHandler : CommandHandler<RegisterReaderCommand>
44: {
45: public override bool Handle(RegisterReaderCommand command)
46: {
47: using (IDomainRepository repository = this.GetDomainRepository())
48: {
49: Reader reader = Reader.Create(command.Id, command.LoginName, command.Name);
50: repository.Save(reader);
51: }
52: return true;
53: }
54:
55: }
56:
57: public class ReturnBookCommandHandler : CommandHandler<ReturnBookCommand>
58: {
59: public override bool Handle(ReturnBookCommand command)
60: {
61: using (IDomainRepository repository = this.GetDomainRepository())
62: {
63: Reader reader = repository.Get<Reader>(command.ReaderId);
64: Book book = repository.Get<Book>(command.BookId);
65: reader.ReturnBook(book);
66: repository.Save(reader);
67: repository.Save(book);
68: }
69: return true;
70: }
71: }
72: }
从上面的代码我们可以看到,所有的命令处理器都从CommandHandler泛型抽象类继承而来,同时实现其Handle方法。通常,在Handle方法中,命令处理器会根据命令的具体内容,通过领域仓储来获得或更新聚合。要得到领域仓储的实例,可以使用定义在CommandHandler基类中的GetDomainRepository方法。建议在GetDomainRepository的调用端使用using子句以便及时释放资源。
就如我们上面讨论的那样,每当系统得到一个命令时,都将把它推送到命令总线中。当总线被提交的时候,已注册的命令处理器会处理与之对应的命令请求。现在,我们需要创建一个应用级的服务外观来接收客户端命令请求。在TinyLibraryCQRS解决方案中,这个应用服务外观被定义成了一个.NET WCF服务。下一讲将介绍这个.NET WCF服务的创建过程。