【開源】OSharp框架解說系列(4):架構分層及IoC

〇、前言

  前面構造了一個後臺管理的界面佈局,下面開始講解整個項目的分層設計。

  關於分層,網上已經存在相當多的討論了,這也是一個程序員初學架構設計最先會碰到的問題。

  • 該不該分層?
  • 怎樣分層?
  • 層與層之間是否需要解耦?是否需要設計接口?接口是否是多餘的?

  看完OSharp的分層設計,我想,你應該多少能得到一些啓示。

注:OSharp 開發框架的前身是《MVC實體架構設計》系列中講到的那個架構示例,所以有很多知識點那個系列講到了,就不會在這個系列再重複了,如果有什麼覺得不太明白的可以參考《MVC實體架構設計》系列。

一、目錄

〇、前言

一、目錄

二、框架整體分層設計

三、分層解耦與依賴注入

四、開源說明

系列導航

二、框架整體分層設計

該不該分層以及分層的好處

  關於該不該分層以及分層的好處,網上已經有相當多的討論了,這裏就不再贅述。這裏引述一篇比較有代表性的文章《分層式結構的優缺點》以供參考,全文如下:

分層式結構究竟其優勢何在?Martin Fowler在《Patterns of Enterprise Application Architecture》一書中給出了答案:

  1. 開發人員可以只關注整個結構中的其中某一層;
  2. 可以很容易的用新的實現來替換原有層次的實現;
  3. 可以降低層與層之間的依賴;
  4. 有利於標準化;
  5. 利於各層邏輯的複用。

概括來說,分層式設計可以達至如下目的:分散關注、鬆散耦合、邏輯複用、標準定義。

一個好的分層式結構,可以使得開發人員的分工更加明確。一旦定義好各層次之間的接口,負責不同邏輯設計的開發人員就可以分散關注,齊頭並進。例如UI人員只需考慮用戶界面的體驗與操作,領域的設計人員可以僅關注業務邏輯的設計,而數據庫設計人員也不必爲繁瑣的用戶交互而頭疼了。每個開發人員的任務得到了確認,開發進度就可以迅速的提高。

鬆散耦合的好處是顯而易見的。如果一個系統沒有分層,那麼各自的邏輯都緊緊糾纏在一起,彼此間相互依賴,誰都是不可替換的。一旦發生改變,則牽一髮而動全身,對項目的影響極爲嚴重。降低層與層間的依賴性,既可以良好地保證未來的可擴展,在複用性上也是優勢明顯。每個功能模塊一旦定義好統一的接口,就可以被各個模塊所調用,而不用爲相同的功能進行重複地開發。

進行好的分層式結構設計,標準也是必不可少的。只有在一定程度的標準化基礎上,這個系統纔是可擴展的,可替換的。而層與層之間的通信也必然保證了接口的標準化。

