从业务域驱动开发看三层架构够不够?

开发 架构
表现层、业务层和数据层的三层架构体系大家都不会陌生。但今天我们要谈的是从业务域驱动开发看三层架构够不够。

三层架构

相对于目前日新月异的新概念,新名词,三层架构已经算得上元老了。虽仍有争议,但业界更多的是共识。

图1 常用三层的描述图

 

足够简单、清晰,我仍要提醒的是,注意层之间连线的箭头,非常之重要,借用UML的定义,箭头表示依赖关系。也就是说,必须先有数据层,才有业务层,然后才有表现层。这又怎么样,小问题。不,这是一个大麻烦!

从DDD看三层

我们暂时靶这个话题放一放,挑个比较新一点的东西。业务域驱动开发(DDD) 近年也是风生水起,红红火火,但它是什么,是怎么回事,似乎就不如三层架构那么妇孺皆知了。

图2 从DDD的角度看三层架构

以业务域为系统的核心,所有其它与业务无关的内容对这个核心来谈,都是外部服务/功能。这里,出于本文说明的需要,独立出了两个较为特别的外部功能,持久层和用户接口。

两个看上去完全不同的架构设计,哪个更对哪个更好?每一个都有大量的拥护者,大量的讨论,互相三间似乎又泾渭分明,至少我们经常看到的文章给我们如此的印象。自然,我们的思考,为什么不能融合在一起呢?其实,它们并不像看起来区别那么大。从名词上,虽然我有意把名称错开,我们也仍能看到之间的对应关系、业务层=业务域,数据层=持久层,表现层=用户接口。当然,这些细节用词的不同仍有必要的,毕竟,它们不完全是一回事。

DDD的三层实现详细架构

好了,抽象的讨论已经足够了,我们也足够糊涂了。细节为王,我们如何实现?来看看这个实际系统的简化架构图。.

图3 实际架构设计

可以看到,在保留了清晰的三层外,重要的是把依赖关系改变了。而所谓依赖注入(DI),只是一种实际的技术实现,完成和实现这种架构设计需求。也可以清晰的看到,图中是以Domain为核心的。 当然,这是一个简化又简化的示意图,不想一开始就把事情弄的复杂.

看代码

最后,来看看具体的代码,才有更好的体验。

业务域 (Domain)

考试类:

  1. namespace Skight.Demo.Domain.Examination{  
  2. public class Exam{public virtual int Id { getset; }  
  3. public virtual string Code { getset; }  
  4. public virtual string Name { getset; }}} 

view raw gistfile1.cs This Gist brought to you by GitHub.

很简单的一个考试类,可以看到,域中的类定义几乎不受持久层(数据库)影响,除了两点:

1.属性ID是从数据表的主键而来;

2. 如果要用nHibernate的Lazy Load每个属性都必须是Virtual。

即使如此,这个类已经足够干净了。我也看到,一些系统实现,专门定义了一个基础类Entity,然后,把ID的定义放在这个类中. 我觉得很没必要, 画蛇添足。

作为示例,这个域类很简单, 但却是核心的核心。项目越往后,这一层膨胀的越厉害。后面几部分,现在看起来比较多,复杂。之后,不会有大的变化,反而显得会越来越简单。

仓储接口:

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq.Expressions;   
  4. namespace Skight.Demo.Domain{  
  5. public interface Repository{Item get_by_id<Item>(int id);   
  6. void save<Item>(Item item);   
  7. Item get_single_item_matching<Item>(Query<Item> query);  
  8. void delete<Item>(Item item);   
  9. IEnumerable<Item> get_all_items_matching<Item>(Query<Item> query);  
  10. IEnumerable<Item> get_all_items<Item>();}} 

view raw gistfile1.cs This Gist brought to you by GitHub.

注意到:

1. 接口命名,我没有加I,这是特意的。

2. 用到了Query<>接口, 这个是对查询的一个抽象。好处是,不需要像大多数的仓储实现,要为每个类建立一个仓储接口,膨胀的很厉害。

Quer接口很简单,没有任何方法和属性,只是为了使用强类型。它的实现类会根据需要, 越来越多。 因为,查询几乎就是数据层的主要功能。

