從業(yè)務域驅(qū)動開發(fā)看三層架構(gòu)夠不夠?
三層架構(gòu)
相對于目前日新月異的新概念,新名詞,三層架構(gòu)已經(jīng)算得上元老了。雖仍有爭議,但業(yè)界更多的是共識。
圖1 常用三層的描述圖
足夠簡單、清晰,我仍要提醒的是,注意層之間連線的箭頭,非常之重要,借用UML的定義,箭頭表示依賴關(guān)系。也就是說,必須先有數(shù)據(jù)層,才有業(yè)務層,然后才有表現(xiàn)層。這又怎么樣,小問題。不,這是一個大麻煩!
從DDD看三層
我們暫時靶這個話題放一放,挑個比較新一點的東西。業(yè)務域驅(qū)動開發(fā)(DDD) 近年也是風生水起,紅紅火火,但它是什么,是怎么回事,似乎就不如三層架構(gòu)那么婦孺皆知了。
圖2 從DDD的角度看三層架構(gòu)
以業(yè)務域為系統(tǒng)的核心,所有其它與業(yè)務無關(guān)的內(nèi)容對這個核心來談,都是外部服務/功能。這里,出于本文說明的需要,獨立出了兩個較為特別的外部功能,持久層和用戶接口。
兩個看上去完全不同的架構(gòu)設計,哪個更對哪個更好?每一個都有大量的擁護者,大量的討論,互相三間似乎又涇渭分明,至少我們經(jīng)常看到的文章給我們?nèi)绱说挠∠?。自然,我們的思考,為什么不能融合在一起呢?其實,它們并不像看起來區(qū)別那么大。從名詞上,雖然我有意把名稱錯開,我們也仍能看到之間的對應關(guān)系、業(yè)務層=業(yè)務域,數(shù)據(jù)層=持久層,表現(xiàn)層=用戶接口。當然,這些細節(jié)用詞的不同仍有必要的,畢竟,它們不完全是一回事。
DDD的三層實現(xiàn)詳細架構(gòu)
好了,抽象的討論已經(jīng)足夠了,我們也足夠糊涂了。細節(jié)為王,我們?nèi)绾螌崿F(xiàn)?來看看這個實際系統(tǒng)的簡化架構(gòu)圖。.
圖3 實際架構(gòu)設計
可以看到,在保留了清晰的三層外,重要的是把依賴關(guān)系改變了。而所謂依賴注入(DI),只是一種實際的技術(shù)實現(xiàn),完成和實現(xiàn)這種架構(gòu)設計需求。也可以清晰的看到,圖中是以Domain為核心的。 當然,這是一個簡化又簡化的示意圖,不想一開始就把事情弄的復雜.
看代碼
最后,來看看具體的代碼,才有更好的體驗。
業(yè)務域 (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.
很簡單的一個考試類,可以看到,域中的類定義幾乎不受持久層(數(shù)據(jù)庫)影響,除了兩點:
1.屬性ID是從數(shù)據(jù)表的主鍵而來;
2. 如果要用nHibernate的Lazy Load每個屬性都必須是Virtual。
即使如此,這個類已經(jīng)足夠干凈了。我也看到,一些系統(tǒng)實現(xiàn),專門定義了一個基礎(chǔ)類Entity,然后,把ID的定義放在這個類中. 我覺得很沒必要, 畫蛇添足。
作為示例,這個域類很簡單, 但卻是核心的核心。項目越往后,這一層膨脹的越厲害。后面幾部分,現(xiàn)在看起來比較多,復雜。之后,不會有大的變化,反而顯得會越來越簡單。
倉儲接口:
- 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<>接口, 這個是對查詢的一個抽象。好處是,不需要像大多數(shù)的倉儲實現(xiàn),要為每個類建立一個倉儲接口,膨脹的很厲害。
Quer接口很簡單,沒有任何方法和屬性,只是為了使用強類型。它的實現(xiàn)類會根據(jù)需要, 越來越多。 因為,查詢幾乎就是數(shù)據(jù)層的主要功能。
查詢接口的定義:
- namespace Skight.Demo.Domain
- {
- public interface Query<Item>{}
- }
view raw gistfile1.txt This Gist brought to you by GitHub.
持久層 (數(shù)據(jù)層)
考試映射類:
- 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對倉儲接口的實現(xiàn):
- 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文本數(shù)據(jù)庫,作為示例。
測試和使用的例子
自動創(chuàng)建數(shù)據(jù)庫:
- 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.
這里,只是用測試的形式,實現(xiàn)功能。如果運行這個測試,將自動生成數(shù)據(jù)庫。并且,可以輸顯示數(shù)據(jù)庫生成腳本。在產(chǎn)品環(huán)境下,我就是用這個腳本來做數(shù)據(jù)庫安裝的。
操作數(shù)據(jù)(模擬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的數(shù)據(jù)的操作。
首先,運行Create_a_exam()插入一個考試對象。
然后,運行g(shù)et_the_exam_by_id()獲取剛插入的考試。
運行 delete_the_exam()刪除考試。
原文鏈接:http://www.cnblogs.com/Wonner/archive/2012/04/16/From_DDD_To_3Tier.html
【編輯推薦】