应用程序框架实战十五:DDD分层架构之领域实体(验证篇)

  在应用程序框架实战十四:DDD分层架构之领域实体(基础篇)一文中,我介绍了领域实体的基础,包括标识、相等性比较、输出实体状态等。本文将介绍领域实体的一个核心内容——验证,它是应用程序健壮性的基石。为了完成领域实体的验证,我们在前面已经准备好了验证公共操作类异常公共操作类

  .Net提供的DataAnnotations验证方法非常强大,Mvc会自动将DataAnnotations特性转换为客户端Js验证,从而提升了用户体验。但是客户端验证是靠不住的,因为很容易绕开界面向服务端提交数据,所以服务端必须重新验证。换句话说,服务端验证才是必须的,客户端验证只是为了提升用户体验而已。

  为了在服务端能够进行验证,Mvc提供了ModelState.IsValid。

[HttpPost]
public ActionResult 方法名( 实体名 model ) {
    if ( ModelState.IsValid == false ) {
    //验证失败就返回,可能会添加错误消息,也可能要转换为客户端能识别的消息格式
    }
//验证成功就执行后面的代码
}

  在控制器里写if ( ModelState.IsValid == false )判断有几个问题,下面进行一些讨论。

  第一,可能误导初学者,导致分层不清。

  从分层架构的角度来讲,验证属于业务层,在DDD分层架构就是领域层。观察ModelState.IsValid可以发现,这句代码并不是在定义验证规则,而是调用验证。在控制器上直接调用验证可能并不是什么问题,但初学者可能会认为,既然可以在控制器上调用ModelState.IsValid进行验证,那么其它验证代码也可以放到控制器上。

        [HttpPost]
        public ActionResult 方法名( 实体名 model ) {
            if ( ModelState.IsValid == false ) {
                //验证失败就返回
            }
            if ( model.A > 1 ) {
                //验证失败就返回
            }
            if ( model.B > 2 ) {
                //验证失败就返回
            }
            //验证成功就执行后面的代码
        }

  观察上面代码,model.A > 1 已经将本属于领域层的验证定义规则泄露到表现层来了,因为这句代码访问了实体的属性,所谓验证规则,就是对实体属性值进行某些约束。

  既然可以在控制器上写验证,那么就会有人在这里写业务逻辑,所以到了后面,DDD分层架构如同虚设。

  第二,错误的验证时机可能导致验证失败。

  考虑这样的场景,如果实体中某些属性需要调用特定方法来产生结果,当提交到控制器操作时,这些属性还是空值,由于还没有调用特定方法,所以调用ModelState.IsValid可能导致验证失败。

  可以看出,这其实是因为验证的时机不对,验证几乎一定要在某些操作之后来进行,比如初始化操作,当然你可以在调用ModelState.IsValid之前调用特定方法,但这会导致分层不清的问题。

  打个比方,实体中有一个订单号,它是一个字符串类型,并且添加了[Required]特性,需要调用某个方法来创建订单号,当订单实体被提交到控制器操作时,调用ModelState.IsValid就会失败,因为订单号现在是空值。当然你可以把生成订单号的操作提前到创建订单界面之前,这样再提交过来就没问题了,在这个例子上一般是可行的,但有些操作你可能无法提前。

  第三,无法保证验证完整性,可能需要多次验证。

  很多时候,DataAnnotations无法满足我们的需求,所以我们还需要为特定业务需求写一些定制的验证代码。而ModelState.IsValid只能验证DataAnnotations特性,所以这时候验证通过意义不大,因为你需要在后面再验证一次。当然你可以通过一些手段进行扩展,让ModelState.IsValid能够验证你的特定规则,但没有多大必要,因为表现层在分层上的要点就是尽量不要写代码。

  第四,导致冗余代码。

  现在来观察每个ModelState.IsValid判断都干了些什么工作,一般都会转换成客户端的特定消息,比如某种格式的Json,然后返回给客户端显示出来。为了这样一个简单的功能,需要在大量的方法上添加这个判断吗?更好的方法是把这个判断抽象到控制器基类,由基类来进行处理,其它地方有错误抛出异常就可以了。这样可以得到一个统一的异常处理模型,并且消除了大量冗余代码。从这里也可以看出,打造你的应用程序框架,总是从这些不起眼的地方着手,反复考虑每个判断,每行代码是不是可以消灭,把尽量多的东西抽象到框架中,这样在开发过程中更多工作就会自动完成,不断提炼可以让你的工作越来越轻松。

  综上所述,在表现层进行验证并不是一个好方法,执行验证可以在应用层,而定义验证就一定要在领域层。下面开始介绍如何对领域实体进行验证支持。

  现在有一个员工实体,叫Employee,如下所示。

    /// <summary>
    /// 员工
    /// </summary>
    public class Employee : EntityBase {
        /// <summary>
        /// 姓名
        /// </summary>
        [Required( ErrorMessage = "姓名不能为空" )]
        public string Name { get; set; }

        /// <summary>
        /// 性别
        /// </summary>
        [Required( ErrorMessage = "性别不能为空" )]
        public string Gender { get; set; }

        /// <summary>
        /// 年龄
        /// </summary>
        [Range(18,50,ErrorMessage = "年龄范围为18岁到50岁")]
        public int Age { get; set; }

        /// <summary>
        /// 职业
        /// </summary>
        [Required(ErrorMessage = "职业不能为空")]
        public string Job { get; set; }

        /// <summary>
        /// 工资
        /// </summary>
        public double Salary { get; set; }
  }

  为了简单起见,我把一些东西简化了,比如性别用枚举更好,但用了字符串类型,而年龄根据出生年月推断会更好等等。这个例子只是想说明验证的方法,所以不用考虑它的真实性。

  可以看见,在员工实体的属性上添加了一些DataAnnotations特性,这些特性保证了基本的验证。现在定义了验证规则,那么怎么执行验证呢?前面已经说了,用ModelState.IsValid虽然可以实现这个功能,但不是最优方法,所以我们要另谋出路。

  执行验证的最简单方法可能长成这样:employee.Validate(),employee是Employee的实例,Validate是Employee中的一个实例方法。

  注意,现在我们在领域实体中定义了一个方法,这可能会打破你平时的习惯和认识。多年的习惯可能让你对实体的认识就是,只有一堆属性的对象。现在要把思维转变过来,这个转变至关重要,它是你进入面向对象开发的第一步。

  想想看,你现在要进行验证,应该上哪才能找到这个能执行验证的方法呢?如果它不在实体中,那么它可能在表现层,也可能在应用层,还可能在领域服务中,当然还有可能不存在,都还没人实现呢。

  所以我们需要给业务逻辑安家,这样才能帮你统一的管理业务逻辑,并提供唯一的访问点。这个家最好的地方就是实体本身,因为属性全都在这里面,属性上执行的逻辑也全部放进来,就能实现对象级别的高内聚。当属性和逻辑发生变化时,对外的方法接口可能不变,这时候所有变化引起的影响就被限制在实体内部,这样就达到了更低的耦合。

  下面,我们来实现Validate方法。

  首先考虑,这个方法应该被定义在哪呢?是不是每个实体上都定义一个,由于验证对于绝大部分实体都是必须的功能,所以需要定义到层超类型上,即EntityBase。

  再来考虑一下Validate的方法签名。需要一个返回值吗,比如bool值,我在之前的文章已经讨论了返回bool值来指示是否验证通过不是一个好方法,所以我们现在返回void。那么方法参数呢?由于现在是直接在实体上调用,所以参数也不是必须的。

        /// <summary>
        /// 验证
        /// </summary>
        public void Validate() {
        }

  为了实现这个方法,我们必须要能够验证实体上的DataAnnotations特性,这在前面的验证公共操作类已经准备好了。我们在Util.Validations命名空间中定义了IValidation接口,并使用企业库实现了这个接口。

  考虑在EntityBase的Validate方法中该如何获得IValidation的实例呢?依赖程度最低的方法是使用构造方法注入。

    /// <summary>
    /// 领域实体
    /// </summary>
    /// <typeparam name="TKey">标识类型</typeparam>
    public abstract class EntityBase<TKey> {
        /// <summary>
        /// 验证器
        /// </summary>
        private IValidation _validation; 

        /// <summary>
        /// 标识
        /// </summary>
        [Required]
        public TKey Id { get; private set; } 

        /// <summary>
        /// 初始化领域实体
        /// </summary>
        /// <param name="id">标识</param>
        /// <param name="validation">验证器</param>
        protected EntityBase( TKey id, IValidation validation ) {
            Id = id;
            _validation = validation;
        }
}

  在外部通过构造方法把需要的验证器实例传进来,这样甚至不需要在Util.Domains中引用任何程序集。这看起来很诱人,但不要盲目的追求低耦合。考虑验证器的稳定性,这应该非常高,你基本不会随便换掉它,更不会动态更换它。再看构造方法,多了一个参数,这会导致实体使用起来非常困难。所以为了不必要的扩展性牺牲易用性,并不划算。

  另一种方法是通过Validate方法的参数注入,这样可能要好些,但还是会让方法在调用时变得难用。

  应用程序框架只是给你或你的团队在小范围使用的,它不像.Net Framework或第三方框架在全球范围使用,所以你没有必要追求非常高的扩展性,如果发生变化导致你需要修改应用程序框架,你打开来改一下也不是啥大问题,因为框架和项目源码都在你的控制范围内,不见得非要达到OCP原则。当然,如果发生变化的可能性高,你还是需要考虑降低依赖。在依赖性和易用性间取舍,一定要根据实际情况,不要盲目追求低耦合。

  另外再考虑每个实体可能需要更换不同的验证器吗?如果需要,那就得引入工厂方法模式。由于这个验证器只是用来验证DataAnnotations特性的,所以没这必要。

  那么直接在EntityBase中new一个Validation实例好不好呢?嘿嘿,这我也只能说要求太低了。一个折中的方案是使用简单静态工厂,如果需要更换验证器实现,你就把这个工厂打开来改改,其它地方不动,一般来讲这已经够用。

  为Util.Domains引用Util.Validations.EntLib程序集,并在Util.Domains中添加ValidationFactory类。

