领域驱动设计实战案例(一):搭建支持DDD的轻量级框架

DDD实战进阶第一波:开发通常业务的大健康行业直销系统(概述)前端

近年来,关于如何开发基于业务的软件系统与产品一直是软件行业的一个重要内容。对于架构师与软件开发人员来讲,开发此类系统头痛的问题大概是如下几个方面:数据库

1.如何将需求准确的转为软件的设计?
2.系统的架构与代码如何有效的体现咱们的设计?
3.如何将领域逻辑与技术分离?
4.如何可以让团队人员的开发可以专一与业务,而不是技术自己?
5.如何交付高质量的软件,如何在出现问题时可以快速定位到代码?
6.如何快速响应需求的变动?
7.如何可以有一个框架或思想限定,让开发人员遵循一个约束,有节奏感的开发?json

为了解决以上问题,软件行业提出了一个成熟的思想(或叫方法论):领域驱动设计(DDD)。经过DDD,咱们可以很好的对需求应对到设计,可以让开发聚焦业务自己,可以让代码体现咱们设计,可以让团队在一个框架内有节奏的开发。后端

有些开发人员或架构师也许了解过DDD,但总时认为很难落地,本系列文章就是经过一个大健康行业的直销系统实战案例,让你们了解如何可以基于DDD的思想和框架,开发一个业务系统。微信

本系列文章将达到如下几个目的:架构

1.熟悉DDD基本概念。
2.基于DDD基本概念构建一个轻量级的框架。
3.基于DDD设计与开发一个实际的直销系统的后端。
4.可以触类旁通,开发其余的业务系统或产品。
5.本系列文章不涉及高性能、大并发的系统开发。并发

本系列文章须要你具有的技术基础:app

1.熟悉C#。
2.熟悉http://Asp.nethttp://Asp.net Core。
3.熟悉EF或EF Core。
4.有必定的系统开发经验。框架

本系列文章大致的内容安排:高并发

1.直销系统概述。
2.DDD基本概念、开发支持DDD基本概念的轻量级框架。
3.开发直销系统的产品上下文。
4.开发直销系统的经销商上下文。
5.开发直销系统的订单上下文。
6.简单了解对前端的其余支持、前端的开发。

直销系统需求(裁剪后,便于你们理解DDD应用便可)概述:

一.产品管理
1.产品上架,产品分为主产品与多个子产品规格。
2.子产品用于经销商购买。
3.子产品有价格与相应的PV(PV用于经销商购买产品后累加,用于核算经销商奖金)。

二.经销商发展
1.子经销商由上级经销商介绍并注册。
2.根据注册时的电子币肯定子经销商的会员级别。
3.被注册的子经销商属于一个介绍层级。
4.一个经销商最多介绍两个子经销商。

三.产品下单
1.经销商经过电子币购买产品。
2.肯定邮寄地址。
3.其余功能与普通电商相似。

搭建支持DDD的轻量级框架

要实现软件设计、软件开发在一个统一的思想、统一的节奏下进行,就应该有一个轻量级的框架对开发过程与代码编写作必定的约束。虽然DDD是一个软件开发的方法,而不是具体的技术或框架,但拥有一个轻量级的框架仍然是必要的,为了开发一个支持DDD的框架,首先须要理解DDD的基本概念和核心的组件。

一.什么是领域驱动设计(DDD)

首先要知道DDD是一种开发理念,核心是维护一个反应领域概念的模型(领域模型是软件最核心的部分,反应了软件的业务本质),而后经过大量模式来指导模型设计与开发。
DDD的通常过程是:首先经过软件需求规格说明书或原型生成一个领域模型(类、类的属性、类与类之间的关系);而后根据模式(应该如何分层?、领域逻辑写在哪?与持久化如何交互?如何协调多对象领域逻辑?如何实现逻辑与数据存储解耦等)指导来实现代码模型。

二.为何使用DDD

DDD能应对复杂性与快速变化:

1.从技术维度实现分层:可以在每层关注本身的事情,好比领域层关注业务逻辑的事情,仓储关注持久化数据的事情,应用服务层关注用例的事情,接口层关注暴露给前端的事情。

2.业务维度:经过将大系统划分层多个上下文,可让不一样团队和不一样人只关注当前上下文的开发。

3.时间维度:经过敏捷式迭代快速验证,快速修正。

三.DDD核心组件

1.界限上下文:

首先要将大系统划分层多个界限上下文,好比大健康行业直销系统能够划分为产品、经销商、订单等几个界限上下文,每一个界限上下文有本身的领域逻辑、数据持久化、用例、接口等。每一个界限上下文根据特色,具体实现方式又不一样,好比有些界限上下文基本没有业务逻辑,就是增删改查,则可使用CRUD最简单的模式;有些界限上线文有必定的业务逻辑,但对高并发、高性能没要求,则可使用经典DDD模式;有些界限上下文有必定的业务逻辑,并且有高性能要求,则可使CQRS模式。

2.实体:

