当前位置: 首页 > 编程学习 > 软件工程 > 领域驱动 > 正文

从零开始使用CodeArt实践最佳领域驱动开发(五)

2018-04-22 来源:博客园/一个人抽烟

7. 领域层的命令模式

在上个章节里我们设计并编码了领域对象Permission,但是目前Permission并没有任何行为上的设计。这是因为我们不建议“凭空去制造行为”,而是在领域对象第一个版本的代码实现之后就立即使用它。在使用过程中观察外界(应用层或其他领域对象)对它的需求,这些需求往往暗藏了进一步揭露对象本质特征的提示。我们可以根据这些提示逐渐挖掘出该对象更多的行为特征,结合CA里相关的设计原则,最终我们可以确认领域对象该如何提供哪些行为方法。所以,大家不要在意领域对象在设计初期有点类似“贫血模型”,因为这不是它的最终形态,这只是它成长的起点。那如果经过一番尝试后,我们还是没有为对象添加任何方法呢?这种情况的确存在,有些对象存在的意义只是被别的对象引用,自身并没有提供任何行为方法,但是如果你的项目里大量充斥着没有行为的领域对象,这就值得你警惕了,肯定是设计上出现了重大的错误。不过只要大家依照教程里的设计原则实施项目,基本上不会出现这种情况,所以也不必太过担心。

因此,我们暂且放下对领域模型的思考,将思路转移到应用层,考虑应用层对Permission有什么需求。涉及到应用层我们又不得不考虑表现层对应用层会有哪些需求,最终我们会发现要思考的问题其实是终端用户会有什么需求。所以,我们考虑第一个问题:用户怎么使用权限机制?这个话题在前文也讨论过,这里我们只用考虑用户使用权限的第一个必须操作是什么?

当然是为项目创建权限描述了,我们需要定义整个项目提供了哪些权限,只有定义了权限信息后系统才能以此为参考识别登录者可以使用哪些功能。因此,创建权限信息是用户的第一个操作需要。但是我们要从使用者的角度对这个需求点进一步修正:“系统管理员可以定义整个项目提供了哪些功能”。系统管理员指的是项目搭建好后,设置项目初始参数的人,这类人比普通用户更加了解系统(往往是我们自己)。从UI界面操作体验上来讲,提示使用者“定义整个项目提供了哪些功能”比“定义整个项目提供了哪些权限”要更容易理解。

分析到这里,我们就知道不论UI界面怎么描述,应用层都要提供“创建权限”的功能。所以我们以创建权限为示例讲述如何建立领域命令。

在CA的4层架构里,应用层由多个服务组成, 这些服务不会直接操作领域对象,它们主要是通过调用子系统提供的命令来完成任务。也就是说,AccountSubsystem里除了定义领域模型的内容外还会提供一组命令给应用层使用,应用层不会直接使用领域模型,一切应用操作都是通过这些命令来下达给领域层去实现的。下面贴出创建权限的命令代码并作出说明:

using System;
using System.Linq;

using CodeArt;
using CodeArt.DomainDriven;

namespace AccountSubsystem
{
    public sealed class CreatePermission : Command<Permission>
    {
        private string _name;

        public string Description
        {
            get;
            set;
        }

        public string MarkedCode
        {
            get;
            set;
        }

        public CreatePermission(string name)
        {
            _name = name;
        }

        protected override Permission ExecuteProcedure()
        {
            Permission permission = new Permission(Guid.NewGuid())
            {
                Name = _name,
                MarkedCode = this.MarkedCode ?? string.Empty,
                Description = this.Description ?? string.Empty
            };
            
            var repository = Repository.Create<IPermissionRepository>();
            repository.Add(permission);

            return permission;
        }
    }
}

1)public sealed class CreatePermission : Command<Permission>,类型CreatePermission继承自命令的泛型版本Command<T>,泛型参数表示该命令需要返回类型为T的结果,这里表示的是执行CreatePermission命令后,需要返回一个Permission权限对象。Command<T>来自CodeArt.DomainDriven命名空间,由程序集CodeArt.DomainDriven提供。CA规定除了查询操作外所有命令对象都需要继承自Command或Command<T>。

2) private string _name;  私有成员 _name用于接收外界传递的权限名称的参数,请注意,因为name是必填项,所以以构造函数参数的形式强制要求外界必须传递name。名称为什么必填? 这跟传统开发里表单必填项没有任何关系,而是因为领域模型Permission的属性Name定义的固定规则包含了不能为空的限制。

3)Description和MarekdCode被设计成为属性并且公开了GET和SET方法,表示外界可以为权限选择性填写描述和标识码。请注意这不是领域属性,这是命令为了接收参数而编写的.NET属性,事实上你可以为这两个属性任意命名(比如 desp或mc,不过考虑可阅读性我们没有这样命名)。这两个属性之所以不必填也是由于Permission对应的属性规则导致的,不再复述。

4)protected override Permission ExecuteProcedure() 是派生类必须实现基类Command<Permission>的执行命令的方法。

5)在执行方法中,第一个段落代码就是构造领域对象Permission。这里要注意的是属性赋值语句:MarkedCode = this.MarkedCode ?? string.Empty; 该代码的意思是,如果调用方没有传递MarkedCode参数(没有传递就为null),那么就使用string.Empty作为标识码构造对象。前文说明过,在CA里所有的领域对象及领域属性值都不能为null,这里就体现了这一点,至于原因前文有详细说明不再复述。

6)var repository = Repository.Create<IPermissionRepository>(); 这句代码很容易理解:创建一个接口类型为IPermissionRepository的仓储实例,该接口提供了关于Permission对象的持久化操作。接口的定义和实现稍后有详细说明。