using Util.Validations;
using Util.Validations.EntLib; 

namespace Util.Domains {
    /// <summary>
    /// 验证工厂
    /// </summary>
    public class ValidationFactory {
        /// <summary>
        /// 创建验证操作
        /// </summary>
        public static IValidation Create() {
            return new Validation();
        }
    }
}

  在EntityBase类中添加Validate方法。

        /// <summary>
        /// 验证
        /// </summary>
        public void Validate() {
            var result = ValidationFactory.Create().Validate( this );
            if ( result.IsValid )
                return;
            throw new Warning( result.First().ErrorMessage );
        }

  我们在Validate方法中将领域实体本身传入Validation实例中进行验证,获得验证结果以后,判断如果验证失败就抛出异常,这里的异常是我们在上一篇定义的异常公共操作类Warning,这样我们就知道是业务上发生了错误,可以把这个抛出的消息显示给客户。

  完成了上面的步骤以后,就可以进行基本的验证了。但是只能用DataAnnotations进行基本验证,很明显无法满足我们的实际需求。

  现在来假想一个验证需求,你的老板是个好人,你们的人力资源系统也是自己开发的,他要求程序员老男人的工资不能小于一万。换句话说,如果是一个程序员老男人,他的信息被保存到数据库的时候,工资不能小于一万,否则就是非法数据。程序员老男人这个词汇很明显不存在,为了加深你的印象,用它来给你演示业务概念如何被映射到系统中。

  程序员老男人包含三个条件:

  1. 职业 == 程序员
  2. 年龄 > 40
  3. 性别 == 男

  你为了验证这个需求,能使用DataAnnotations特性吗,也许你真的可以,但是大部分人都做不到,哪怕做到也异常复杂。

  为了实现这个功能,你可能在调用了Validate()方法之后,紧接着进行判断。

 employee.Validate();
 if ( employee.Job == "程序员" && employee.Age > 40 && employee.Gender == "男" && employee.Salary < 10000 )
    throw new Warning( "程序员老男人的工资不能低于1万" );

  如果你调用Validate是在应用层,这下好了,把验证逻辑泄露到应用层去了,很快,你的分层架构就会乱成一团。

  时刻记住,只要是业务逻辑,你就一定要放到领域层。验证是业务逻辑的一个重要组成部分,这就是说,没有验证,业务逻辑可能是错的,因为进来的数据不在合法范围。

  现在把这句判断移到Employee实体,最合适的地方就是Validate方法中,但这个方法是在基类EntityBase上定义的,为了能够给基类方法添加行为,可以把EntityBase中的Validate方法设为虚方法,这样子类就可以重写了。

  基类EntityBase中的Validate方法修改如下。

        /// <summary>
        /// 验证
        /// </summary>
        public virtual void Validate() {
            var result = ValidationFactory.Create().Validate( this );
            if ( result.IsValid )
                return;
            throw new Warning( result.First().ErrorMessage );
        }

  在Employee实体中重写Validate方法,注意必须调用base.Validate(),否则对DataAnnotations的验证将丢失。

        public override void Validate() {
            base.Validate();
            if ( Job == "程序员" && Age > 40 && Gender == "男" && Salary < 10000 )
                throw new Warning( "程序员老男人的工资不能低于1万" );
        }

  对于应用层来讲,它并不关心具体怎么验证,它只知道调用employee.Validate()就行了。这样就把验证给封装了起来,为应用层提供了一个清晰而简单的API。

  一般说来,DataAnnotations和重写Validate方法添加自定义验证可以满足大部分领域实体的验证需求。但是,如果验证规则很多,而且很复杂,会发现重写的Validate方法很快变成一团乱麻。

  除了代码杂乱无章之外,还有一个问题是,业务概念被淹没在大量的条件判断中,比如Job == "程序员" && Age > 40 && Gender == "男" && Salary < 10000这个条件实际上代表的业务概念是程序员老男人的工资规则。

  另一个问题是,有些验证规则只在某些特定条件下进行,直接固化到实体中并不合适。

  当验证变得逐渐复杂时,就需要考虑将验证从实体中拆分出来。将一条验证规则封装到一个验证规则对象中,这就是规约模式在验证上的应用。规约的概念很简单,它是一个谓词,用来测试一个对象是否满足某些条件。规约的强大之处在于,将一堆相关的条件表达式封装起来,清晰的表达了业务概念。

  把程序员老男人的工资规则提取到一个OldProgrammerSalaryRule类中,如下所示。

    /// <summary>
    /// 程序员老男人的工资验证规则
    /// </summary>
    public class OldProgrammerSalaryRule {
        /// <summary>
        /// 初始化程序员老男人的工资验证规则
        /// </summary>
        /// <param name="employee">员工</param>
        public OldProgrammerSalaryRule( Employee employee ) {
            _employee = employee;
        } 

        /// <summary>
        /// 员工
        /// </summary>
        private readonly Employee _employee; 

        /// <summary>
        /// 验证
        /// </summary>
        public bool Validate() {
            if ( _employee.Job == "程序员" && _employee.Age > 40 && _employee.Gender == "男" && _employee.Salary < 10000 )
                return false;
            return true;
        }
    }

  上面的验证规则对象,通过构造方法接收业务实体,然后通过Validate方法进行验证,如果验证失败就返回false。

  返回bool值的一个问题是,错误描述就拿不到了。为了获得错误描述,我把返回类型从bool改成ValidationResult。