有业务生命周期,采用业务标识符进行跟踪。好比一个订单就是实体,订单有生命周期的,并且有一个订单号惟一的标识它本身,若是两个订单全部属性值所有相同,但订单号不一样,也是不一样的实体。

3.值对象:

无业务生命周期,无业务标识符,一般用于模式实体。好比订单的收货地址、订单支付的金额等就是值对象。

4.服务:

无状态,有行为,一般就是一个用例来协调多个领域逻辑完成功能。

5.聚合:

一般将多个实体和值对象组合到一个聚合中来表达一个完整的概念,好比订单实体、订单明细实体、订单金额值对象就表明一个完整的订单概念,并且生命周期是相同的,而且须要统一持久化到数据库中。

6.聚合根:

将聚合中表达总概念的实体作成聚合根,好比订单实体就是聚合根,对聚合中全部实体的状态变动必须通过聚合根,由于聚合根协调了整个聚合的逻辑,保证一致性。固然其余实体能够被外部直接临时查询调用。

7.服务:

协调聚合之间的业务逻辑,而且完成用例。

8.仓储:

用于对聚合进行持久化,一般为每一个聚合根配备一个仓储便可。仓储可以很好的解耦领域逻辑与数据库。

9.工厂

用于建立复杂的领域对象,可以将领域对象复杂的建立过程保护起来。

了解了DDD的好处与基本的核心组件后,咱们先不急着进入支持DDD思想的轻量级框架开发,也不急于直销系统需求分析和具体代码实现,咱们还少一块,那就是经典DDD的架构,只有了解了经典DDD的架构,你才能知道具体在哪层要实现哪些功能,编写哪些代码,具体在开发DDD的轻量级框架与具体模块代码实现时,才能作到有的放矢。

在这里须要说明的是,咱们的大健康行业直销系统有必定的业务复杂性,没有高并发、高性能的需求,因此不管是经销商上下文、产品上下文仍是订单上下文的具体实现,咱们都将遵循经典DDD架构,而不是CRUD简单方式或CQRS DDD架构的方式。

传统三层架构以及问题:

clipboard.png

问题:
1.过度注重数据访问层,而不重视领域。
2.业务逻辑直接与数据访问层耦合,与领域为核心的DDD思想背道而驰。
3.没有一系列的模式与方法论指导这种分层架构的开发约束。

经典DDD架构:

clipboard.png

1.基础结构层:整个产品或系统的底层支撑

a.经常使用工具、支撑功能:这个.net core项目至少要实现如下的功能:Json配置文件的读取、WebApi返回给前端的基本格式对象的定义、Json序列化与反序列化、加密功能、依赖注入框架的二次封装等。

b.支持DDD框架:这个.net core 项目至少要实现如下的功能:聚合根接口定义、实体接口定义、值对象接口定义、仓储接口定义、仓储接口的EF Core顶层实现(工做单元模式)。

c.聚合根仓储实现:这个.net core项目严格来说其实不属于基础结构层部分,只是因为习惯,把它放到基础结构层这个解决方案文件夹中。它实际上是引用了领域层的领域对象,而且 从领域层对应的聚合根仓储接口中继承,而后实现领域对象持久化到数据库,这样,仓储实现是依赖衣领对象,领域对象与领域逻辑就不须要依赖仓储。领域模型才是系统真正的核心。

2.领域层:界限上下文的领域逻辑

a.首先要实现这个界限上下文的领域对象的POCO模型。

b.而后针对这个界限上下文的全部领域对象,创建每一个领域对象本身的业务逻辑,注意的是,领域对象的业务逻辑最好不与仓储直接发生交互,就算领域逻辑要临时查询数据库也不要这样。

c.定义该界限上下文聚合根的仓储接口,这个接口表明的是聚合根与持久化打交道的基础约束,具体实现仍是在基础结构层的聚合根仓储中实现,这样就实现了解耦。把聚合根仓储接口定义在领域层的意义是能够为领域层的调用方-应用服务层的用例提供对聚合持久化支持。

d.定义该界限上下文的EF Core上下文接口并实现,这样就经过映射关系,EF Core就能够处理领域对象与数据库表之间的映射了。

3.应用服务层:界限上下文的用例

a.某个上下文的应用服务层的某个用例,经过调用领域对象的领域逻辑,完成相关领域逻辑的实现。

b.领域逻辑完成后,应用服务层用例调用领域层的聚合根的仓储接口的方法,完成领域对象的预持久化。(应用服务经过基础结构层的依赖注入框架与Json配置文件找到聚合根仓储接口对应的实现)

c.应用服务层用例而后调用基础结构层的EF Core仓储接口的工做单元方式,完成真正的持久化。(应用服务经过基础接口层的依赖注入框架与Json配置文件找到顶层仓储接口对应的工做单元实现)

d.用例返回给接口层须要的前端所需的json对象格式。

4.接口层:很是薄的一层

a.只须要调用应用服务层用例

b.向前端返回所需的json对象格式

从上述架构特色能够看出,聚合根的仓储与领域逻辑彻底解耦,是经过应用服务层的用例将他们协调起来完成功能。

