三层架构
相对于目前日新月异的新概念,新名词,三层架构已经算得上元老了。虽仍有争议,但业界更多的是共识。
图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.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.
Fluent nHibernate的配置:
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 Injectrepository=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()删除考试。
原文链接:http://www.cnblogs.com/Wonner/archive/2012/04/16/From_DDD_To_3Tier.html
【编辑推荐】