using System.ComponentModel.DataAnnotations; 

namespace Util.Domains.Tests.Samples {
    /// <summary>
    /// 程序员老男人的工资验证规则
    /// </summary>
    public class OldProgrammerSalaryRule {
        /// <summary>
        /// 初始化程序员老男人的工资验证规则
        /// </summary>
        /// <param name="employee">员工</param>
        public OldProgrammerSalaryRule( Employee employee ) {
            _employee = employee;
        } 

        /// <summary>
        /// 员工
        /// </summary>
        private readonly Employee _employee; 

        /// <summary>
        /// 验证
        /// </summary>
        public ValidationResult Validate() {
            if ( _employee.Job == "程序员" && _employee.Age > 40 && _employee.Gender == "男" && _employee.Salary < 10000 )
                return new ValidationResult( "程序员老男人的工资不能低于1万" );
            return ValidationResult.Success;
        }
    }
}

  验证规则对象虽然抽出来了,但是在哪调用它呢?最好的地方就是领域实体的Validate方法,因为这样应用层将非常简单。

  为了能够在领域实体的Validate方法中调用验证规则对象,需要将验证规则添加到该实体中,这可以在Employee中增加一个AddValidationRule方法。

    /// <summary>
    /// 员工
    /// </summary>
    public class Employee : EntityBase {
        //构造方法和属性

        /// <summary>
        /// 验证规则集合
        /// </summary>
        private List<OldProgrammerSalaryRule> _rules; 

        /// <summary>
        /// 添加验证规则
        /// </summary>
        /// <param name="rule">验证规则</param>
        public void AddValidationRule( OldProgrammerSalaryRule rule ) {
            if ( rule == null )
                return;
            _rules.Add( rule );
        } 

        /// <summary>
        /// 验证
        /// </summary>
        public override void Validate() {
            base.Validate();
            foreach ( var rule in _rules ) {
                var result = rule.Validate();
                if ( result == ValidationResult.Success )
                    continue;
                throw new Warning( result.ErrorMessage );
            }
        }
    }

  如果另一个领域实体需要使用验证规则,就要复制代码过去改一下,这显然是不行的,所以需要把添加验证规则抽到基类EntityBase中。为了支持这个功能,首先要为验证规则抽象出一个接口,代码如下。

