系統架構師談企業應用架構之服務層
一、上章回顧
上篇我們主要講解了系統架構中的四種架構模式,并且分析了四種架構模式的實現及應用場景,那么先來回顧下架構中的業務邏輯層的使用及總結。
如果大家對圖中講述的內容不明白或者說是不深入那么可以參考上篇講
解的內容:系統架構師-基礎到企業應用架構-業務邏輯層。
二、摘要
本文將已架構的方式去分析分層結構中的服務層的設計,如何設計出來滿足我們說的業務需求及設計規范的服務層將是我們的目標,可能我想大家在項目架構的
過程中可能有些同仁,沒有用到該層,或者說是采用的是常用的分層結構的設計,而沒有把服務層單獨的抽出來,當然我們必須首先知道服務層是干什么用的?為什么
要單獨寫一個服務層呢?還有就是設計服務層我們從哪些方面入手呢?及怎么判定一個服務層設計的好壞呢?這些都是本章要講解的具體內容,當然本文中的內容都是
本人平時在項目中的一些經驗,可能在一些有豐富經驗設計的大牛面前,我講解的都是皮毛,但是我抱著能給初學者指引和為已知者溫習的目的而寫,錯誤之處再所難
免,請大家提出寶貴意見和建議。本文講述以下內容:
下面我們將針對上面的問題分別進行講述。
三、本章大綱
1、上章回顧。
2、摘要。
3、本章大綱。
4、服務層的介紹。
5、服務層實戰。
6、本章總結。
7、系列進度。
8、下篇預告。
四、服務層的介紹
本節中將會對服務層的設計進行詳細的分析。我們知道我們現在在軟件開發的過程中,通常我們會將一些業務邏輯的代碼寫在表現層,當然這樣的方式不是不允
許,當然一般情況下來說我們感覺沒什么,但是采用這樣的方式,那么表現層與業務邏輯層之間的關系是耦合性的,可能我們是屬于那種希望設計簡潔或者說對設計規
范嚴格要求的時候,那么我們就可以考慮將業務邏輯的組織通過服務層來實現,那么服務層的作用就是將表現層與業務邏輯層之間完成解耦。那么表現層中就不會出現
任何的業務代碼,當然這樣帶來的好處也是顯而易見的,就是當我們修改業務層代碼時,我們不需要修改表現層的代碼,當然如果服務層設計的不好,那么可能會造成
反效果。
服務層是干什么的? 通過上面的簡單介紹,我想大家都對服務層有了個簡單的認識,那么下面我們還是通過圖形的方式來看看服務層的位置及作用吧,可能那樣
更容易理解。
這幾層之間都是通過數據傳輸對象來完成各層之間的數據通信。通過上圖我們知道,服務層是通過數據傳輸對象
與業務邏輯層直接進行交互,那么業務邏輯層與服務層具體交互的方式及內容是什么呢?
下面我們來看看,業務邏輯層中的四種模式在服務層中的表現。
下面我們來舉例說明服務層的作用。通過表現層與業務邏輯的解耦來說明服務層的作用。我們還是以B2C中的購物流程來說。
我們先以購物流程中的添加產品到購物車來說
可以簡單的看作下面幾個對象之間的交互,首先我們先要選擇產品,然后將產品添加到購物車
中,然后當然這個購物車是某個會員登陸以后的購物清單,一個購物車中可能有多個產品。我們來看看代碼可能如下:
我們定義的產品信息如下:
- /// <summary>
- /// 產品信息
- /// </summary>
- public class Product
- {
- /// <summary>
- /// 返回所有的產品信息
- /// </summary>
- /// <returns></returns>
- public List<Product> GetAll()
- {
- return new List<Product>();
- }
- /// <summary>
- /// 返回產品信息根據產品ID
- /// </summary>
- /// <returns></returns>
- public Product GetByID(int ID)
- {
- return new Product();
- }
- }
產品信息中包含2個方法,一個是獲取所有產品的列表,還有一個是獲取實體的信息根據主鍵。我們來看看購物車的代碼:
- /// <summary>
- /// 購物車
- /// </summary>
- public class ShopCar
- {
- /// <summary>
- /// 購物車中的產品
- /// </summary>
- Dictionary<int, Product> products = new Dictionary<int, Product>();
- /// <summary>
- /// 將指定產品ID的產品添加到購物車
- /// </summary>
- public bool Add(int ID)
- {
- Product product = new Product();
- product= product.GetByID(ID);
- if (products.ContainsKey(ID))
- return false;
- products.Add(ID,product);
- return true;
- }
- }
下面我們來看看前臺調用的代碼:
- public class ShopCar
- {
- /// <summary>
- /// 將指定產品ID的產品添加到購物車
- /// </summary>
- public bool Add(int ID)
- {
- ShopCar cart = new ShopCar();
- return cart.Add(ID);
- }
- }
上面的代碼引用了ShopCar對象,說明UI層的ShopCar與業務層的ShopCar 有依賴關系,那么我們如何解耦呢?通過引入第三方類,來實現依賴的解除。具體
的代碼如下:
- public class ShopCar
- {
- /// <summary>
- /// 將指定產品ID的產品添加到購物車
- /// </summary>
- public bool Add(int ID)
- {
- IShopCar car;
- CarFactory factory = new CarFactory();
- car = factory.ShopCarFactory();
- car.Add(ID);
- return true;
- }
- }
修改后通過服務層中的購物車工廠構造出購物車實例,這樣就可以把業務邏輯層與界面層之間的耦合關系通過新增加一個服務層來實現解耦,那么表現層的代碼更
簡介,也符合設計規范。
其實我們這里的服務只是講述了服務層的簡單應用場景,其實具體的服務層在使用的過程中遠比我們前面講解的復雜,首先可能服務層還會與數據訪問層直接交
互,比如說操作數據庫,持久化等,服務層主要是組織業務邏輯中的業務邏輯組件,服務層還通過ORM框架中提供的數據庫訪問服務完成相應的操作。我們前面講過,
一般情況下,表現層與服務層交互是,都是通過數據傳輸對象來進行通信的,那么顯然有時候我們需要在服務層做處理,將數據傳輸對象轉換成領域模型,當然有時候
我們在設計系統架構時,如果系統中的領域模型較多,或者說是拆分后的數據傳輸對象太多時,我們只要將一個領域模型對應一個數據傳輸對象就好,只不過是把領域
模型中的行為省略而已。
我們來看看目前比較流行的關于服務層的認識:
我想目前最流行的關于服務層的架構就是SOA(面向服務架構),可以說是SOA的出現才讓服務層流行起來,SOA中對服務的理解是這樣的,服務層是提供一系
列的服務,而具體的業務流程是通過一系列的服務組成的,把服務看作不是面向某種特定的技術,而是業務流程的組織方式。達到的目的是,提供了一系列的服務,只
需要配置組織服務的流程,就可以不管什么樣的技術,都能滿足要求的業務流程。當然這是理想化的形式,具體的實行起來還是有相當大的難度。
其實我們可以這樣想象,服務就是一個提供了API的類,通過封裝,外界訪問服務時只能通過服務提供的接口來訪問。
例如我們通過服務層提供上述的四種服務,然后在表現層中通過
服務層中的服務的調用來完成相應的功能,比如我們在表現層中有新紀錄添加時,我們通過服務層的添加記錄的方法來完成,服務層通過提供的服務直接完成相應的準備
工作,并且這些服務在定義時都是通過接口的方式來向外提供功能,因此只要滿足接口契約就可以完成組件的替換工作,而不會影響系統的運行。
我們有的時候有這樣的需求,我們的軟件程序要求既有B/S的形式直接通過瀏覽器來完成應用,有時候還需要C/S客戶端的形式訪問系統的功能,這時候我們如果
通過服務來組織業務邏輯那么我們只需要寫一個服務層就可以完成遠程服務訪問,而不用B/S下寫一次業務邏輯調用,然后C/S下再寫一次,而且這樣一旦修改了相應的
業務邏輯,那么我們需要變動的代價很大。我們來看看服務層給我們提供了什么。
通過服務層我們可以不關心業務邏輯的實現,我們在用戶圖形
化界面中只需要訪問相關的服務即可,舉個簡單例子,就行銀行的銀聯,跨行取款的行為。下面可以簡單的描述了用戶取款的服務。
上面簡單的描述了,用戶的取款服
務,當然只要是銀聯的銀行卡,都可以享受到跨行的異地取款,當然不管什么卡,提供給用戶的服務都是相同的。當然我們這里說的是C/S客戶端服務這樣的要求,當
然如果說是B/S架構的形式,那么可能我們不用單獨抽出這樣的遠程服務的形式。而服務層可能就是表現層的一部分,這時候我們建議不要把服務層設計成web服務,這
時候我們設計服務層時更關心服務層的抽象,而不是實現方式。
一般情況下來說服務層的設計實現,可能有時候和部署時的要求有關,例如有時候我們需要將服務部署在應用服務器上,這時候我們就必須考慮服務層必須發布成
遠程調用服務或者Web服務,當然具體的遠程調用服務可以有幾種方式,我們主要看基于什么通信方式,是remoting還是soap,還是socket通信等。
當然有時候我們對服務分為二種類型,粗粒度的服務與細粒度的服務,那么我們怎么理解它呢?其實說白了粗粒度的服務就是某個大的服務,而細粒度的就是大
的服務內部的子服務??梢赃@樣理解,粗粒度是按照用例來組織操作的,例如我們上面的銀行取款服務,那么粗粒度的服務就看作這樣的流程,用戶插卡-輸入密碼-取
款-退卡。而細粒度的服務關系的是:檢查用戶賬戶,余額的轉換等,包括一些比較詳細的,密碼的驗證等等,這些都是細粒度的服務,可以把粗粒度看作某個用例的大
的業務操作,對領域中的對象不關心,只關心領域模型中的交互。細粒度可以看作領域模型中的具體對象及對象的行為。
接下來我們將講述服務層常用的幾種架構模式:
第五節中將會詳細的講述每種模式及模式直接的區別及應用。
我們來看看服務層的應用場景及什么情況下使用服務層?
一般來說服務層適合企業應用系統中,也適合多層系統中,特別是業務邏輯比較復雜的系統程序中,如果說應用程序在多中形式的終端上使用時,推薦使用服務
層,當然如果你的系統中只有一種類型的前端表現形式時,例如Web應用程序,只有一個前端要求時,例如通過瀏覽器訪問的形式,并且業務邏輯層能夠很好的與表現層交互時,那么如果我們還把業務邏輯與表現層直接的通信抽出來通過服
務層來完成的話,那么服務層只是完成任務的轉發,并沒有實際行的好處及減少開銷,那么此時不推薦使用服務層。
我們來總結下服務層的優勢與劣勢,已衡量我們在系統中使用服務層的必要性:
五、服務層實戰
前面已經講述了服務層的優缺點及應用場景的介紹,那么我們本節中將要詳細的講解服務層設計的幾種模式,主要是體現出服務層設計實現的幾個思路,方便我
們在項目的實踐過程中少走彎路。為我們的系統帶來更好的適應性及擴展性要求。我們閑來將第一類模式
裝飾模式在服務層的含義,跟設計模式中的裝飾模式可以說是有著異曲同工之妙,就是將服務層的一系列方法包裝成干凈
的接口,以供外部調用,通過該模式,我們能夠將細粒度的服務包裝成粗粒度的服務,這樣可以更方便的為表現層服務,并且可以通過裝飾模式將服務包裝成遠程調用
服務的方式,具體內部的實現都不是主要的關注點,對客戶來說,他們可以以統一的方式不管客戶端的形式是怎么樣的。
我們都知道面向對象的設計原則是要求某個對象功能單一,盡可能的簡單,但是通常我們在表現層中的一些業務流程中要求有多個實體之間進行交互時,那么我
們通過服務來組織就會顯得比較好,我們通過裝飾模式來實現這樣的業務要求就會比較好,我們將一系列細粒度比較復雜的業務邏輯放在一個服務的API方法中,表現層
通過調用這個方法來完成復雜的業務邏輯交互。
服務層中的裝飾模式更關心的是如何為表現層提供更好的服務,隱藏內部的細節,下面我們來看看相關的例子吧,我們這里還是以購物流程來說吧。
購物流程的簡單流程可能如此,那么當然我們在購物流程中還有其他的服務,比如我們在購物的過程中的
短信提醒給賣家,或者說是發送郵件給賣家。等這些是我們系統提供的服務,包括一些日志性的服務。那么這些我們如何去做呢?當然在上面的流程中,用戶只需要關
系最后的一步,提交訂單,付款的環節,當用戶付款后會給用戶發送短信,那么顯然我們在服務中就可以把下單的過程中默認提供系統日志,系統安全,權限等等問題
一并處理,而給客戶提供的方法則只包含支付的接口。
- public interface IPay
- {
- /// <summary>
- /// 支付接口
- /// </summary>
- /// <param name="product"></param>
- void Pay(Rule.Product product);
- }
上面我們定義了支付的接口,來看業務層中的訂單操作:
- public class Order
- {
- /// <summary>
- /// 添加產品
- /// </summary>
- /// <returns></returns>
- public int Add(Product product)
- {
- return 0;
- }
- /// <summary>
- /// 保存
- /// </summary>
- /// <returns></returns>
- public int Save()
- {
- return 0;
- }
- /// <summary>
- /// 刪除
- /// </summary>
- /// <returns></returns>
- public int Delete()
- {
- return 0;
- }
- /// <summary>
- /// 更新
- /// </summary>
- /// <returns></returns>
- public int Update()
- {
- return 0;
- }
- }
我們來看看服務類中接口的實現:
- public class Pay : IPay
- {
- public bool PayMent(Rule.Product product)
- {
- //具體的下單操作
- Rule.Order order = new Rule.Order();
- //持久化操作
- order.Add(product);
- //發送手機短信,發送給賣家,通知有人買什么產品等
- SendMessage.Instance.SendMsg(string.Empty, string.Empty);
- return true;
- }
- #region IPay 成員
- public void IPay.Pay(Rule.Product product)
- {
- this.PayMent(product);
- }
- #endregion
- }
那么上面我們在服務層組合多個簡單的服務提供給一個方法,那么UI層只要簡單的調用服務層接口中提供的方法即可,就能完成服務的調用。我們來看看UI層的代碼
- public class Order
- {
- public void Pay()
- {
- Service.PayFactory factory=new Service.PayFactory();
- //調用服務層中的支付
- Service.IPay pay = factory.CreatePay();
- //這里只是測試,所以沒有屏蔽New的代碼
- pay.Pay(new Rule.Product());
- }
- }
那么通過上面的簡單形式我們就完成了一個簡單的裝飾模式的服務層的設計,是不是很簡單呢?可能看起來代碼有點多,不過這樣的設計很利于我們在后期的擴展
性和適應性,特別是等到系統的功能更復雜更多時。好的設計就能體現出它的價值了。
當然上面我們通過了直接使用領域模型中的對象作為數據傳輸,當然我們可以通過數據傳輸對象的自定義對象來完成,情況更好,我這里就不舉例說明了,下面我
們來講述下一個模式:傳輸對象模式。
那么我們前面也講過了數據傳輸對象,其實這個模式只是講解了數據傳輸對象的用法。
傳輸對象模式:
該模式主要是針對系統中各分層之間的數據傳輸模式的設計,通過傳輸對象可以降低各層之間分發數據的次數,提高系統性能,通常來說該模式非常有用。但是也
有它的弊端。比如說當領域模型太多的時候,如果把領域模型中的每個對象的數據載體,都設計成傳輸對象,那么系統將是一個非常龐大的工程,因為過度設計,讓系
統難于維護與控制。我們來總結下使用該模式的優缺點:
那么有優點肯定就有缺點,我們來看看傳輸對象可能帶來的劣勢:
現在目前我們在使用數據傳輸對象的時候,都必須手動的去維護及創建,目前沒有比較好的工具去完成自動創建的功能。比如說能將同一個對象,根據不同UI的需
求自動的將一些屬性屏蔽或者啟用等??赡芡ㄟ^XML配置文件來完成會是可行的方案,不過目前還沒有一個比較好的工具去自動的根據領域模型中的對象自動的創建傳
輸對象,然后還能提供這個傳輸對象根據不同UI界面要求完成不同的自定義配置功能,希望各位如果了解的可以給小弟指點下,跪求!
傳輸對象模式我想具體的實例代碼我就簡單的書寫下吧,就是把對象中的行為去掉,只包含數據信息,就和我們平時說的3層結構中的Model層一樣,只有get;set;
訪問器和私有成員變量,我們來看看實例代碼吧?
- /// <summary>
- /// 產品信息
- /// </summary>
- public class Product
- {
- private int _pro_id;
- private string _pro_property = string.Empty;
- private string _pro_cid;
- private int? _pro_brandid;
- private string _pro_name;
- private string _pro_model;
- /// <summary>
- /// 產品ID
- /// </summary>
- public int pro_ID
- {
- set
- {
- _pro_id = value;
- }
- get
- {
- return _pro_id;
- }
- }
- /// <summary>
- /// 擴展屬性值
- /// </summary>
- public string pro_Property
- {
- set
- {
- _pro_property = value;
- }
- get
- {
- return _pro_property;
- }
- }
- /// <summary>
- /// 商品分類
- /// </summary>
- public string pro_CID
- {
- set
- {
- _pro_cid = value;
- }
- get
- {
- return _pro_cid;
- }
- }
- /// <summary>
- /// 商品品牌
- /// </summary>
- public int? pro_BrandID
- {
- set
- {
- _pro_brandid = value;
- }
- get
- {
- return _pro_brandid;
- }
- }
- /// <summary>
- /// 商品名稱
- /// </summary>
- public string pro_Name
- {
- set
- {
- _pro_name = value;
- }
- get
- {
- return _pro_name;
- }
- }
- /// <summary>
- /// 商品型號
- /// </summary>
- public string pro_Model
- {
- set
- {
- _pro_model = value;
- }
- get
- {
- return _pro_model;
- }
- }
- }
我這里提供一個我認為的生成領域模型的思路,主要還是通過XML文件來完成,將具體的數據傳輸對象不是通過類文件的形式來完成,通過序列化成XML文件來完
成,這樣就相當于每個XML對應一個序列化文件,然后這個文件中會保存相應的配置信息,比如說哪個頁面顯示哪些字段,那個頁面調用這個類時不掉用這個頁面。具
體的配置通過提供一個可視化的方式來維護就好了,然后在前臺綁定的時候根據讀取或者寫入XML文件來完成,可能這也是比較靈活的方式,具體的實現我沒有去做,
請大家提出更好的思路,小弟謝過!
我們接下來講述第三種模式:適配器模式,這個也是設計模式中最常用的設計模式的一種,適配器模式的主要作用是將某個接口轉換成我們需要的另外一個接口,
這個怎么理解呢?
我們把手機服務包裝成MP3的接口,或者把MP3的接口包裝成手機,都是可以的,可能我這里的例子舉得不合適
但是意思就是將某種服務,通過適配器轉換成另外一種服務。
我這里簡單的講解幾個例子來完整適配器模式的介紹,我們先以將傳輸對象轉換為我們的領域模型中的對象,通過適配器來完成數據的轉換。
我們先來看看不通過適配器模式來完成領域對象中的類與傳輸對象之間的交互,通過構造函數注入的方式來完成。
- /// <summary>
- /// 產品信息
- /// </summary>
- public class ProductCase
- {
- private Product _product;
- public ProductCase(Product product)
- {
- _product = product;
- }
- /// <summary>
- /// 產品ID
- /// </summary>
- public int pro_ID
- {
- set
- {
- _product.pro_ID = value;
- }
- get
- {
- return _product.pro_ID;
- }
- }
- /// <summary>
- /// 擴展屬性值
- /// </summary>
- public string pro_Property
- {
- set
- {
- _product.pro_Property = value;
- }
- get
- {
- return _product.pro_Property;
- }
- }
- /// <summary>
- /// 商品分類
- /// </summary>
- public string pro_CID
- {
- set
- {
- _product.pro_CID = value;
- }
- get
- {
- return _product.pro_CID;
- }
- }
- /// <summary>
- /// 商品品牌
- /// </summary>
- public int? pro_BrandID
- {
- set
- {
- _product.pro_BrandID = value;
- }
- get
- {
- return _product.pro_BrandID;
- }
- }
- /// <summary>
- /// 商品名稱
- /// </summary>
- public string pro_Name
- {
- set
- {
- _product.pro_Name = value;
- }
- get
- {
- return _product.pro_Name;
- }
- }
- /// <summary>
- /// 商品型號
- /// </summary>
- public string pro_Model
- {
- set
- {
- _product.pro_Model = value;
- }
- get
- {
- return _product.pro_Model;
- }
- }
- }
上面的方式通過構造函數的注入完成相應的訪問,通過get;set;訪問器來完成。
下面我們通過適配器的方式來實現轉換,看看有什么不同:
- public class ProductTest
- {
- private Product _product;
- public ProductTest(Product product)
- {
- ProductAdapter adapter = new ProductAdapter(product);
- adapter.InitDTO(this);
- }
- }
下面看看具體的適配器中的代碼:
- public class ProductAdapter
- {
- private Product _product;
- public ProductAdapter(Product product)
- {
- this._product = product;
- }
- public bool InitDTO(ProductTest test)
- {
- //賦值的過程,將Product中的信息轉換為ProductTest對象
- test.pro_BrandID = _product.pro_BrandID;
- //...
- return true;
- }
- }
我們上面看到了,通過依賴注入的形式,將要包裝的接口傳入到適配器,然后在適配器的內部進行相應的包裝,傳出包裝后的接口,這就是一個完整的適配器流
程,具體的業務邏輯就是根據需要來做了。通過上面的方式我們的確完成了相應的轉換,不過轉換的代價是非常的大,不過有的時候我們的業務需求是這樣的,可能我
們也沒有更好的辦法,只能通過這樣的方式來做,可能對解決方案的實現比效率更有價值。其實我們在使用傳輸對象的時候還是需要仔細的斟酌項目的需求,看看是不
是必須要使用這個,如果不是必須的,其實我們可以不需要強迫性的使用。
六、本文總結
本章主要講述了系統架構中的服務層的架構中的注意事項及幾個簡單的設計模式及使用,并且講述了服務層應用的場景和帶來的好處,當然我們也需要服務層的
優劣處,還有就是服務的實現方案,本文前面可能沒有講解發布服務的幾種方式,這里簡單的用圖來說明下吧?
WCF已經內置繼承了remoting,socket和SOAP的方式來進行遠程調用服務,當然HTTP方式的SOAP的服務方
式,還是推薦使用Web服務的方式來做。
模式需要注意的事項,將會舉例說明每個設計模式可能出現的場景。希望大家持續關注!
作者:CallHot-何戈洲
出處:http://www.cnblogs.com/hegezhou_hot/
【編輯推薦】