欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

程序员文章站 2022-03-10 16:45:43
前言 领域驱动设计,其实已经是一个很古老的概念了,但它的复杂度依旧让学习的人头疼不已。 互联网关于领域驱动的文章有很多,每一篇写的都很好,理解领域驱动设计的人都看的懂。 不过,这些文章对于那些初学者而言,还是如同天书一样。 买本驱动领域的书来看?别逗了,这可不是C#语法入门,哪里有书能写明白的。 想 ......

前言

领域驱动设计,其实已经是一个很古老的概念了,但它的复杂度依旧让学习的人头疼不已。

互联网关于领域驱动的文章有很多,每一篇写的都很好,理解领域驱动设计的人都看的懂。

不过,这些文章对于那些初学者而言,还是如同天书一样。

买本驱动领域的书来看?别逗了,这可不是c#语法入门,哪里有书能写明白的。

想学会领域驱动设计,只有一途——实践,不断的实践。

领域驱动设计是什么?

领域驱动设计就是我们俗称的ddd,英文全拼是domain-driven design。

我认为,理解领域驱动设计的第一步是,顾名思义;所以,让我们先直白的通过名字来解释看看。

领域驱动设计:用业务领域来做模块分割,以领域为核心思想设计框架,用设计好的领域来驱动系统实现。

如何?这样是不是就好理解了。

其实,领域驱动设计,和我们之前常用的模型驱动设计很相似。其核心区别,也就是一个聚合的概念。

虽然,现在看来,codefirst中的聚合太普遍了,但早在十几年前,聚合可是一个让我们头疼的难题,因为那个时代还没有codefirst这么便捷的框架。

什么?你不知道聚合是什么?

别担心,我们在后续实现框架的地方,结合代码把这些聚合啦,值对象啦,等等名词一一讲解。

其实,以现在的技术框架的成熟度,聚合这种东西,不理解也就不理解了,无所谓的。

领域驱动设计的意义

虽然,我不想把领域驱动设计搞的那么神秘,但,事实上,领域驱动设计确实挺难学的。

虽然,我们有了codefirst这样优秀的框架,但那只是针对使用者,而对设计者而言,codefirst并没有减少设计逻辑。所以,想学会领域驱动设计,还是要有一点耐心,并花一点时间,付诸于实践。

虽然,领域驱动设计很复杂,但,我认为它是值得我们付出时间和心血学习的。

因为,驱动领域设计是技术思维的一个分水岭,学会了这种技术思维后,会对框架设计的理解更上一个台阶。

那么,让我们一起做一个领域驱动的框架,在实践中领会这门技艺吧。

领域驱动设计的实现

我们即将编写的框架是基于entity framework的,所以越熟悉entity framework越好,如果你不熟悉ef,那也没关系,因为我们是从头一步一步编写的。

下面让我们一起编写框架吧。

首先,我们创建项目如下:

【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

接下来我们把相关的dll放到kibaddd程序集下待用。

【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

然后我们编写核心代码程序集repository。

首先为repository程序集引入外部dll[entityframework,entityframework.extended,entityframework.sqlserver,codefirststoredprocs],同时,再为程序集引入utility程序集。

然后我们开始设计repository程序集的布局。

【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

如上图所示,我们建立了repository程序集的布局,布局中的文件夹及文件作用如下:

tablemapping文件夹:用于存储数据表的映射关系。

tablemodel文件夹:用于存储数据表模型。

tablerepository文件夹:用于操作数据表。

datebasecontext文件:管理数据库的核心文件。

repositorystatic文件:存储静态的datebasecontext对象,供其他程序集调用,实现线程内,使用同一个datebasecontext对象,减少内存开销。

repository的实现

tablemodel

tablemodel中我们建立了一个表——kiba_user,代码如下:

public partial class kiba_user
{ 
    [key]
    public int userid { get; set; } 
    [required]
    [stringlength(50)]
    public string username { get; set; } 
    [stringlength(200)]
    public string usernickname { get; set; 
    [stringlength(100)]
    public string password { get; set; } 
    public int? age { get; set; } 
    public int? sex { get; set; } 
    [stringlength(500)]
    public string remark { get; set; }  
}

代码很简单,就是把数据表和其字段转换成了类和属性,我们可以把这个类暂时理解为表的数据模型。

tablemapping

tablemapping中我们建立kiba_user的数据模型表与数据库表的映射关系,代码如下所示:

public class kiba_usermap : entitytypeconfiguration<kiba_user>
{
    public kiba_usermap()
    { 
        this.property(e => e.username)
          .isunicode(false);
        this.property(e => e.usernickname)
            .isunicode(false);
        this.property(e => e.password)
            .isunicode(false); 
        this.property(e => e.remark)
            .isunicode(false);  
    }
}

从代码中我们可以发现,映射只对部分字符串类型的属性进行了映射,而其他属性,并没有做映射处理。

原因是这样的,没有显示映射处理的属性,会默认映射到同名的数据表字段上;所以这里节省了一些代码量。

datebasecontext文件

表的数据模型和映射我们已经编写完了,并且,我们还编写了仓储用来对表进行操作;但,这样还不能让数据库和代码模型关联到一起。

我们还需要编写datebasecontext文件,通过datebasecontext文件编写,我们就可以把表模型和表映射与数据库关联了。

datebasecontext文件的代码如下所示:

public partial class datebasecontext : dbcontext
{ 
    public datebasecontext()
        : base("name=datebasecontext")
    {
        this.configuration.validateonsaveenabled = true;//保存时验证
        this.configuration.autodetectchangesenabled = true;//跟踪变化
        this.configuration.lazyloadingenabled = true;//懒惰加载
        this.configuration.proxycreationenabled = true;//代理创建数据库
    }
    #region table list
    public virtual dbset<kiba_user> kiba_user { get; set; }
    #endregion
    protected override void onmodelcreating(dbmodelbuilder modelbuilde
    { 
        modelbuilder.configurations.add(new kiba_usermap()); 
    }
}

代码很简单,下面我们一起来解读下datebasecontext文件里的代码。

首先是datebasecontext继承了dbcontext类;dbcontext可以理解为微软提供的,专门来管理数据库和代码之间的关系的类。

然后再构造函数datebasecontext()里,可以看到,我们在构造函数中做了几项基础配置,代码中已经做了相应的注释。

其中this.configuration.proxycreationenabled属性,我们重点讲一下。

当proxycreationenabled属性设置为true时,我们一旦运行系统,系统会自动的,数据模型同步到数据库,并且会创建一个__migrationhistory表,来记录同步的内容。

ps:【虽然,在领域驱动设计的理念中,是先有表的数据模型,然后在建立表结构。但,这只是理念,我们运用的时候,先建立表在建立数据模型也是可以的。我这里只是为了简单的实现,所以将proxycreationenabled设置为了true】

接下来,我们定义了一个public virtual dbset<kiba_user> kiba_user { get; set; }属性。

kiba_user 这个属性,我们可以把他理解为,数据库表在代码世界的代理,如果我们想对数据库表内容进行查询和修改,只要对这个代理进行修改,就会自动同步到数据库了。

然后我们重写了onmodelcreating方法,在onmodelcreating里,把我们刚刚建立的映射关系添加了进去,这样数据库的表,就被我们立体的加载到了代码世界。

tablerepository

tablerepository中主要是应用datebasecontext来对表进行增删改查的处理,理论上tablerepository是修改数据库的唯一入口;

我们首先,先看下baserepository类;代码如下:

public class baserepository
{ 
    public datebasecontext database
    {
        get
        {
            var context = repositorystatic.datebasecontext as datebasecontext;

            if (context == null)
            {
                context = new datebasecontext();
                repositorystatic.datebasecontext = context;
            }
             
            return context;
        } 
    } 
    public int savechanges()
    {
        int i = 0;
        int savecount = 0;
        bool savefailed;
        do
        {
            savefailed = false;

            try
            {
                savecount++;
                i = database.savechanges();
                logger.debug("savechanges retrun:" + i);

            }
            catch (dbupdateconcurrencyexception ex)
            {
                if (savecount > 3)
                {
                    throw new exception("服务器繁忙,请稍后");
                }
                logger.error("dbupdateconcurrencyexception保存次数:" + savecount, ex);
                savefailed = true;
                try
                {
                    ex.entries.single().reload();
                }
                catch (exception exreload)
                {
                    logger.info("exreload保存失败");
                    throw exreload;
                }
            }
            catch (dbupdateexception ex)
            {
                if (ex.message.contains("与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。"))
                {
                    throw new exception("服务器繁忙,请稍后");
                }
                else
                {
                    throw ex;
                } 
            }
            catch (dbentityvalidationexception dbex)
            {
                logger.error(dbex);
                throw dbex;
            }

            catch (exception ex)
            {
                logger.info("savechanges保存失败");
                throw ex;
            }

        } while (savefailed);
        return i;
    }
}

这里我们主要定义一个属性database和一个方法savechanges。

database就是datebasecontext类的实例,相当于代码世界的数据库。

savechanges就是调用database的savechanges方法来保存数据的修改,当然,我们对该方法进行了一些封装,让他更饱满一些。

然后我们在一起看下表的独立仓储kiba_userrepo,代码如下:

 public class kiba_userrepo : baserepository
 {   
     public list<t> getselector<t>(expression<func<kiba_user, t>> selector, expression<func<kiba_user, bool>> where)
     {
         return database.kiba_user.where(where).select(selector).tolist();
     } 
     public list<kiba_user> getwhere(expression<func<kiba_user, bool>> where, int currentpage, int pagecount)
     {
         return database.kiba_user.where(where).orderbydescending(p => p.userid).skip((currentpage - 1) * pagecount).take(pagecount).tolist();
     }
     public int getwherecount(expression<func<kiba_user, bool>> where)
     {
         return database.kiba_user.where(where).count();
     } 
     public kiba_user add(kiba_user model)
     {
         var addmodel = database.kiba_user.add(model);
         return addmodel;
     }
     public kiba_user delete(kiba_user model)
     {
         var delmodel = database.kiba_user.remove(model);
         return delmodel;
     }
 }

表仓储里的代码很简单,就是普通的linq增删改查。

----------------------------------------------------------------------------------------------------

到此,框架的基本雏形就已经编写完成了,接下来我们做一下简单调用,测试一下。

在kibaddd项目建立测试类——testrun;代码如下:

public class testrun
{ 
    public testrun()
    {
        kiba_userrepo repo = new kiba_userrepo();
        repo.add(new kiba_user() { username = "kiba518" });
        repo.savechanges(); 
    }
}

运行结果:

【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

数据库无中生有的,为我们创建了表kiba_user,并且数据也顺利的插入进了数据库表。

这样,我们的领域驱动框架就已经完成了雏形搭建,下一篇文章将进一步搭建,实现领域驱动独有的聚合。

----------------------------------------------------------------------------------------------------

框架代码已经传到github上了,欢迎大家下载。

github地址:https://github.com/kiba518/kibaddd

----------------------------------------------------------------------------------------------------

注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错,请点击下右下角的推荐】,非常感谢!