using System.ComponentModel.DataAnnotations; 

namespace Util.Validations {
    /// <summary>
    /// 验证规则
    /// </summary>
    public interface IValidationRule {
        /// <summary>
        /// 验证
        /// </summary>
        ValidationResult Validate();
    }
}

  在EntityBase中添加AddValidationRule方法,并修改Validate方法,代码如下。

        /// <summary>
        /// 验证规则集合
        /// </summary>
        private readonly List<IValidationRule> _rules; 

        /// <summary>
        /// 添加验证规则
        /// </summary>
        /// <param name="rule">验证规则</param>
        public void AddValidationRule( IValidationRule rule ) {
            if ( rule == null )
                return;
            _rules.Add( rule );
        } 

        /// <summary>
        /// 验证
        /// </summary>
        public virtual void Validate() {
            var result = ValidationFactory.Create().Validate( this );
            foreach ( var rule in _rules )
                result.Add( rule.Validate() );
            if ( result.IsValid )
                return;
            throw new Warning( result.First().ErrorMessage );
        }

  现在让OldProgrammerSalaryRule实现IValidationRule接口,应用层可以像下面这样调用。

employee.AddValidationRule( new OldProgrammerSalaryRule( employee ) );
employee.Validate();

  可以在几个地方为领域实体设置验证规则对象。

  1. 领域实体的构造方法中。
  2. 具体的领域实体重写Validate方法中。
  3. 当工厂创建领域实体(聚合)时。
  4. 领域服务或应用服务调用领域实体进行验证时。

  设置验证规则的要点是,稳定的验证规则尽量放到实体中,以方便使用。

  现在还有一个问题是,验证处理是抛出一个异常,这个异常的消息设置为验证结果集合的第一个消息。这在大部分时候都够用了,但是某些时候对错误的处理会有所不同,比如你现在要显示全部验证失败的消息,这时候将要修改框架。所以把验证的处理提取出来是个不错的方法。

  定义一个验证处理的接口IValidationHandler,这个验证处理接口有一个Handle的处理方法,接收一个验证结果集合的参数,代码如下。

    /// <summary>
    /// 验证处理器
    /// </summary>
    public interface IValidationHandler {
        /// <summary>
        /// 处理验证错误
        /// </summary>
        /// <param name="results">验证结果集合</param>
        void Handle( ValidationResultCollection results );
    }

  由于只需要在特殊情况下更换验证处理实现,所以定义一个默认的实现,代码如下。

    /// <summary>
    /// 默认验证处理器,直接抛出异常
    /// </summary>
    public class ValidationHandler : IValidationHandler{
        /// <summary>
        /// 处理验证错误
        /// </summary>
        /// <param name="results">验证结果集合</param>
        public void Handle( ValidationResultCollection results ) {
            if ( results.IsValid )
                return;
            throw new Warning( results.First().ErrorMessage );
        }
    }

  为了能够更换验证处理器,需要在EntityBase中提供一个方法SetValidationHandler,代码如下。

        /// <summary>
        /// 验证处理器
        /// </summary>
        private IValidationHandler _handler; 

        /// <summary>
        /// 设置验证处理器
        /// </summary>
        /// <param name="handler">验证处理器</param>
        public void SetValidationHandler( IValidationHandler handler ) {
            if ( handler == null )
                return;
            _handler = handler;
        }

  在EntityBase构造方法中初始化_handler = new ValidationHandler(),并修改Validate方法。

        /// <summary>
        /// 验证
        /// </summary>
        public virtual void Validate() {
            var result = ValidationFactory.Create().Validate( this );
            foreach ( var rule in _rules )
                result.Add( rule.Validate() );
            if ( result.IsValid )
                return;
            _handler.Handle( result );
        }

  最后,用提取方法重构来改善一下Validate代码。

        /// <summary>
        /// 验证
        /// </summary>
        public virtual void Validate() {
            var result = GetValidationResult();
            if ( result.IsValid )
                return;
            HandleValidationResult( result );
        } 

        /// <summary>
        /// 获取验证结果
        /// </summary>
        private ValidationResultCollection GetValidationResult() {
            var result = ValidationFactory.Create().Validate( this );
            Validate( result );
            foreach ( var rule in _rules )
                result.Add( rule.Validate() );
            return result;
        } 

        /// <summary>
        /// 验证并添加到验证结果集合
        /// </summary>
        /// <param name="results">验证结果集合</param>
        protected virtual void Validate( ValidationResultCollection results ) {
        } 

        /// <summary>
        /// 处理验证结果
        /// </summary>
        private void HandleValidationResult( ValidationResultCollection results ) {
            _handler.Handle( results );
        }

  注意,这里添加了一个Validate( ValidationResultCollection results )虚方法,这是一个钩子方法,提供它的目的是允许子类向ValidationResultCollection中添加自定义验证的结果。它和重写Validate()方法的区别是,如果重写Validate()方法,那么你将需要自己处理验证,而Validate( ValidationResultCollection results )方法将以统一的方式被handler处理。

  这样,我们就实现了验证规则定义与验证处理的分离。

  最后,再对这个小例子完善一下,可以将“程序员老男人”这个概念封装到Employee的一个方法中。

        /// <summary>
        /// 是否程序员老男人
        /// </summary>
        public bool IsOldProgrammer() {
            return Job == "程序员" && Age > 40 && Gender == "男";
        }

  OldProgrammerSalaryRule验证规则的实现修改为如下代码。

        /// <summary>
        /// 验证
        /// </summary>
        public ValidationResult Validate() {
            if ( _employee.IsOldProgrammer() && _employee.Salary < 10000 )
                return new ValidationResult( "程序员老男人的工资不能低于1万" );
            return ValidationResult.Success;
        }

  这样不仅概念上更清晰,而且当多个地方需要对“程序员老男人”进行验证时,还能体现出更强的封装性。

  由于代码较多,完整代码就不粘贴了,如有需要请自行下载。

  如果你有更好的验证方法,请一定要告诉我,等我理解以后分享给大家。

  .Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

  谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

  下载地址:http://files.cnblogs.com/xiadao521/Util.2014.11.20.1.rar

