三层架构

三层架构

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

图1 常用三层的描述图

 

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

从DDD看三层

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

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

 

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

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

DDD的三层实现详细架构

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

图3 实际架构设计

 

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

看代码

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

业务域 (Domain)

考试类:
     
     
 namespace Skight.Demo.Domain.Examination
{
     public class Exam
     {
         public virtual int Id { get ; set ; }
         public virtual string Code { get ; set ; }
         public virtual string Name { get ; set ; }
     }
}
view raw gistfile1.cs This Gist  brought to you by   GitHub.

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

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

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

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

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

仓储接口:
     
     
using System ;
using System.Collections.Generic ;
using System.Linq.Expressions ;
namespace Skight.Demo.Domain
{
     public interface Repository
     {
         Item get_by_id < Item >( int id );
        
         void save < Item >( Item item );
         Item get_single_item_matching < Item >( Query < Item > query );
         void delete < Item >( Item item );
         IEnumerable < Item > get_all_items_matching < Item >( Query < Item > query );
         IEnumerable < Item > get_all_items < Item >();
     }
}
view raw gistfile1.cs This Gist  brought to you by   GitHub.

注意到:

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

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

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

查询接口的定义:
     
     
namespace Skight.Demo.Domain
{
    public interface Query<Item>
    {
    }
}
view raw gistfile1.txt This Gist  brought to you by   GitHub.

持久层 (数据层)

考试映射类:
     
     
using FluentNHibernate.Mapping ;
using Skight.Demo.Domain.Examination ;
namespace Skight.Demo.NHRepository
{
     public class ExamMap : ClassMap < Exam >
     {
         public ExamMap ()
         {
             Id ( x => x . Id );
             Map ( x => x . Code );
             Map ( x => x . Name );
         }
     }
}
view raw gistfile1.cs This Gist  brought to you by   GitHub.
Fluent nHibernate对仓储接口的实现:
     
     
using System ;
using System.Collections.Generic ;
using System.Linq ;
using NHibernate ;
using NHibernate.Criterion ;
using Skight.Demo.Domain ;
using Skight.Demo.NHRepository.QueryImpls ;
namespace Skight.Demo.NHRepository
{
     public class RepositoryImpl : Repository
     {
         ISession session
         {
             get { return SessionProvider . Instance . CurrentSession ; }
         }
         #region CRUD
         public Item get_by_id < Item >( int id )
         {
             return session . Get < Item >( id );
         }
         public void save < Item >( Item item )
         {
             session . SaveOrUpdate ( item );
         }
         public void delete < Item >( Item item )
         {
             session . Delete ( item );
         }
         #endregion
         #region Advanced Query
         public IEnumerable < Item > get_all_items_matching < Item >( Query < Item > query )
         {
             if ( query == null )
                 throw new ArgumentNullException ();
             if ( query is QueryImplByQueryOver < Item >)
             {
                 QueryOver < Item > my_query = ( query as QueryImplByQueryOver < Item >). Query ;
                 return my_query . GetExecutableQueryOver ( session ). List ();
             }
             throw new ArgumentException (
                 string . Format ( "Query {0} is not type supported." , query . GetType ()));
         }
         public Item get_single_item_matching < Item >( Query < Item > query )
         {
             IEnumerable < Item > result = get_all_items_matching ( query );
             if ( result . Count () > 1 )
                 throw new TooManyRowsMatchingException ();
             if ( result . Count () <= 0 )
                 throw new NoRowsMatchingQueryException ();
             return result . Single ();
         }
         #endregion
         //Shouldn't use every often
         public IEnumerable < Item > get_all_items < Item >()
         {
             return session . CreateCriteria ( typeof ( Item )). List < Item >();
         }
     }
}
view raw gistfile1.cs This Gist  brought to you by   GitHub.
Fluent nHibernate的配置:
     
     
using System ;
using System.IO ;
using System.Reflection ;
using FluentNHibernate.Cfg ;
using FluentNHibernate.Cfg.Db ;
using NHibernate ;
using NHibernate.Cfg ;
using NHibernate.Tool.hbm2ddl ;
namespace Skight.Demo.NHRepository
{
     public class SessionProvider
     {
         #region Instance for use outside
         private static SessionProvider instance ;
         public static SessionProvider Instance {
             get
             {
                 if ( instance == null )
                 {
                     instance = new SessionProvider ();
                 }
                 return instance ;
             }
         }
         #endregion
         #region Set up database
         private const string DBFile = "SkightDemo.db" ;
         public bool IsBuildScheme { get ; set ; }
         public void initilize ()
         {
           
             session_factory = Fluently . Configure ()
                 . Database ( SQLiteConfiguration . Standard . UsingFile ( DBFile ). ShowSql ())
                 . Mappings ( m => m . FluentMappings . AddFromAssembly ( Assembly . GetExecutingAssembly ()))
                 . ExposeConfiguration ( c => c . SetProperty ( "current_session_context_class" , "thread_static" ))
                 . ExposeConfiguration ( build_schema )
                 . BuildSessionFactory ();
         }
         private void build_schema ( Configuration configuration )
         {
             if ( IsBuildScheme )
             {
                 new SchemaExport ( configuration )
                     . Execute ( true , true , false );
             }
         }
         #endregion
         private readonly object lock_flag = new object ();
         private ISessionFactory session_factory ;
         public ISessionFactory SessionFactory {
             get {
                 if ( session_factory == null ) {
                     lock ( lock_flag ) {
                         if ( session_factory == null ) {
                             initilize ();
                         }
                     }
                 }
                 return session_factory ;
             }
         }
         public ISession CreateSession () {
             ISession session = SessionFactory . OpenSession ();
             return session ;
         }
         public ISession CurrentSession
         {
             get { return SessionFactory . GetCurrentSession (); }
         }
     }
}
view raw gistfile1.cs This Gist  brought to you by   GitHub.

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

