企業(yè)級應(yīng)用架構(gòu)設(shè)計
基本設(shè)計原則
一個設(shè)計精良的系統(tǒng)并不是一系列指令和修改的堆砌,其中包含很多與設(shè)計直接或間接相關(guān)的要素,與國際化標(biāo)準(zhǔn)中定義的其它質(zhì)量特性相比,需要更加重視代碼的可維護性,之所以選擇這個特性,并不是因為其他特性和可維護性相比不重要,而是保持代碼的可維護性的代碼比較高,而且容易讓開發(fā)者忽視,可維護是最關(guān)鍵的問題。
對于可維護性可分為兩點解析,結(jié)構(gòu)化設(shè)計是第一要素,可通過一系列的編碼技術(shù)來保證。代碼的可讀性是另外一個重要因素。
結(jié)構(gòu)化設(shè)計
結(jié)構(gòu)化設(shè)計從理念誕生開始,內(nèi)部隱藏的結(jié)構(gòu)化設(shè)計核心原則一直是今天的指導(dǎo)原則,高內(nèi)聚和低耦合這兩個原則在面向?qū)ο笫澜缰酗L(fēng)光無限。內(nèi)聚的衡量標(biāo)準(zhǔn)從低到高,內(nèi)聚越高說明軟件設(shè)計的越好。高內(nèi)聚的模塊意味著可維護性和可重用性,因為這些模塊的外部依賴很少。低內(nèi)聚的模塊容易在其他模塊中留下依賴,讓軟件變得頑固且高粘度。耦合的衡量標(biāo)準(zhǔn)從低到高,耦合越低說明軟件設(shè)計越好。高內(nèi)聚和低耦合唇齒相依,若系統(tǒng)滿足這兩個條件,則說明該系統(tǒng)基本滿足高可讀性、高可維護性,并易于測試和易于重用的要求。
分離關(guān)注點
在設(shè)計系統(tǒng)時需要考慮內(nèi)聚和耦合兩個因素時,分離關(guān)注點有助于實現(xiàn)這個目標(biāo)。分離關(guān)注點的核心在于將系統(tǒng)拆分成各不相同且最好沒有重疊的功能。分離關(guān)注點原則建議一次只處理一個關(guān)注點。這并不表示此時要將所有的其他關(guān)注點都拋之腦后,而是說當(dāng)決定用某個模塊來實現(xiàn)這個關(guān)注點之后,只需要全神貫注于實現(xiàn)該模塊即可。從這個角度考慮,其他關(guān)注點都是不相關(guān)的。具體來說,分離關(guān)注點是通過模塊化代碼以及大量運用信息隱藏來實現(xiàn)的。模塊化編碼鼓勵使用不同的模塊來實現(xiàn)不同的功能,模塊擁有自己的公開接口,和其他模塊通信,模塊同時包含大量內(nèi)部信息,供自己使用。信息隱藏是一條通用的設(shè)計選擇,固定的公開接口隱藏軟件模塊的實現(xiàn)細節(jié),以降低未來修改造成的影響。
其實分離關(guān)注點第一種支持的編程理念是過程式編程,在過程式編程中,分離關(guān)注點依靠函數(shù)和過程來實現(xiàn)。不僅分離關(guān)注點的概念不僅限于編程語言,它超越了純粹編程的領(lǐng)域。還引用了軟件架構(gòu)的很多方面。在面向服務(wù)架構(gòu)SOA中,服務(wù)用來表示關(guān)注點。分層架構(gòu)也基于分離關(guān)注點的原則構(gòu)建。
面向?qū)ο笤O(shè)計
面向?qū)ο笤O(shè)計可以說是一座里程碑,面向?qū)ο笤O(shè)計中的一個重要的步驟就是為問題領(lǐng)域?qū)ふ乙粋€趕緊靈活的抽象。若要很好的完整這一步,應(yīng)該考慮的是事情而非流程。應(yīng)該關(guān)注的是“什么”而非“如何”。
可重用性是面向?qū)ο罄砟钪幸粋€非常重要的方面,也是面向?qū)ο髲V泛使用的根本原因。《設(shè)計模式》總結(jié)出兩種實現(xiàn)重用的方式:白盒重用和黑盒重用。白盒重用基于繼承,黑盒重用基于對象組合。在實際設(shè)計中,盡量使用對象組合而非類型繼承。在現(xiàn)實世界中,對象組合更加安全,易于維護和測試。在組合中,修改組合對象并不會影響到內(nèi)部對象。
高級原則
開放封閉原則
開放封閉原則能夠幫助軟件的單元(包括類型、函數(shù)、模塊)更加容易適應(yīng)變化。每次發(fā)生變化時,要通過添加新代碼來增強現(xiàn)有類型的行為,而非修改原有代碼。當(dāng)前這個原則最好的方式是提供一個固定的接口,然后讓可能發(fā)生變化的類實現(xiàn)該接口,隨后調(diào)用者基于該接口操作。
里氏替換原則
但某個類型派生于某個現(xiàn)有類型時,派生類應(yīng)該能夠用于任何可以使用父類的地方即多態(tài)。開放封閉原則和里氏替換原則有著緊密的關(guān)系,任何使用違反里氏替換原則的類型的方法都無法滿足開放封閉原則。
依賴倒置原則
依賴倒置原則中的倒置表示在實現(xiàn)過程中應(yīng)采用自頂向下的方式,且應(yīng)該關(guān)注于高層次模塊的工作流,而非低層次的模塊具體的實現(xiàn)。從這點考慮,低層次模塊可以直接插入到高層次模塊中。
知識點
ASP.NET MVC請求是如何進入管道的
請求處理管道
請求管道是用于處理HTTP請求的模塊組合,在ASP.NET中請求管道有兩大核心組件IHttpModule
和IHttpHandler
。所有HTTP請求會進入IHttpHandler
,有IHttpHandler
進行最終的處理,而IHttpModule
通過訂閱HttpApplication
對象中的事件,可在IHttpHandler
對HTTP請求進行處理前對請求進行預(yù)處理或IHttpHandler
對HTTP請求之后進行再次處理。
IIS7之前請求處理管道分為兩個:IIS請求處理管道和ASP.NET管道,若客戶端請求靜態(tài)資源則只有IIS管道進行處理,而ASP.NET管道不會處理該請求。從IIS7開始兩個管道合二為一,稱為集成管道。
ASP.NET MVC處理管道
ASP.NET MVC的請求管理和ASP.MET請求管道基本類似,
ASP.NET MVC請求處理流程
- 瀏覽器發(fā)送HTTP請求
- Web服務(wù)器IIS
- ISAPIRuntime
- HttpWorkRequest
- HttpRuntime
- HttpContext
- 尋找Global文件并編譯
- 確保Global文件中Application_Start被調(diào)用
- 創(chuàng)建HttpApplication,使用了池(棧)。如果池中沒有根據(jù)Global文件編譯的類型,則通過反射的形式創(chuàng)建出HttpApplication。
- 獲取所有在Web.config配置文件中的HttpModules,此時System.Web.Routing下UrlRoutingModule也被獲取,執(zhí)行每個Module下的Init方法。UrlRoutingModule的Init方法完成了請求管道第七個事件的注冊。
- 進入管道
- 第七個事件觸發(fā)執(zhí)行相應(yīng)方法,完成MVCHandler的創(chuàng)建。
-
走到請求管道中的11與12事件之間,執(zhí)行MVCHandler中的ProcessRequest方法,該方法尋找控制器和方法,執(zhí)行方法中的代碼,最終尋找視圖并渲染。
ASP.NET MVC請求處理流程
搭建框架
新建空白解決方案并命名為OA
,在空白項目中添加類庫與ASP.NET MVC的Web應(yīng)用程序。
- 添加類庫
OA.Common
公共幫助類庫 - 添加類庫
OA.Model
EF實體數(shù)據(jù)模型 - 添加類庫
OA.DAL
- 添加類庫
OA.IDAL
數(shù)據(jù)訪問層 - 添加類庫
OA.DALFactory
抽象工廠類 - 添加類庫
OA.BLL
- 添加類庫
OA.IBLL
業(yè)務(wù)邏輯層 - 添加Web應(yīng)用程序
OA.WebApp
,并將其設(shè)置為解決方案的默認啟動項。
模型層
創(chuàng)建數(shù)據(jù)模型
- 在
OA.Model
中添加ADO.NET實體數(shù)據(jù)模型并命名為OAModel
,并采用Code First
編碼優(yōu)先的方式創(chuàng)建數(shù)據(jù)模型。
- 查看
OA.Model
中的App.config
應(yīng)用配置文件,添加本地數(shù)據(jù)庫連接字符串。
<connectionStrings>
<add
name="OAModel"
connectionString="data source=(LocalDb)\MSSQLLocalDB;initial catalog=OA.Model.OAModel;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"
providerName="System.Data.SqlClient" />
</connectionStrings>
- 在模型中添加實體
$ vim OA.Model/OAModel.cs
namespace OA.Model
{
using System;
using System.Data.Entity;
public class OAModel : DbContext
{
//上下文已配置為從應(yīng)用程序的配置文件(App.config 或 Web.config)使用“OAModel”連接字符串
public OAModel(): base("name=OAModel")
{
}
//為要在模型中包含的每種實體類型都添加 DbSet。
public virtual DbSet<UserInfo> UserInfo { get; set; }
}
public class UserInfo
{
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public bool Status { get; set; }
public int Sort { get; set; }
public String Remark { get; set; }
}
}
數(shù)據(jù)訪問層
數(shù)據(jù)訪問層的設(shè)計很大程度上取決于項目干系人需求的影響。例如,數(shù)據(jù)訪問層應(yīng)該持久化對象模型還是簡單的值的集合呢?數(shù)據(jù)訪問層應(yīng)該支持一種數(shù)據(jù)庫還是多種數(shù)據(jù)庫呢?
數(shù)據(jù)庫獨立性
數(shù)據(jù)訪問層是系統(tǒng)中唯一知道并使用連接字符串和數(shù)據(jù)表名的地方,考慮到這些,數(shù)據(jù)訪問層必須要依賴于數(shù)據(jù)庫管理系統(tǒng)DBMS。對于外部觀察者,數(shù)據(jù)訪問層應(yīng)該是一個黑盒,可以插入到現(xiàn)有系統(tǒng)中,封裝了為某個特定DBMS實現(xiàn)的讀取和寫入的操作。
像插入一樣可以配置
通常來說,數(shù)據(jù)庫獨立性需要一套普通的、跨庫的應(yīng)用編程接口。實現(xiàn)真正數(shù)據(jù)庫獨立需要將數(shù)據(jù)庫訪問層作為一個黑盒,該黑盒提供了一個固定的接口,并從配置文件中動態(tài)地讀取當(dāng)前數(shù)據(jù)庫訪問層組件的細節(jié)。還有一種實現(xiàn)數(shù)據(jù)庫獨立性的做法是使用對象/關(guān)系映射工具ORM。ORM提供一套公共的API,讓你僅需簡單修改配置參數(shù)就可以切換到另一個數(shù)據(jù)庫。不過,有時候項目允許你使用ORM,有時卻不行。
持久化應(yīng)用程序的對象模型
無論何種形式,數(shù)據(jù)訪問層都必須能夠持久化應(yīng)用程序的數(shù)據(jù)。若必須提供對象模型,那么數(shù)據(jù)訪問層要能夠?qū)⒛P统志没陵P(guān)系型結(jié)構(gòu)中。當(dāng)然,會造成臭名昭著的“對象關(guān)系阻抗失調(diào)”問題。關(guān)系型數(shù)據(jù)庫實際上存放的數(shù)據(jù)元組,而對象模型則構(gòu)造出一張對象圖。因此,兩個模型之間自然需要映射,這也是數(shù)據(jù)訪問層的主要功能。持久化應(yīng)用程序的對象模型是指將數(shù)據(jù)加載到新創(chuàng)建對象模型和將某個實例的內(nèi)容寫回數(shù)據(jù)庫的能力。無論選擇什么樣的DBMS或物理上的表結(jié)構(gòu),持久化功能都不應(yīng)該受到影響。依照領(lǐng)域模型模式設(shè)計的對象模型并不了解數(shù)據(jù)訪問層的存在,不過若對象模型屬于活動記錄,那么數(shù)據(jù)訪問層內(nèi)嵌在實現(xiàn)模型的所有框架中。
數(shù)據(jù)訪問層的職責(zé)
數(shù)據(jù)訪問層對使用者來說有4個職責(zé):
首先,數(shù)據(jù)訪問層需要將數(shù)據(jù)持久化至物理存儲中,并為外部世界提供CRUD服務(wù)。
其次,數(shù)據(jù)訪問層還要處理其接收的所有數(shù)據(jù)相關(guān)的請求。
再次,數(shù)據(jù)訪問層必須滿足事務(wù)性需求。
最后,數(shù)據(jù)訪問層也要合理的處理并發(fā)。
從概念角度,數(shù)據(jù)訪問層可以看作是封裝了4種服務(wù)的黑箱。
創(chuàng)建數(shù)據(jù)訪問層接口
-
為數(shù)據(jù)訪問層接口添加對模型層的引用
為數(shù)據(jù)訪問層接口添加對模型層的引用 創(chuàng)建基礎(chǔ)數(shù)據(jù)訪問層接口,定義公共的數(shù)據(jù)操作方法接口,其它數(shù)據(jù)訪問接口均繼承于它。
定義基礎(chǔ)的數(shù)據(jù)操作方法,如CURD與分頁。
$ vim OA.IDAL/IBaseDal.cs
using System;
using System.Linq;
using System.Linq.Expressions;
namespace OA.IDAL
{
public interface IBaseDal<T> where T : class, new()
{
/*查詢*/
IQueryable<T> Get(Expression<Func<T, bool>> whereLambda);
/**
* 分頁
* s為方法泛型,表示排序字段的數(shù)據(jù)類型。
*/
IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, s>> orderByLambda, bool isAsc);
/*刪除*/
bool Delete(T entity);
/*更新*/
bool Update(T entity);
/*創(chuàng)建*/
T Create(T entity);
}
}
- 創(chuàng)建具體數(shù)據(jù)訪問層接口
$ vim OA.IDAL/IUserInfo.cs
using OA.Model;
namespace OA.IDAL
{
/*數(shù)據(jù)訪問接口*/
public interface IUserInfoDal:IBaseDal<UserInfo>
{
}
}
創(chuàng)建數(shù)據(jù)持久層類并實現(xiàn)其接口
-
添加數(shù)據(jù)訪問層DAL對模型層Model和數(shù)據(jù)訪問層接口IDAL的引用
添加數(shù)據(jù)訪問層DAL對模型層Model和數(shù)據(jù)訪問層接口IDAL的引用
- 添加對EF的引用
查看模型層中對EF的引用分別為EntityFramework
和EntityFramework.SqlServer
查看模型層中EF引用的版本,注意整個解決方案中所有引入EF的位置必須保證版本一致。
$ vim OA.Model/App.config
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
在數(shù)據(jù)訪問層添加臨時的模型層,系統(tǒng)會自動加載所需的EF程序集的引用,添加后刪除該模型文件。
- 創(chuàng)建數(shù)據(jù)操作類并實現(xiàn)公共的基礎(chǔ)接口方法
$ vim OA.DAL/UserInfoDal.cs
using OA.IDAL;
using OA.Model;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace OA.DAL
{
public class UserInfoDal : IUserInfoDal
{
OAModel db = new OAModel();
public UserInfo Create(UserInfo entity)
{
db.UserInfo.Add(entity);
return entity;
}
public bool Delete(UserInfo entity)
{
db.Entry<UserInfo>(entity).State = EntityState.Deleted;
return db.SaveChanges() > 0;
}
public IQueryable<UserInfo> Get(Expression<Func<UserInfo, bool>> whereLambda)
{
return db.UserInfo.Where<UserInfo>(whereLambda);
}
public IQueryable<UserInfo> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<UserInfo, bool>> whereLambda, Expression<Func<UserInfo, s>> orderByLambda, bool isAsc)
{
var temp = db.UserInfo.Where<UserInfo>(whereLambda);
totalCount = temp.Count();
int skip = (pageIndex - 1) * pageSize;
if (isAsc)
{
temp = temp.OrderBy<UserInfo, s>(orderByLambda).Skip<UserInfo>(skip).Take<UserInfo>(pageSize);
}
else
{
temp = temp.OrderByDescending<UserInfo, s>(orderByLambda).Skip<UserInfo>(skip).Take<UserInfo>(pageSize);
}
return temp;
}
public bool Update(UserInfo entity)
{
db.Entry<UserInfo>(entity).State = EntityState.Modified;
return db.SaveChanges() > 0;
}
}
}
- 由于每個數(shù)據(jù)操作類都需要實現(xiàn)基礎(chǔ)操作,因此使用繼承的方式添加公共的數(shù)據(jù)操作父類。
基類:無需實現(xiàn)IBaseDal
$ vim OA.DAL/BaseDal.cs
using OA.Model;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace OA.DAL
{
public class BaseDal<T> where T: class, new()
{
OAModel db = new OAModel();
public T Create(T entity)
{
db.Set<T>().Add(entity);
db.SaveChanges();
return entity;
}
public bool Delete(T entity)
{
db.Entry<T>(entity).State = EntityState.Deleted;
return db.SaveChanges() > 0;
}
public IQueryable<T> Get(Expression<Func<T, bool>> whereLambda)
{
return db.Set<T>().Where<T>(whereLambda);
}
public IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, s>> orderByLambda, bool isAsc)
{
var temp = db.Set<T>().Where<T>(whereLambda);
totalCount = temp.Count();
int skip = (pageIndex - 1) * pageSize;
if (isAsc)
{
temp = temp.OrderBy<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
else
{
temp = temp.OrderByDescending<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
return temp;
}
public bool Update(T entity)
{
db.Entry<T>(entity).State = EntityState.Modified;
return db.SaveChanges() > 0;
}
}
}
子類:注意需要先繼承后實現(xiàn)
$ OA.DAL/UserInfoDal.cs
using OA.IDAL;
using OA.Model;
namespace OA.DAL
{
public class UserInfoDal : BaseDal<UserInfo>, IUserInfoDal
{
}
}
數(shù)據(jù)會話層
- 數(shù)據(jù)會話層位于數(shù)據(jù)操作層與業(yè)務(wù)處理層之間
- 數(shù)據(jù)會話層封裝了所有數(shù)據(jù)操作類實例的創(chuàng)建
- 業(yè)務(wù)處理層通過數(shù)據(jù)會話層來獲取要操作數(shù)據(jù)持久層中操作類的實例
- 數(shù)據(jù)會話層本質(zhì)就是一個工廠類,負責(zé)對象的創(chuàng)建。
- 將業(yè)務(wù)處理層與數(shù)據(jù)持久層進行解耦,提供一個數(shù)據(jù)訪問的統(tǒng)一訪問點。
- 添加引用
- 添加對數(shù)據(jù)模型
OA.Model
的引用 - 添加對數(shù)據(jù)操作層接口
OA.IDAL
的引用 - 添加對數(shù)據(jù)操作層
OA.DAL
的引用
- 創(chuàng)建數(shù)據(jù)會話層類
- 數(shù)據(jù)會話層封裝了所有數(shù)據(jù)操作類的實例的創(chuàng)建,本質(zhì)是一個工廠類。
- 將業(yè)務(wù)層與數(shù)據(jù)層解耦
- 提供數(shù)據(jù)訪問的統(tǒng)一訪問點
$ vim OA.DALFactory/DBSession.cs
using OA.DAL;
using OA.IDAL;
using OA.Model;
namespace OA.DALFactory
{
/**
* 數(shù)據(jù)會話層
* 1.本質(zhì)是一個工廠類
* 2.負責(zé)完成所有數(shù)據(jù)操作類實例的創(chuàng)建
* 3.業(yè)務(wù)處理層通過數(shù)據(jù)會話層來獲取操作數(shù)據(jù)類的實例
* 4.數(shù)據(jù)會話層將業(yè)務(wù)層和數(shù)據(jù)層解耦
*/
public class DBSession
{
/*負責(zé)完成所有數(shù)據(jù)操作類實例的創(chuàng)建*/
private IUserInfoDal _UserInfoDal;
public IUserInfoDal UserInfoDal
{
get
{
if(_UserInfoDal == null)
{
_UserInfoDal = new UserInfoDal();
}
return _UserInfoDal;
} ,
set
{
_UserInfoDal = value;
}
}
}
- 在數(shù)據(jù)會話層中添加對所有數(shù)據(jù)保存的方法
數(shù)據(jù)會話層OA.DALFactory中添加對EF的引用
$ vim OA.DALFactory/DbSession.cs
using OA.DAL;
using OA.IDAL;
using OA.Model;
namespace OA.DALFactory
{
/**
* 數(shù)據(jù)會話層
* 1.本質(zhì)是一個工廠類
* 2.負責(zé)完成所有數(shù)據(jù)操作類實例的創(chuàng)建
* 3.業(yè)務(wù)處理層通過數(shù)據(jù)會話層來獲取操作數(shù)據(jù)類的實例
* 4.數(shù)據(jù)會話層將業(yè)務(wù)層和數(shù)據(jù)層解耦
* 5.完成所有數(shù)據(jù)的保存
*/
public class DbSession
{
/*負責(zé)完成所有數(shù)據(jù)操作類實例的創(chuàng)建*/
private IUserInfoDal _UserInfoDal;
public IUserInfoDal UserInfoDal
{
get
{
if(_UserInfoDal == null)
{
_UserInfoDal = new UserInfoDal();
}
return _UserInfoDal;
}
set
{
_UserInfoDal = value;
}
}
/**
* 工作單元設(shè)計模式 - 完成所有數(shù)據(jù)的保存
* 一個業(yè)務(wù)中涉及到對多張表的操作
* 連接一次數(shù)據(jù)庫完成對多張表的數(shù)據(jù)操作
*/
OAModel db = new OAModel();
public bool SaveChanges()
{
return db.SaveChanges() > 0;
}
}
}
- 將數(shù)據(jù)持久層中公共的基礎(chǔ)數(shù)據(jù)持久類中所有的
SaveChanges()
取消
$ vim OA.DAL/BaseDal.cs
using OA.Model;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace OA.DAL
{
public class BaseDal<T> where T: class, new()
{
OAModel db = new OAModel();
public T Create(T entity)
{
db.Set<T>().Add(entity);
//db.SaveChanges();
return entity;
}
public bool Delete(T entity)
{
db.Entry<T>(entity).State = EntityState.Deleted;
//return db.SaveChanges() > 0;
return true;
}
public IQueryable<T> Get(Expression<Func<T, bool>> whereLambda)
{
return db.Set<T>().Where<T>(whereLambda);
}
public IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, s>> orderByLambda, bool isAsc)
{
var temp = db.Set<T>().Where<T>(whereLambda);
totalCount = temp.Count();
int skip = (pageIndex - 1) * pageSize;
if (isAsc)
{
temp = temp.OrderBy<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
else
{
temp = temp.OrderByDescending<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
return temp;
}
public bool Update(T entity)
{
db.Entry<T>(entity).State = EntityState.Modified;
//return db.SaveChanges() > 0;
return true;
}
}
}
- EF線程內(nèi)唯一
目前的問題是,EF在數(shù)據(jù)操作層中會使用,EF在數(shù)據(jù)會話層中也會使用到,這是兩個不同的對象。而在一個請求中只能創(chuàng)建一個EF實例,也就是線程內(nèi)唯一。對此,因采用工廠模式。
如果在DALFactory
中創(chuàng)建一個工廠類封裝EF實例的創(chuàng)建,會出現(xiàn)一個問題。由于DALFactory
已經(jīng)引入了DAL,EF若在DALFactory
中創(chuàng)建,那么DAL也必須引用DALFactory
,此時也就成了相互循環(huán)引用。所以,應(yīng)該將負責(zé)EF實例創(chuàng)建的工廠類DbContextFactory
放到DAL中。
$ OA.DAL/DbContextFactory.cs
using OA.Model;
using System.Data.Entity;
using System.Runtime.Remoting.Messaging;
namespace OA.DAL
{
/**
* 負責(zé)創(chuàng)建EF數(shù)據(jù)操作上下文實例
* 使用工廠模式且必須保證線程內(nèi)唯一
*/
public class DbContextFactory
{
public static DbContext CreateDbContext()
{
DbContext dbContext = (DbContext)CallContext.GetData("dbContext");
if (dbContext == null)
{
dbContext = new OAModel();
CallContext.SetData("dbContext", dbContext);
}
return dbContext;
}
}
}
在數(shù)據(jù)會話層DbSession和數(shù)據(jù)處理層中公共基類BaseDal中調(diào)用CreateDbContext方法,完成EF實例的創(chuàng)建。
OAModel db = new OAModel();
將原來db的實例化的方式修改為通過DbContextFactory創(chuàng)建
public DbContext db
{
get
{
return DbContextFactory.CreateDbContext();
}
}
簡化方式
DbContext db = DbContextFactory.CreateDbContext();
在DbSession中獲取db實例
$ vim OA.DALFactory/DbSession.cs
using OA.DAL;
using OA.IDAL;
using System.Data.Entity;
namespace OA.DALFactory
{
/**
* 數(shù)據(jù)會話層
* 1.本質(zhì)是一個工廠類
* 2.負責(zé)完成所有數(shù)據(jù)操作類實例的創(chuàng)建
* 3.業(yè)務(wù)處理層通過數(shù)據(jù)會話層來獲取操作數(shù)據(jù)類的實例
* 4.數(shù)據(jù)會話層將業(yè)務(wù)層和數(shù)據(jù)層解耦
* 5.完成所有數(shù)據(jù)的保存
*/
public class DbSession
{
/*負責(zé)完成所有數(shù)據(jù)操作類實例的創(chuàng)建*/
private IUserInfoDal _UserInfoDal;
public IUserInfoDal UserInfoDal
{
get
{
if(_UserInfoDal == null)
{
_UserInfoDal = new UserInfoDal();
}
return _UserInfoDal;
}
set
{
_UserInfoDal = value;
}
}
/**
* 工作單元 設(shè)計模式
* 完成所有數(shù)據(jù)的保存
* 一個業(yè)務(wù)中涉及到對多張表的操作
* 連接一次數(shù)據(jù)庫完成對多張表的數(shù)據(jù)操作
*/
//OAModel db = new OAModel();
DbContext db = DbContextFactory.CreateDbContext();
public bool SaveChanges()
{
return db.SaveChanges() > 0;
}
}
}
在BaseDal中獲取db實例
$ vim OA.DAL/BaseDal.cs
using OA.Model;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace OA.DAL
{
public class BaseDal<T> where T: class, new()
{
//OAModel db = new OAModel();
//public DbContext db
//{
// get
// {
// return DbContextFactory.CreateDbContext();
// }
//}
DbContext db = DbContextFactory.CreateDbContext();
public T Create(T entity)
{
db.Set<T>().Add(entity);
//db.SaveChanges();
return entity;
}
public bool Delete(T entity)
{
db.Entry<T>(entity).State = EntityState.Deleted;
//return db.SaveChanges() > 0;
return true;
}
public IQueryable<T> Get(Expression<Func<T, bool>> whereLambda)
{
return db.Set<T>().Where<T>(whereLambda);
}
public IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, s>> orderByLambda, bool isAsc)
{
var temp = db.Set<T>().Where<T>(whereLambda);
totalCount = temp.Count();
int skip = (pageIndex - 1) * pageSize;
if (isAsc)
{
temp = temp.OrderBy<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
else
{
temp = temp.OrderByDescending<T, s>(orderByLambda).Skip<T>(skip).Take<T>(pageSize);
}
return temp;
}
public bool Update(T entity)
{
db.Entry<T>(entity).State = EntityState.Modified;
//return db.SaveChanges() > 0;
return true;
}
}
}
- 將DbSession工廠類中實例化數(shù)據(jù)操作類的方式修改使用抽象工廠來完成
在表現(xiàn)層WebApp的配置文件Web.config
中設(shè)置程序集與命名空間。
$ vim OA.WebApp/Web.conifg
<appSettings>
<add key="webpages:Version" value="3.0.0.0"/>
<add key="webpages:Enabled" value="false"/>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
<!--配置程序集與命名空間-->
<add key="AssemblyPath" value="OA.DAL"/>
<add key="NameSpace" value="OA.DAL"/>
</appSettings>
在數(shù)據(jù)會話層中引入框架中的System.Configuration
程序集
為了進一步將數(shù)據(jù)會話層與話劇操作層解耦,在數(shù)據(jù)會話層DALFactory中創(chuàng)建抽象工廠,以完成數(shù)據(jù)操作類的實例化。
$ vim OA.DALFactory/AbstractFactory.cs
using OA.IDAL;
using System.Configuration;
using System.Reflection;
namespace OA.DALFactory
{
/**
* 抽象工廠
* 通過反射的方式創(chuàng)建類的實例
*/
public class AbstractFactory
{
private static readonly string AssemblyPath = ConfigurationManager.AppSettings["AssemblyPath"];
private static readonly string NameSpace = ConfigurationManager.AppSettings["NameSpace"];
private static object CreateInstance(string className)
{
var assembly = Assembly.Load(AssemblyPath);
return assembly.CreateInstance(className);
}
public static IUserInfoDal CreateUserInfoDal()
{
string fullClassName = NameSpace + ".UserInfoDal";
return CreateInstance(fullClassName) as IUserInfoDal;
}
}
}
數(shù)據(jù)會話層調(diào)用抽象工廠完成實例化
$ vim OA.DALFactory/DbSession.cs
using OA.DAL;
using OA.IDAL;
using System.Data.Entity;
namespace OA.DALFactory
{
/**
* 數(shù)據(jù)會話層
* 1.本質(zhì)是一個工廠類
* 2.負責(zé)完成所有數(shù)據(jù)操作類實例的創(chuàng)建
* 3.業(yè)務(wù)處理層通過數(shù)據(jù)會話層來獲取操作數(shù)據(jù)類的實例
* 4.數(shù)據(jù)會話層將業(yè)務(wù)層和數(shù)據(jù)層解耦
* 5.完成所有數(shù)據(jù)的保存
*/
public class DbSession
{
/*負責(zé)完成所有數(shù)據(jù)操作類實例的創(chuàng)建*/
private IUserInfoDal _UserInfoDal;
public IUserInfoDal UserInfoDal
{
get
{
if(_UserInfoDal == null)
{
//_UserInfoDal = new UserInfoDal();
//使用抽象工廠來封裝了類的實例的創(chuàng)建(解耦)
_UserInfoDal = AbstractFactory.CreateUserInfoDal();
}
return _UserInfoDal;
}
set
{
_UserInfoDal = value;
}
}
/**
* 工作單元 設(shè)計模式
* 完成所有數(shù)據(jù)的保存
* 一個業(yè)務(wù)中涉及到對多張表的操作
* 連接一次數(shù)據(jù)庫完成對多張表的數(shù)據(jù)操作
*/
//OAModel db = new OAModel();
DbContext db = DbContextFactory.CreateDbContext();
public bool SaveChanges()
{
return db.SaveChanges() > 0;
}
}
}
核心在于
//_UserInfoDal = new UserInfoDal();
//使用抽象工廠來封裝了類的實例的創(chuàng)建(解耦)
_UserInfoDal = AbstractFactory.CreateUserInfoDal();
數(shù)據(jù)會話層DbSession調(diào)用數(shù)據(jù)操作層DAL使用的是數(shù)據(jù)操作層所提供的接口IUserInfoDal,業(yè)務(wù)層BLL調(diào)用數(shù)據(jù)會話層DbSession同樣采用接口的方式,因此數(shù)據(jù)會話層DbSession必須提供對業(yè)務(wù)層的接口。
為便于引用及使用,在OA.IDAL定義IDbSession接口。
$vim OA.IDAL/IDbSession.cs
using System.Data.Entity;
namespace OA.IDAL
{
/**
* 業(yè)務(wù)層BLL調(diào)用的是數(shù)據(jù)會話層的接口
*/
public interface IDbSession
{
DbContext db{get;}
bool SaveChanges();
IUserInfoDal UserInfoDal { get; set; }
}
}
在DbSession中實現(xiàn)IDbSession接口
$ vim OA.DALFactory/DbSession.cs
using OA.DAL;
using OA.IDAL;
using System.Data.Entity;
namespace OA.DALFactory
{
/**
* 數(shù)據(jù)會話層
* 1.本質(zhì)是一個工廠類
* 2.負責(zé)完成所有數(shù)據(jù)操作類實例的創(chuàng)建
* 3.業(yè)務(wù)處理層通過數(shù)據(jù)會話層來獲取操作數(shù)據(jù)類的實例
* 4.數(shù)據(jù)會話層將業(yè)務(wù)層和數(shù)據(jù)層解耦
* 5.完成所有數(shù)據(jù)的保存
*/
public class DbSession:IDbSession
{
/*負責(zé)完成所有數(shù)據(jù)操作類實例的創(chuàng)建*/
private IUserInfoDal _UserInfoDal;
public IUserInfoDal UserInfoDal
{
get
{
if(_UserInfoDal == null)
{
//_UserInfoDal = new UserInfoDal();
//使用抽象工廠來封裝了類的實例的創(chuàng)建(解耦)
_UserInfoDal = AbstractFactory.CreateUserInfoDal();
}
return _UserInfoDal;
}
set
{
_UserInfoDal = value;
}
}
/**
* 工作單元 設(shè)計模式
* 完成所有數(shù)據(jù)的保存
* 一個業(yè)務(wù)中涉及到對多張表的操作
* 連接一次數(shù)據(jù)庫完成對多張表的數(shù)據(jù)操作
*/
//OAModel db = new OAModel();
//DbContext db = DbContextFactory.CreateDbContext();
public DbContext db
{
get
{
return DbContextFactory.CreateDbContext();
}
}
public bool SaveChanges()
{
return db.SaveChanges() > 0;
}
}
}
業(yè)務(wù)邏輯層
任何復(fù)雜的軟件都可以通過分層來組織,每層表示系統(tǒng)中的一個邏輯部分,一般來說,業(yè)務(wù)邏輯層中的模塊包含了系統(tǒng)所需的所有功能上的算法和計算過程,并于數(shù)據(jù)層和表現(xiàn)層交互。抽象的說,業(yè)務(wù)邏輯層是軟件中專門處理業(yè)務(wù)相關(guān)任務(wù)性能的部分。
業(yè)務(wù)邏輯層表示了系統(tǒng)的邏輯,此處的代碼將要進行必要的決斷并執(zhí)行操作。在業(yè)務(wù)邏輯層的安全性意味著使用基于角色的安全原則,僅允許認證用戶訪問特定的業(yè)務(wù)對象。從外界看,業(yè)務(wù)邏輯層可看作是一個操作業(yè)務(wù)對象的機制。一般來說,業(yè)務(wù)對象不過是某個領(lǐng)域?qū)崿F(xiàn)的實現(xiàn),或是某類輔助類型,用來執(zhí)行一些計算。業(yè)務(wù)邏輯層處于分層系統(tǒng)的中間位置,業(yè)務(wù)邏輯層的輸入和輸出不一定是業(yè)務(wù)對象。很多時候,架構(gòu)師更加傾向于數(shù)據(jù)遷移對象在層之間交換數(shù)據(jù)。
數(shù)據(jù)遷移對象和業(yè)務(wù)對象之間的取舍一直是團隊中爭議的話題,建議使用數(shù)據(jù)遷移對象的理論認為,數(shù)據(jù)遷移能減少層之間的耦合,使系統(tǒng)更加整潔干凈。不過在現(xiàn)實中,人們都會說復(fù)雜性已經(jīng)很高,因此應(yīng)該避免增加任何不必要的對象。一條使用的原則是當(dāng)已經(jīng)有了數(shù)百個業(yè)務(wù)對象時,或許并不應(yīng)該僅僅為了設(shè)計的干凈而讓這個數(shù)字加倍。在這種情況下,數(shù)據(jù)遷移對象通常就是業(yè)務(wù)對象。業(yè)務(wù)對象同時包含了數(shù)據(jù)和行為,是一個可以參與到領(lǐng)域邏輯的完整對象。而數(shù)據(jù)遷移對象更像是一種值。即一系列數(shù)據(jù)的容器而沒有相關(guān)的行為。為了序列化,業(yè)務(wù)對象中的數(shù)據(jù)會復(fù)制到數(shù)據(jù)遷移對象中。除了get/set訪問器以外,數(shù)據(jù)遷移對象沒有邏輯行為。數(shù)據(jù)遷移對象并不僅僅是領(lǐng)域?qū)ο笕サ袅诵袨椋憩F(xiàn)了特定領(lǐng)域?qū)ο蟮囊粋€子集,用于專門的上下文中。一般來說或領(lǐng)域?qū)ο笫且粋€對象圖,而數(shù)據(jù)遷移對象僅僅是所需部分?jǐn)?shù)據(jù)的投射而已。
業(yè)務(wù)對象的屬性來自于其映射的實體的屬性,業(yè)務(wù)對象的方法來自于自身的職責(zé)以及應(yīng)用到該實體上的部分業(yè)務(wù)規(guī)則。業(yè)務(wù)規(guī)則在很大程度上是對數(shù)據(jù)的驗證。換句話說,很多業(yè)務(wù)規(guī)則說到底就是驗證某個業(yè)務(wù)對象的當(dāng)前內(nèi)容。按照這樣的理解,若有專門的驗證層,并讓業(yè)務(wù)對象可選擇的支持,這個設(shè)計將會非常不錯。
業(yè)務(wù)邏輯層不應(yīng)該看作是一個整體的組件,或是一些不相干模塊的組合。多年的實踐經(jīng)驗告訴我們,業(yè)務(wù)邏輯層在其他層中適當(dāng)?shù)闹貜?fù)是可以接受的,也是很多程序的做法。不過這種做法是有一定的限度,且不應(yīng)該受到鼓勵。
業(yè)務(wù)邏輯是系統(tǒng)的核心,不過并不是整個系統(tǒng)。業(yè)務(wù)邏輯設(shè)計上的選擇將會影響到其他層,特別是持久化和數(shù)據(jù)訪問層,這兩層加起來,對項目的成敗產(chǎn)生了決定性的影響。
-
添加引用
在業(yè)務(wù)邏輯層BLL添加對模型層Model、數(shù)據(jù)訪問層接口IDAL、數(shù)據(jù)訪問層工廠類DALFactory的引用。
BLL添加引用
2.創(chuàng)建基礎(chǔ)業(yè)務(wù)邏輯的抽象類
using OA.DALFactory;
using OA.IDAL;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace OA.BLL
{
public abstract class BaseService<T> where T:class,new()
{
/*使用多態(tài)完成子類對父類中當(dāng)前數(shù)據(jù)訪問類的實例化*/
public abstract void SetDbSession();
public BaseService()
{
SetDbSession();//子類必須要實現(xiàn)抽象方法
}
/*獲取當(dāng)前數(shù)據(jù)會話類*/
public IDbSession CurrentDbSession
{
get
{
return new DbSession();
}
}
/*獲取當(dāng)前數(shù)據(jù)訪問類*/
public IBaseDal<T> CurrentDal { get; set; }
/*公共的基礎(chǔ)操作與DAL保持一致*/
public IQueryable<T> Get(Expression<Func<T, bool>> whereLambda)
{
return CurrentDal.Get(whereLambda);
}
public IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T,bool>> whereLambda, Expression<Func<T,s>> orderByLambda, bool isAsc)
{
return CurrentDal.Page(pageIndex, pageSize, out totalCount, whereLambda, orderByLambda, isAsc);
}
public T Create(T entity)
{
CurrentDal.Create(entity);
CurrentDbSession.SaveChanges();
return entity;
}
public bool Update(T entity)
{
CurrentDal.Update(entity);
return CurrentDbSession.SaveChanges();
}
public bool Delete(T entity)
{
CurrentDal.Delete(entity);
return CurrentDbSession.SaveChanges();
}
}
}
- 創(chuàng)建具體的業(yè)務(wù)邏輯類并實現(xiàn)抽象父類中的方法
$ vim OA.BLL/UserInfoService.cs
using OA.Model;
namespace OA.BLL
{
public class UserInfoService : BaseService<UserInfo>
{
public override void SetDbSession()
{
//根據(jù)當(dāng)前數(shù)據(jù)會話類獲取當(dāng)前數(shù)據(jù)訪問類
CurrentDal = this.CurrentDbSession.UserInfoDal;
}
}
}
在業(yè)務(wù)基類BaseService
中完成數(shù)據(jù)會話層類DbSession
的調(diào)用,將業(yè)務(wù)層中公共的方法定義在業(yè)務(wù)基類BaseService
中。公共的方法并不知道通過DbSession來獲取那個數(shù)據(jù)操作類的實例。因此,將該業(yè)務(wù)基類定義成抽象類Abstract class
,并加上一個抽象方法public abstract void SetDbSession()
和一個IBaseDal的屬性public IBaseDal<T> CurrentDal { get; set; }
,并且讓基類的構(gòu)造方法調(diào)用抽象方法CurrentDal = this.CurrentDbSession.UserInfoDal
,目的是在表現(xiàn)層new
具體的業(yè)務(wù)子類時,父類的構(gòu)造方法被調(diào)用,此時執(zhí)行抽象方法(執(zhí)行的是子類中具體的實現(xiàn)),業(yè)務(wù)子類通過DbSession
獲取哪個數(shù)據(jù)操作類的實例。
- 為展示層添加業(yè)務(wù)邏輯層接口
為業(yè)務(wù)邏輯層接口IBLL
添加對模型層Model
和數(shù)據(jù)訪問層接口IDAL
的引用。
IBLL添加引用
創(chuàng)建業(yè)務(wù)邏輯層基類接口
$ vim OA.IBLL/IBaseService.cs
using OA.IDAL;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace OA.IBLL
{
public interface IBaseService<T> where T : class, new()
{
IDbSession CurrentDbSession { get; }
IBaseDal<T> CurrentDal { get; set; }
IQueryable<T> Get(Expression<Func<T, bool>> whereLambda);
IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T, bool>> whereLambda, Expression<Func<T, s>> orderByLambda, bool isAsc);
T Create(T entity);
bool Update(T entity);
bool Delete(T entity);
}
}
創(chuàng)建具體業(yè)務(wù)邏輯層接口
$ vim OA.IBLL/IUserInfoService.cs
using OA.Model;
namespace OA.IBLL
{
public interface IUserInfoService:IBaseService<UserInfo>
{
}
}
- 為業(yè)務(wù)邏輯層BLL基類添加引用和接口
在業(yè)務(wù)邏輯層BLL
中添加對業(yè)務(wù)邏輯層接口IBLL
的引用
添加引用
在具體業(yè)務(wù)邏輯層中添加對應(yīng)的接口實現(xiàn),原則是“先繼承后實現(xiàn)”。
$ vim OA.BLL/UserInfoService.cs
using OA.IBLL;
using OA.Model;
namespace OA.BLL
{
public class UserInfoService : BaseService<UserInfo>,IUserInfoService
{
public override void SetDbSession()
{
//根據(jù)當(dāng)前數(shù)據(jù)會話類獲取當(dāng)前數(shù)據(jù)訪問類
CurrentDal = this.CurrentDbSession.UserInfoDal;
}
}
}
業(yè)務(wù)邏輯層與數(shù)據(jù)訪問層思路是一樣的
- 為防止連續(xù)多次的實例化DbSession,改造成工廠模式以保證線程內(nèi)唯一。
在DALFactory
中創(chuàng)建DbSessionFactory
工廠類
$ vim OA.DALFactory/DbSessionFactory.cs
using OA.IDAL;
using System.Runtime.Remoting.Messaging;
namespace OA.DALFactory
{
public class DbSessionFactory
{
public static IDbSession CreateDbSession()
{
IDbSession dbSession = (IDbSession)CallContext.GetData("dbSession");
if (dbSession == null)
{
dbSession = new DbSession();
CallContext.SetData("dbSession", dbSession);
}
return dbSession;
}
}
}
改造BaseService
$ vim OA.BLL/BaseService.cs
using OA.DALFactory;
using OA.IDAL;
using System;
using System.Linq;
using System.Linq.Expressions;
namespace OA.BLL
{
public abstract class BaseService<T> where T:class,new()
{
/*使用多態(tài)完成子類對父類中當(dāng)前數(shù)據(jù)訪問類的實例化*/
public abstract void SetDbSession();
public BaseService()
{
SetDbSession();//子類必須要實現(xiàn)抽象方法
}
/*獲取當(dāng)前數(shù)據(jù)會話類*/
public IDbSession CurrentDbSession
{
get
{
//return new DbSession();
return DbSessionFactory.CreateDbSession();
}
}
/*獲取當(dāng)前數(shù)據(jù)訪問類*/
public IBaseDal<T> CurrentDal { get; set; }
/*公共的基礎(chǔ)操作與DAL保持一致*/
public IQueryable<T> Get(Expression<Func<T, bool>> whereLambda)
{
return CurrentDal.Get(whereLambda);
}
public IQueryable<T> Page<s>(int pageIndex, int pageSize, out int totalCount, Expression<Func<T,bool>> whereLambda, Expression<Func<T,s>> orderByLambda, bool isAsc)
{
return CurrentDal.Page(pageIndex, pageSize, out totalCount, whereLambda, orderByLambda, isAsc);
}
public T Create(T entity)
{
CurrentDal.Create(entity);
CurrentDbSession.SaveChanges();
return entity;
}
public bool Update(T entity)
{
CurrentDal.Update(entity);
return CurrentDbSession.SaveChanges();
}
public bool Delete(T entity)
{
CurrentDal.Delete(entity);
return CurrentDbSession.SaveChanges();
}
}
}
重點
/*獲取當(dāng)前數(shù)據(jù)會話類*/
public IDbSession CurrentDbSession
{
get
{
//return new DbSession();
return DbSessionFactory.CreateDbSession();
}
}
服務(wù)層與門面層
由于展現(xiàn)層中與業(yè)務(wù)層產(chǎn)生緊密的耦合關(guān)系,在分布式的環(huán)境中,無法分割部署。因此有必要將展現(xiàn)層與業(yè)務(wù)層進行解耦,在其間添加服務(wù)層或門面層,典型如WebService、WCF、WebAPI等技術(shù)。實際上,服務(wù)層主要完成的是對業(yè)務(wù)層實例化的操作,可采用之前的抽象工廠的方式,推薦的方式是使用IoC控制反轉(zhuǎn)的容器來實現(xiàn)。使用第三方的IoC容器,除了能夠統(tǒng)一進行實例化對象外還可對其進行依賴注入DI,即對實例化后對象進行一些初始化的操作。在此,推薦的第三方組建如Sprint.Net、Unity等,都是功能強大的控制反轉(zhuǎn)的容器。
服務(wù)層
在領(lǐng)域模型模式中,大多將服務(wù)層看作業(yè)務(wù)層的一部分,通常來說,服務(wù)層為表現(xiàn)層定義了一個接口,從而允許表現(xiàn)層出發(fā)一些預(yù)定義的系統(tǒng)操作。服務(wù)層可看作是表現(xiàn)層結(jié)束,業(yè)務(wù)邏輯層開始的一個邊界,服務(wù)層用來盡可能降低表現(xiàn)層和義務(wù)邏輯層之間的耦合。讓表現(xiàn)層無需關(guān)注業(yè)務(wù)邏輯層中具體實現(xiàn)組織方式。因此,無論采用任何一種業(yè)務(wù)邏輯模式(表模式、活動記錄、領(lǐng)域模型等),系統(tǒng)都可以提供一個服務(wù)。
實際上服務(wù)層不執(zhí)行任何具體的工作,其功能在于組織各個業(yè)務(wù)對象。服務(wù)層非常了解業(yè)務(wù)邏輯(包括工作流、組件、服務(wù)),進而也非常了解領(lǐng)域模型。服務(wù)層不僅組織業(yè)務(wù)邏輯,還組織應(yīng)用程序?qū)S械姆?wù)、工作流以及其他任何在業(yè)務(wù)邏輯層中的特殊組件。
用服務(wù)作為表現(xiàn)層和業(yè)務(wù)層之間的名字是存在爭議的,這一層可通過Web服務(wù)或WCF服務(wù)實現(xiàn),也可選擇其他技術(shù)。雖然服務(wù)層有服務(wù)一詞,但要將其理解為一個與技術(shù)無關(guān)的詞匯。
服務(wù)層位于系統(tǒng)中兩個相互通信的邏輯層之間,使兩個層能夠在松散耦合并又沒彼此離開的同時,仍舊可完美地相互通信。
每個用戶驅(qū)動的交互的核心都包括兩個參與者:表現(xiàn)層的用戶界面和服務(wù)層實現(xiàn)的用以響應(yīng)用戶操作的模塊。也就是說服務(wù)層不僅用來i組織業(yè)務(wù)邏輯,也許要與持久化層進行交互。所有的交互都源自于表現(xiàn)層,并從服務(wù)層獲取響應(yīng),根據(jù)接收的輸入,服務(wù)層將組織業(yè)務(wù)邏輯層中的組件,包括服務(wù)、工作流、領(lǐng)域模型中的對象,并根據(jù)需要調(diào)用數(shù)據(jù)訪問層。
不僅僅只有服務(wù)層會發(fā)送數(shù)據(jù)操作請求,業(yè)務(wù)還有其他情況,業(yè)務(wù)邏輯層也可能包含一些工作流或業(yè)務(wù)服務(wù)需要使用的數(shù)據(jù)訪問層。業(yè)務(wù)邏輯層唯一需要完全和數(shù)據(jù)庫細節(jié)分離的部分就是領(lǐng)域模型。
服務(wù)從廣義上來講,只要是使用別人的東西那么就是在使用別人提供的服務(wù)。在這里,服務(wù)是指可能被一個或多個系統(tǒng)使用的核心的業(yè)務(wù)邏輯,可見其簡單的想象成一些可供調(diào)用的API。
如何將業(yè)務(wù)邏輯層提供給其他層來調(diào)用呢?
在很多系統(tǒng)中,不是直接將業(yè)務(wù)層的組件引用就可以的,特別是在分布式的系統(tǒng)中,往往在服務(wù)端暴露一些服務(wù)接口,讓其他子系統(tǒng)或外部系統(tǒng)來調(diào)用提供的服務(wù)。
一般來說,服務(wù)層位于業(yè)務(wù)層和表現(xiàn)層之間,當(dāng)前服務(wù)層也可以處于系統(tǒng)與系統(tǒng)之間。服務(wù)層往往提供一些供外部調(diào)用的服務(wù)接口。這些接口是一些粗粒度即提供一些簡單易用功能強大的接口。當(dāng)客戶端調(diào)用接口服務(wù)后,服務(wù)層就開始處理比較復(fù)雜的業(yè)務(wù)邏輯、驗證規(guī)則、持久化數(shù)據(jù)等。
服務(wù)層內(nèi)的邏輯的組織形式類似于Transaction Script模式,可簡單地把服務(wù)層看作一個中介,從客戶端接收請求,通過一系列的步驟后,請求到達服務(wù)層,服務(wù)層開始協(xié)調(diào)和組織所需的業(yè)務(wù)類,把請求的具體處理交給業(yè)務(wù)類來處理,最后將結(jié)果返回給客戶端。
在服務(wù)層的邏輯組織往往是比較過程化的,與Transaction Script不同的是,Transaction Script的每個方法處理一個比較細小而具體的業(yè)務(wù)流程和邏輯。而服務(wù)層的接口往往處理的是一個較大的流程。
Spring.Net
.NET中創(chuàng)建對象最常用的方式是通過new
實例化創(chuàng)建對象,這樣的做法違背了“層與層之間松散耦合”的原則。Spring.Net使用了IoC、DI等概念,提供了一種全新的創(chuàng)建對象的方式。
控制反轉(zhuǎn)IoC,指原來創(chuàng)建對象的權(quán)力由程序來控制即new實例化,IoC則改由容器來創(chuàng)建,相當(dāng)于一個工廠。
依賴注入DI,沒有IoC就沒有DI,依賴注入指的是容器在創(chuàng)建對象時,通過讀取配置文件設(shè)置的默認值,使其在創(chuàng)建時就擁有某些注入的值,用以初始化。
Spring.Net是一個依賴注入的設(shè)計框架,使項目的層與層之間解耦達到更加靈活。