时间: 2024-12-24 17:10:41

应用程序框架实战十五:DDD分层架构之领域实体(验证篇)的相关文章

应用程序框架实战十四:DDD分层架构之领域实体(基础篇)

上一篇,我介绍了自己在DDD分层架构方面的一些感想,本文开始介绍领域层的实体,代码主要参考自<领域驱动设计C#2008实现>,另外参考了网上找到的一些示例代码. 什么是实体 由标识来区分的对象称为实体. 实体的定义隐藏了几个信息: 两个实体对象,只要它们的标识属性值相等,哪怕标识属性以外的所有属性值都不相等,这两个对象也认为是同一个实体,这意味着两个对象是同一实体在其生命周期内的不同阶段. 为了能正确区分实体,标识必须唯一. 实体的标识属性值是不可变的,标识属性以外的属性值是可变的.如果标识值

应用程序框架实战十八:DDD分层架构之聚合

前面已经介绍了DDD分层架构的实体和值对象,本文将介绍聚合以及与其高度相关的并发主题. 我在之前已经说过,初学者第一步需要将业务逻辑尽量放到实体或值对象中,给实体“充血”,这样可以让业务逻辑高度内聚,并为你提供业务逻辑的唯一访问点.而聚合则是第二步,它将多个相关业务概念包装到单一的概念中,从而大幅简化系统设计,由于受传统数据建模思维影响,我在聚合方面吃过大亏,花了将近一年才真正用起来,为了你少走弯路,我会把一些要点总结出来供你参考. 什么是聚合? 聚合包装一组高度相关的对象,作为一个数据修改的单

