基于业务主键系统解决方案

主键概念不应该局限在数据库的范围

首先,这里主键不是数据库的主键,而是系统设计的概念。在数据层与数据库打交道仍然可以使用技术主键,但是在这之后,就可以放弃技术主键,而转入业务主键的概念。这个本身是可以分离的,而且这种分离更能反映域驱动开发的理念和优势。

主键是聚合根的主键

聚合根是一个最基本的业务单元,也是系统处理的切入点,怎么样拿到一个聚合根,就是通过主键。

组合主键

有些类(聚合根)是关系类,需要不同的数据才能唯一确定,如考试,有考试代码,考区和考试时间才确定, 这种非简单主键可以看做是组合主键,有有其他简单主键组合而成。

这种组合关系,既有对象关系层面,如考试对象可以应用考试定义对象,也有键值层面,如考试代码组成12|99|2013-01-03,还有用户界面环境层面,如用户是在某个考区下,操作处理某个考试。

这些层面的组合关系看起来是一样的,如上面的例子,都是考试和考试定义, 考区的关系。 因而,很容易会想用同一套机制(代码)实现。但是,这个策略是错误的,在实施以后,随着系统的扩大和复杂,会出现扭曲需求来适应所谓框架的情况。这是极其危险的征兆。

业务键

定义为一个接口,和业务对象不是一个概念,是基本数据。

namespace Skight.Arch.Domain.Core
{
    public interface BusinessIdentifier
    {
        string FullCode { get;  }
    }
}

因为是基本数据,定义为struct,但是在考虑,是否还是用class来实现,因为struct不能继承,导致大量的重复代码。一值比较纠结struct和class的区别。

using Skight.Arch.Domain.Core;

namespace Skight.ES.Register.Domain.Core
{
   public struct ExamIdentifier : BusinessIdentifier
    {
        public string FullCode { get; private set; }
        ...
    }
}

业务对象

针对业务对象,定义个接口,把它和键对象联系起来

 public interface CanIdentifyBy<T> 
        where T:BusinessIdentifier
    {
         T Identifier { get; }
    }

    public interface CanIdentifyBy
    {
        BusinessIdentifier Identifier { get; }
    }

考试对象的实现:

 public class Exam : CanIdentifyBy<ExamIdentifier>, CanIdentifyBy
    {
         public virtual ExamIdentifier Identifier { get; set; }

        BusinessIdentifier CanIdentifyBy.Identifier
        {
            get { return Identifier; }
        }
    }

组合业务键

组合键是个比较麻烦的东西,灵活和统一始终是个矛盾。灵活,就能很好扩展;统一,才能减少重复逻辑。 原本想也如同业务键接口一样,定义一个子接口。 但是,接口的限制太大,而且泛型也不能很好的支持。最根本的是,组合业务键是一个接口吗?还是只是数据和流程。

重新整理一下思路:组合业务键只是说,它包含有其他业务键(简单键甚至是另外一个组合键)的信息,仅此而已。 不再把其他的东西纳入进来,如,和其他对象的关系在业务对象中设计,可以一致也可能不一致,没有强制的要求;再如,URL(环境变量)是否需要为考试上下文,再加一个考区上下文,也视情况而定,考试键中是可以找到考区键,页面导航和用户的视角才决定URL的设计。

因此,一个方案就是用装饰(Attribute)区别组合业务键,在组合类上装饰为CompositeAttribute,在组合类属性上装饰为ComponentAttribute。(更进一步的思考,是否这两个装饰可以用在任何其他的组合实现中呢?)

( 本文版权属于© 2015 卓逸天成 | 转载请注明作者和出处:卓逸知识文库 http://kb.skight.com )