zoukankan      html  css  js  c++  java
  • DDD眼中的三层

    软件复杂度的根本,来源于思维的复杂度。

    三层架构

    从DDD看三层

    DDD的三层实现详细架构

    看代码

    业务域 (Domain)

    持久层 (数据层)

    测试和使用的例子

    完整代码下载


     得心应手武器库:

    • Fluent nHibernate

    • nUnit

    • Git (GitHub)

     本文所涉及使用的工具, 见前文: 我的.Net武器库 ------ 新.Net架构必备工具列表

    三层架构

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

    图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; }
        }
    }

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

    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>();
        }
    }

    注意到:

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

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

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

    查询接口的定义:

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

    持久层 (数据层)

    考试映射类:

    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);
            }
        }
    }

    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>();
            }
        }
    }

    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(); }
            }
     
        }
    }

    使用的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();
            }
             
        }
    }

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

    操作数据(模拟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);
            }
             
        }
    }

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

    完全代码下载

    从优秀到卓越 
    皓月碧空,漫野如洗,行往卓越的路上 
  • 相关阅读:
    SVN被锁定解决办法
    onchange监听input值变化及input隐藏后change事件不触发的原因与解决方法(设置readonly后onchange不起作用的解决方案)
    button的格式的问题
    javaScript年份下拉列表框内容为当前年份及前后50年
    ORACLE导入、导出所有数据到文件的SQL语句
    Oracle存储过程学习笔记
    SQlServer的日期相减(间隔)datediff函数
    td中嵌套table,让table完全填充父元素td
    Cause: org.apache.ibatis.executor.ExecutorException: Error getting generated key or setting result to parameter object. Cause: java.sql.SQLException: 不支持的特性
    HTML认知
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2451233.html
Copyright © 2011-2022 走看看