应用程序框架实战十六:DDD分层架构之值对象(介绍篇)

前面介绍了DDD分层架构的实体,并完成了实体层超类型的开发,同时提供了验证方面的支持.本篇将介绍另一个重要的构造块——值对象,它是聚合中的主要成分. 如果说你已经在使用DDD分层架构,但你却从来没有使用过值对象,这毫不奇怪,因为多年来养成的数据建模思维已经牢牢把你禁锢,以致于你在使用面向对象方式进行开发时,还是以数据为中心. 当我们完成了基本的需求分析以后,如果说需要进行设计,那么你能想到的就是数据库表及表关系的设计,这就是数据建模.数据建模的主要依据是数据库范式设计,根据要求严格程度的递增分为

应用程序框架实战二十一:DDD分层架构之仓储(介绍篇)

前面已经介绍过Entity Framework的工作单元和映射层超类型的封装,从本文开始,将逐步介绍仓储以及对查询的扩展支持. 什么是仓储 仓储表示聚合的集合. 仓储所表现出来的集合外观,仅仅是一种模拟,除了测试以外,没有理由使用内存中真正的集合来创建仓储. 不应该为所有实体建立仓储,只有聚合才拥有仓储. 仓储用来重建已持久化的聚合,而工厂用于新建聚合. 使用仓储的优点 直接使用Entity Framework的DbContext不是很好吗,为什么还要在DbContext的上方封装一层仓储呢,这