测试和使用的例子

自动创建数据库:
     
     
using NUnit.Framework ;
namespace Skight.Demo.NHRepository.Tests
{
[TestFixture]
     public class CreateDatabase
     {
[Test]
         public void Run ()
         {
             var provider = SessionProvider . Instance ;
             provider . IsBuildScheme = true ;
             provider . initilize ();
         }
         
     }
}
view raw gistfile1.cs This Gist  brought to you by   GitHub.

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

操作数据(模拟UI):
     
     
using NHibernate ;
using NHibernate.Context ;
using NUnit.Framework ;
using Skight.Demo.Domain ;
using Skight.Demo.Domain.Examination ;
namespace Skight.Demo.NHRepository.Tests
{
[TestFixture]
     public class DataOperation
     {
         private Repository repository ;
         private ISession session ;
         private ITransaction transaction ;
[SetUp]
         public void SetUp ()
         {
             //Dependecy Inject
             repository = new RepositoryImpl ();
             session = SessionProvider . Instance . CreateSession ();
             transaction = session . BeginTransaction ();
             CurrentSessionContext . Bind ( session );
         }
[TearDown]
         public void TearDown ()
         {
            
             transaction . Commit ();
             transaction . Dispose ();
             transaction = null ;
             session . Close ();
             session . Dispose ();
         }
[Test]
         public void create_a_exam ()
         {
             var exam = new Exam ();
             exam . Code = "001" ;
             exam . Name = "计算机考试" ;
             repository . save ( exam );
         }
[Test]
         public void get_the_exam_by_id ()
         {
             var exam = repository . get_by_id < Exam >( 1 );
             Assert . IsNotNull ( exam );
         }
[Test]
         public void delete_the_exam () {
             var exam = repository . get_by_id < Exam >( 1 );
             repository . delete ( exam );
         }
         
     }
}
view raw gistfile1.cs This Gist  brought to you by   GitHub.

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

完全代码下载

C# WinForm简单三层框架源码共享。 本框架采用三层架构实现,大家可直接在上面扩展,进行自己系统的二次开发等。 框架中数据库字符串的配置在根目录的DBCfg.xml文件中,该文件中部分内容已经加密, 文件中有两个数据库的字符串,实际过程中只用一个即可(案例中是我自己的项目的需求) 系统框架简介: 采用广为人知的简单三层DAL(DBUtility),BLL,UI层实现,习惯开发web的朋友一看就明白了 框架默认提供很多方法,如需添加新的操作表,只需要新建一个类,拷贝下基本的代码即可 同时提供数据库字符串修改窗体,方便您的修改。 运行本框架需要创建一张表 sql如下: CREATE TABLE [dbo].[Infos]( [Iid] [int] IDENTITY(1,1) NOT NULL, [title] [varchar](255) COLLATE Chinese_PRC_CI_AS NULL, [details] [text] COLLATE Chinese_PRC_CI_AS NULL, [imgs] [varchar](5000) COLLATE Chinese_PRC_CI_AS NULL, [classify] [varchar](50) COLLATE Chinese_PRC_CI_AS NULL, [publiship] [varchar](30) COLLATE Chinese_PRC_CI_AS NULL, [publishman] [varchar](20) COLLATE Chinese_PRC_CI_AS NULL, [publishtime] [datetime] NULL, [isshow] [varchar](4) COLLATE Chinese_PRC_CI_AS NULL, [istop] [varchar](4) COLLATE Chinese_PRC_CI_AS NULL, [hits] [int] NULL, [notes] [varchar](255) COLLATE Chinese_PRC_CI_AS NULL, CONSTRAINT [PK_Infos] PRIMARY KEY CLUSTERED ( [Iid] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值