咱们讲了经典DDD架构对比传统三层架构的优点,以及经典DDD架构每一层的职责后,下面将介绍基础结构层中支持DDD的轻量级框架的主要代码。

这里须要说明的是,DDD轻量级框架可以体现DDD的思想便可,不必作得很重,你也能够根据理解,本身实现支持DDD的框架。

1.实体、聚合根与值对象的顶层体现

实体顶层定义:
public interface IEntity

{
    string Code { get; set; }
    Guid Id { get; set; }
}

Id是一个将来存储到数据库表中的技术主键,Code是领域对象的惟一业务标识符。你也能够扩展这个接口,定义两个实体比较接口(将来实现就是比较两个实体若是Code一致,则表明两个实体相等)。

聚合根顶层定义:
public interface IAggregationRoot:IEntity

{

}

聚合根接口就是从实体接口继承,只是将来的用法能够在仓储中定义持久化时的领域对象必须从这个接口或继承了这个接口的抽象类继承下来的。

值对象顶层定义:

public interface IValueObject

{
    Guid Id { get; set; }
}

值对象接口只须要保留一个技术主键便可,它没有业务标识符。在数据库中,值对象可能做为单独表存储,也能够做为实体的一部分存储。你也能够扩展这个接口,定义两个值对象比较接口(将来实现就是比较两个值对象若是全部属性值一致,则表明两个值对象相等)。

工做单元顶层定义:
public interface IUnitOfWork

{
      void Commit();
}

工做单元接口就定义了一个提交方法,在具体实现时,其实就是对应的EF Core的整个聚合的事务提交方法。

仓储接口顶层定义:
public interface IRepository:IUnitOfWork,IDisposable

{
}

仓储接口从工做单元接口与资源释放接口继承,为将来的数据访问框架和可替换性提供顶层约束。

EF Core顶层仓储持久化实现:

public class EFCoreRepository : IRepository

{
     private readonly DbContext context;
     public EFCoreRepository(DbContext context)
    {
         this.context = context;
    }
     public void Commit()
    {
         try
        {
            context.SaveChanges();
        }
        catch(Exception error)
        {
             throw error;
        }
    }

    public void Dispose()
    {
        context.Dispose();
    }
}

从上述代码中能够看到,主要实现了仓储接口的Commit方法,其实就是使用了EF Core的DbContext数据访问上下文类的SaveChanges()事务提交方法,应用服务层的用例就能够获取到某个聚合根的当前状态,而后调用仓储接口的Commit方法,实现了整个聚合全部对象的一次性事务提交。

2.经常使用工具类的实现

咱们还应该定义另外一个项目,这个项目是整个系统都须要使用到的工具,其中至少应该包括http://Asp.net Core Json配置文件的读,Json序列化与反序列化,加密,依赖注入,返回给前端的对象格式定义等,这里先列出几个须要的,其余的在后面具体案例中在补充。

http://Asp.net Core Json配置文件读取:

Json配置文件会存储咱们的一些配置信息,好比数据库链接字符串,微信AppId与AppSecure等,因此须要有功能支持Json配置文件的Key到Value的读取

public class AppSetting

{
      private static IConfigurationSection appsections = null;
      public static void SetAppSetting(IConfigurationSection section)
    {
        appsections = section;
    }
     public static string GetAppSetting(string key)
    {
         string str = "";
         if (appsections.GetSection(key) != null)
        {
            str = appsections.GetSection(key).Value;
        }
        return str;
    }
}

返回前端的对象格式定义:

咱们的应用服务层将返回WebApi接口必定的数据格式,WebApi接口也会将这个数据返回给前端,前端拿到后就会作相应的处理。

public class ResultEntity<T>

{
    public bool IsSuccess { get; set; }
    public string Msg { get; set; }
    public T Data { get; set; }
    public int ErrorCode { get; set; }
    public int Count { get; set; }
}

public class BaseAppSrv

{
     protected ResultEntity<T> GetResultEntity<T>(T vobj,string msg="未成功获取到对象",int errorcode = 0)
    {
          var ueresult = new ResultEntity<T>();
          var issuccess = true;
          if(vobj is int && Convert.ToInt32(vobj) <= 0)
        {
               issuccess = false;
        }
         else if(vobj is bool && !Convert.ToBoolean(vobj))
        {
               issuccess = false;
        }
        else if(vobj is string && string.IsNullOrEmpty(Convert.ToString(vobj)))
        {
               issuccess = false;
        }
        if (!issuccess)
        {
              ueresult.Msg = msg;
              ueresult.ErrorCode = 200;
        }
        ueresult.IsSuccess = issuccess;
        ueresult.Data = vobj;
        return ueresult;
    }
}

将来全部的用例都将从BaseAppSrv继承,最终返回的格式都是ResultEntity<T>。

好了,基本的框架搭建好了,下一章就能够直接进入案例,看案例中如何经过DDD思想进行设计,并经过经典DDD架构与DDD轻量级框架进行实际业务系统的代码编写。

相关文章
相关标签/搜索