分層式結構同樣也具有一些缺陷:

  1. 降低了系統的性能。這是不言而喻的。如果不採用分層式結構,很多業務可以直接造訪數據庫,以此獲取相應的數據,如今卻必須通過中間層來完成。
  2. 有時會導致級聯的修改。這種修改尤其體現在自上而下的方向。如果在表示層中需要增加一個功能,爲保證其設計符合分層式結構,可能需要在相應的業務邏輯層和數據訪問層中都增加相應的代碼。

 分層與面向接口編程

  談到分層之間的那些接口,大概馬上就會有很多同學來吐槽了,不願意用接口,原因無非如下:

  • 定義接口導致項目分層過細,增加一個模塊,要增加很多的類文件及代碼量,增加開發人員的工作量
  • 單一實現的接口,實際用處不大,但閱讀代碼時,接口導致“轉到定義”只能轉到接口上,實現類還得手動去找。

  以上是通常拒絕使用接口的理由。

  但是,從我個人的看法,從接口使用的利弊來說,利是遠遠大於弊的:

  • 使用接口的好處之一是解耦各個層次的依賴,讓我們能輕鬆將“具體實現”替換掉(對於大多數項目而言,這個好處並不明顯)
  • 接口是項目的骨架,是架構的整體體現,架構師在構建項目的時候,直接定義出各個層次的接口,就把整個項目的主體骨架設計出來了,隨後開發人員只需要以“填空”的方式去逐個實現各種方法,能極大的提高開發效率,並保證項目的完整性與合理性
  • 在進行層次的單元測試的時候,由於各個層次是以接口隔離的,上層不依賴於下層的具體實現,測試上層的時候,只要使用Moq、Fakes等Mock框架對下層接口進行模擬,即可非常輕鬆的測試上層的業務實現。如果上層是與下層緊耦合的,那上層的代碼幾乎是不可測試的。(如果不瞭解單元測試,可以看看我寫的兩篇文章:基於VS2012 Fakes框架的TDD實戰

  補充:關於接口是否有必要的討論,iteye.com上有一個帖子討論得非常熱烈,強烈推薦看一下:

主題:在項目架構中如何進行分層纔是最合理的?

OSharp的分層設計

  OSharp開發框架約定的分層方案,依然是傳統的三層(數據層 - 業務層 - 展現層)分層方式,但也有自己的特點:

  • 使用了三層分層方式,但並不嚴格遵守傳統三層的“各層職責極其分明”的約定
  • 參考了“領域驅動設計”的一些思想,但爲了保證性能,並不使用 DDD 的聚合模型

 數據層:

  

  如上圖所示,OSharp的數據層的作用是向業務層提供數據實體的數據庫持久化操作,向業務層與展現層提供進行數據查詢的查詢數據集。對外API很簡單,主要是 IUnitOfWork 與 IRepository<TEntity> 兩個接口,同時定義了支持泛型主鍵類型的實體模型基類 EntityBase,數據傳輸對象接口 IAddDto 與 IEditDto。  

 業務層:

  

  OSharp的業務層以模塊爲劃分,一個模塊是業務內聚的一個或多個實體組成的操作單元,由三個部分組成:

  1. 數據實體模型(Model)的定義,數據模型是業務層與數據層之間的數據交互對象,也是數據層進行持久化的對象,爲業務數據的最終承載。
  2. 數據傳輸對象(Dto)的定義,數據傳輸對象是展現層向業務層的交互對象,Dto在需要的時候也可以作爲視圖模型展現到頁面上。
  3. 業務處理模塊的定義,業務處理模塊包括業務契約(IXXXContract)的定義與業務邏輯(IXXXService)的實現。業務處理模塊從展現層接受 Dto 形式的數據,經過業務處理後,轉化爲Model提交給數據層進行持久化操作。業務處理模塊同時還向展現層提供相應實體的 IQueryable<TEntity> 類型的查詢數據集,作爲展現層進行數據查詢的數據源。原則上業務處理模塊只處理數據的插入、更新、刪除操作,不對展現層提供數據查詢服務。

  業務層有如下特點:

  1. 業務層接受來自展現層的參數爲 Dto 或簡單類型參數
  2. 業務層向展現層返回業務操作結果爲 OperationResult 類型
  3. 業務層只處理 插入、修改、刪除等非查詢業務,原則上不提供展現層數據的查詢業務
  4. 業務層向展現層開放相應實體的 IQueryable<T> 類型的查詢數據集,作爲展現層的查詢數據源
  5. Dto 與 Model 的轉換工作由 Automapper 來完成
  6. Model 的構建工作由業務層來完成,展現層向業務層提供必要的 Dto,不參與 Model 的構建工作

展現層:

  

  MVC 的展現層包含 控制器(Controller)與 視圖(View)兩個部分。控制器負責使用業務層開放的查詢數據集來進行數據查詢操作,再把查詢結果提交給 視圖 進行展示,還負責接收來自 View 提交的數據,轉換爲 Dto 提交給業務層進行處理,並接收並解析業務層反饋的業務處理結果(OperationResult),交給 視圖 展現給用戶。視圖 接收並解析 控制器 傳遞過來的數據,解析成 HTML 頁面發送到瀏覽器進行展示,並向 控制器 提交用戶的請求數據。 

在本篇,只是對分層做一個簡要的講解,從整體上認識 OSharp 的分層架構,在後面進入實際應用的時候,還要對各個層進行更詳細的分析。

三、分層解耦與依賴注入

  前面已經分析瞭解耦的必要性,那麼,OSharp 開發框架中的解耦,是怎樣實現的呢?OSharp 中的解耦工作,主要是通過 Autofac 這個 IoC 組件來完成的。Autofac 是一個輕量級的,對系統污染與侵入性非常小的 IoC 組件,並且對 Webform、MVC、WebApi、SignalR等主流 ASP.NET Web 技術都有非常好的支持,是個相當不錯的 IoC 組件。

  爲了統一管理 IoC 相關的代碼,並避免在 OSharp 底層類庫中到處引用 Autofac 這個第三方組件,OSharp 中定義了一個專門用於管理需要依賴注入的接口與實現類的空接口 IDependency:

1  /// <summary>
2  /// 依賴注入接口,表示該接口的實現類將自動註冊到IoC容器中
3  /// </summary>
4  public interface IDependency
5  { }

  這個接口沒有任何方法,不會對系統的業務邏輯造成污染,所有需要進行依賴注入的接口,都要繼承這個空接口,例如:

  業務單元操作接口:

複製代碼
1 /// <summary>
2 /// 業務單元操作接口
3 /// </summary>
4 public interface IUnitOfWork : IDependency
5 {
6     ...
7 }
複製代碼

   實體倉儲操作接口:

複製代碼
1 /// <summary>
2 /// 實體倉儲模型的數據標準操作
3 /// </summary>
4 /// <typeparam name="TEntity">實體類型</typeparam>
5 /// <typeparam name="TKey">主鍵類型</typeparam>
6 public interface IRepository<TEntity, TKey> : IDependency where TEntity : EntityBase<TKey>
7 {
8     ...
9 }
複製代碼

   賬戶模塊業務契約:

複製代碼
1 /// <summary>
2 /// 業務契約——賬戶模塊
3 /// </summary>
4 public interface IIdentityContract : IDependency
5 {
6     ...
7 }
複製代碼

   在需要引用 注入對象 的地方,統一使用“構造函數注入”的方式來進行注入。

  實體倉儲實現類: 

複製代碼
 1 /// <summary>
 2 /// EntityFramework的倉儲實現
 3 /// </summary>
 4 /// <typeparam name="TEntity">實體類型</typeparam>
 5 /// <typeparam name="TKey">主鍵類型</typeparam>
 6 public class Repository<TEntity, TKey> : IRepository<TEntity, TKey> where TEntity : EntityBase<TKey>
 7 {    
 8     private readonly IUnitOfWork _unitOfWork;
 9 
10     public Repository(IUnitOfWork unitOfWork)
11     {
12         _unitOfWork = unitOfWork;
13     }
14 
15     ...
16      
17
複製代碼

   賬戶模塊業務實現類:

複製代碼
 1 /// <summary>
 2 /// 業務實現——賬戶模塊
 3 /// </summary>
 4 public partial class IdentityService : ServiceBase, IIdentityContract
 5 {
 6     private readonly IRepository<User, int> _userRepository;
 7     private readonly IRepository<Role, int> _roleRepository;
 8     private readonly IRepository<Organization, int> _organizationRepository;
 9 
10     /// <summary>
11     /// 初始化一個<see cref="IdentityService"/>類型的新實例
12     /// </summary>
13     public IdentityService(IRepository<User, int> userRepository,
14         IRepository<Role, int> roleRepository,
15         IRepository<Organization, int> organizationRepository)
16         : base(userRepository.UnitOfWork)
17     {
18         _userRepository = userRepository;
19         _roleRepository = roleRepository;
20         _organizationRepository = organizationRepository;
21     }
22 }
複製代碼

   Autofac 是支持批量子類註冊的,有了 IDependency 這個基接口,我們只需要 Global 中很簡單的幾行代碼,就可以完成整個系統的依賴注入匹配:

複製代碼
 1 ContainerBuilder builder = new ContainerBuilder();
 2 builder.RegisterGeneric(typeof(Repository<,>)).As(typeof(IRepository<,>));
 3 Type baseType = typeof(IDependency);
 4 
 5 // 獲取所有相關類庫的程序集
 6 Assembly[] assemblies = ...
 7 
 8 builder.RegisterAssemblyTypes(assemblies)
 9     .Where(type => baseType.IsAssignableFrom(type) && !type.IsAbstract)
10     .AsImplementedInterfaces().InstancePerLifetimeScope();//InstancePerLifetimeScope 保證對象生命週期基於請求
11 IContainer container = builder.Build();
12 DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
複製代碼

   如此,只有站點主類庫需要引用 Autofac,而不是到處都存在着注入的相關代碼,大大降低了系統的複雜度。

四、開源說明

 (一)github.com

   OSharp項目已在github.com上開源,地址爲:https://github.com/i66soft/osharp,歡迎閱讀代碼,歡迎 Fork,如果您認同 OSharp 項目的思想,歡迎參與 OSharp 項目的開發。

  在Visual Studio 2013中,可直接獲取 OSharp 的最新源代碼,獲取方式如下,地址爲:https://github.com/i66soft/osharp.git

  

 (二)nuget

  OSharp的相關類庫已經發布到nuget上,歡迎試用,直接在nuget上搜索 “osharp” 關鍵字即可找到
  

系列導航

  1. 【開源】OSharp框架解說系列(1):總體設計
  2. 【開源】OSharp框架解說系列(2.1):EasyUI的後臺界面搭建及極致重構
  3. 【開源】OSharp框架解說系列(2.2):EasyUI複雜佈局及數據操作
  4. 【開源】OSharp框架解說系列(3):擴展方法
  5. 【開源】OSharp框架解說系列(4):架構分層及IoC
  6. 【開源】OSharp框架解說系列(5.1):數據層設計
  7. 【開源】OSharp框架解說系列(5.2):EntityFramework的封裝
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章