应用程序框架实战十:开发环境准备与学习资料清单

前面几篇已经把废话基本说完了,现在准备进入实战阶段. 第一步是把开发环境准备好,下面是本系列所使用的开发工具及其版本.为了避免你无法顺利打开本系列提供的下载示例,建议你最好跟我使用的版本相同. Visual Studio 2013 ReSharpe 最新版本,目前版本是8.2.2 ReSharpe是一个VS上面的插件,它不是必须的,但它能够大幅提升你的编码速度和代码质量,特别是采用了像DDD这样的架构,整个系统包含大量的Interface,你需要在接口和多个实现之间来回跳转,VS在这方面缺乏支持

应用程序框架实战十九:工作单元层超类型

上一篇介绍了DDD聚合以及与并发相关的各种锁机制,本文将介绍另一个核心元素——工作单元,它是实现仓储的基础. 什么是工作单元 维护受业务事务影响的对象列表,并协调变化的写入和并发问题的解决. 这是<企业应用架构模式>中给出的定义,不过看上去有点抽象.它大概的意思是说,对多个操作进行打包,记录对象上的所有变化,并在最后提交时一次性将所有变化通过系统事务写入数据库. 当然,工作单元不一定是针对数据库的,不过大部分程序员还是工作在关系数据库中,所以我默认你也在使用关系数据库,由此产生的不准确性你就不

应用程序框架实战十二:公共操作类开发技巧(初学者必读)

本文专门为初学者而写,因为很多初学者可能还不了解公共操作类的作用和封装技巧,大部分有经验的程序员都会把自己所碰到的技术问题整理封装成类,这就是公共操作类.公共操作类往往具有一些通用性,也可能专门解决某些棘手问题.公共操作类是应用程序框架的核心,主要目标是解决大部分技术问题.我将在本文介绍封装公共操作类的要点,供初学者参考. 开发公共操作类的原因 很多初学者会奇怪,.Net Framework提供的API相当易用,为何还要多此一举,进行一层封装呢.下面列举封装公共操作类的一些动机. .Net Fr

Apworks框架实战(五):EasyMemo的领域模型设计

在上一讲中,我们已经新建了一个聚合根对象Account,并已经可以开始设计领域模型了.在这一讲中,我们会着重介绍EasyMemo领域模型的分析和设计,并引入Visual Studio Ultimate(旗舰版)版本的特性,介绍在Visual Studio 2013 Ultimate中如何使用体系结构建模工具进行领域模型设计,并自动化产生支持Apworks框架的代码. 界定上下文 由于EasyMemo所需实现的功能非常简单,因此,我们很容易从领域概念中剥离出两个界定上下文:用户账户上下文和用户便签

应用程序框架实战三十六:CRUD实战演练介绍

从本篇开始,本系列将进入实战演练阶段. 前面主要介绍了一些应用程序框架的概念和基类,本来想把所有概念介绍完,再把框架内部实现都讲完了,再进入实战,这样可以让初学者基础牢靠.不过我的精力很有限,文章进度越来越慢,所以准备切换一下介绍顺序,把实战演练提前,以方便你阅读代码. 实战演练介绍 本系列实战演练共分两个部分. 实战演练第一部分介绍如何快速解决CRUD机械操作,这一部分我将手把手带领各位同学从搭建VS环境开始,创建程序集及各程序集间的依赖关系,以及引入依赖的外部DLL,并手工完成代码示例中Ap