查询接口的定义:

  1. namespace Skight.Demo.Domain  
  2. {  
  3. public interface Query<Item>{}  

view raw gistfile1.txt This Gist brought to you by GitHub.

持久层 (数据层)

考试映射类:

  1. using FluentNHibernate.Mapping;  
  2. using Skight.Demo.Domain.Examination;   
  3. namespace Skight.Demo.NHRepository{  
  4. public class ExamMap:ClassMap<Exam>{  
  5. public ExamMap(){Id(x => x.Id);Map(x => x.Code);  
  6. Map(x => x.Name);}}} 

view raw gistfile1.cs This Gist brought to you by GitHub.

Fluent nHibernate对仓储接口的实现:

  1. using System;using System.IO;  
  2. using System.Reflection;  
  3. using FluentNHibernate.Cfg;  
  4. using FluentNHibernate.Cfg.Db;  
  5. using NHibernate;using NHibernate.Cfg;  
  6. using NHibernate.Tool.hbm2ddl;  
  7. namespace Skight.Demo.NHRepository{      
  8. public class SessionProvider      
  9. {         
  10.  #region Instance for use outside          
  11. private static SessionProvider instance;          
  12. public static SessionProvider Instance {             
  13.  get            {                 
  14.  if (instance == null)                  
  15. {                     
  16.  instance = new SessionProvider();          
  17.         }                  
  18. return instance;            
  19.   }        }       
  20.    #endregion        
  21.   #region Set up database       
  22.    private const string DBFile = "SkightDemo.db";     
  23.      public bool IsBuildScheme { getset; }     
  24.      public void initilize()      
  25.     {                       session_factory = Fluently.Configure()            
  26.       .Database(SQLiteConfiguration.Standard.UsingFile(DBFile).ShowSql())           
  27.        .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))          
  28.       .ExposeConfiguration(c => c.SetProperty("current_session_context_class""thread_static"))         
  29.        .ExposeConfiguration(build_schema)              
  30.     .BuildSessionFactory();        }      
  31.     private void build_schema(Configuration configuration)      
  32.     {            if (IsBuildScheme)        
  33.       {                new SchemaExport(configuration)           
  34.            .Execute(truetruefalse);            
  35.   }        }        #endregion       
  36.    private readonly object lock_flag = new object();     
  37.      private ISessionFactory session_factory;       
  38.    public ISessionFactory SessionFactory {           
  39.    get {                 
  40.  if (session_factory == null) {       
  41.                lock (lock_flag) {                        
  42.  
  43.   if (session_factory == null) {                         
  44.      initilize();                       
  45.    }                    }               
  46.    }                
  47.   return session_factory;         
  48.      }        }          
  49. public ISession CreateSession() {         
  50.      ISession session = SessionFactory.OpenSession();           
  51.    return session;        
  52.   }          
  53. public ISession CurrentSession      
  54.     {          
  55.     get { return SessionFactory.GetCurrentSession(); }      
  56.     }     
  57.  }} 

view raw gistfile1.cs This Gist brought to you by GitHub.

Fluent nHibernate的配置:

view raw gistfile1.cs This Gist brought to you by GitHub.

使用的SQLite文本数据库,作为示例。

测试和使用的例子

自动创建数据库:

  1. using NUnit.Framework;   
  2. namespace Skight.Demo.NHRepository.Tests{[TestFixture]  
  3. public class CreateDatabase{[Test]public void Run(){
  4. var provider = SessionProvider.Instance;provider.IsBuildScheme = true;provider.initilize();}
  5.  }} 

view raw gistfile1.cs This Gist brought to you by GitHub.

这里,只是用测试的形式,实现功能。如果运行这个测试,将自动生成数据库。并且,可以输显示数据库生成脚本。在产品环境下,我就是用这个脚本来做数据库安装的。

操作数据(模拟UI):

  1. using NHibernate;  
  2. using NHibernate.Context;  
  3. using NUnit.Framework;  
  4. using Skight.Demo.Domain;  
  5. using Skight.Demo.Domain.Examination;   
  6. namespace Skight.Demo.NHRepository.Tests{[TestFixture]  
  7. public class DataOperation{  
  8. private Repository repository;  
  9. private ISession session;private ITransaction transaction;  
  10. [SetUp]public void SetUp(){  
  11. //Dependecy Injectrepository=new RepositoryImpl();  
  12. session = SessionProvider.Instance.CreateSession();  
  13. transaction = session.BeginTransaction();  
  14. CurrentSessionContext.Bind(session);  
  15. }[TearDown]public void TearDown(){   
  16. transaction.Commit();  
  17. transaction.Dispose();  
  18. transaction = null;   
  19. session.Close();  
  20. session.Dispose();  
  21. }[Test]public void create_a_exam(){var exam = new Exam();  
  22. exam.Code = "001";exam.Name = "计算机考试";  
  23. repository.save(exam);  
  24. }   
  25. [Test]public void get_the_exam_by_id(){
  26. var exam = repository.get_by_id<Exam>(1);  
  27. Assert.IsNotNull(exam);  
  28. }   
  29. [Test]public void delete_the_exam()   
  30. {var exam = repository.get_by_id<Exam>(1);repository.delete(exam);  
  31. }   
  32. }} 

view raw gistfile1.cs This Gist brought to you by GitHub.

同样,用测试的形式,模拟UI的数据的操作。
首先,运行Create_a_exam()插入一个考试对象。
然后,运行get_the_exam_by_id()获取刚插入的考试。
运行 delete_the_exam()删除考试。

完全代码下载  下载页面     直接下载

原文链接:http://www.cnblogs.com/Wonner/archive/2012/04/16/From_DDD_To_3Tier.html

【编辑推荐】

  1. 架构师向左,项目经理向右??
  2. 浅谈Web自动化测试原理
  3. 百度首席架构师眼中的架构
  4. 给用户和开发者最佳的.Net框架部署方案
  5. XQuery 开发:一种更好的数据库编程语言
责任编辑:彭凡 来源: 博客园
相关推荐

2013-01-09 11:00:20

架构开发三层架构.NET架构

2011-04-19 13:53:41

三层架构

2015-07-02 10:57:11

General框架架构开发

2009-05-06 09:40:04

LINQWEB开发构架

2009-08-26 18:20:42

三层架构

2015-05-25 15:15:53

浪潮

2009-07-28 17:25:14

ASP.NET三层结构

2010-02-22 13:41:49

三层交换机

2010-01-04 17:36:40

2011-08-08 14:14:03

架构

2012-02-03 09:44:33

.NET

2022-07-26 12:33:38

架构设计场景

2022-07-22 10:09:28

架构设计

2018-10-31 14:32:53

数据中心网络架构

2009-07-28 15:08:50

MVC三层架构实例

2009-04-30 15:56:50

三层架构MVCMVP

2018-03-08 15:30:31

超融合架构传统三层架构

2010-01-15 10:33:28

三层交换技术演变

2010-02-22 14:05:33

三层交换机

2012-02-07 10:40:13

MVCJava
点赞
收藏

51CTO技术栈公众号