應(yīng)用服務(wù)介紹
對(duì)于一個(gè)新的設(shè)計(jì)元素,可以先假定不需要它,等到確實(shí)認(rèn)識(shí)到它的作用再引入。那么,應(yīng)用服務(wù)為我們帶來了哪些好處呢?
- 應(yīng)用服務(wù)幫助簡化表現(xiàn)層操作
以MVC為例,如果沒有應(yīng)用服務(wù),那么控制器將直接調(diào)用倉儲(chǔ),設(shè)置查詢條件,轉(zhuǎn)換DTO等。
當(dāng)控制器操作變得復(fù)雜,你會(huì)想辦法把控制器代碼分離出去。分離控制器代碼的最好方法就是建立對(duì)應(yīng)的應(yīng)用服務(wù),不要輕易的分離控制器本身,因?yàn)檫@會(huì)導(dǎo)致查找視圖變得困難,并且破壞了約定,導(dǎo)致更高的維護(hù)成本。 - 應(yīng)用服務(wù)為表現(xiàn)層提供唯一的API訪問點(diǎn)
當(dāng)一個(gè)控制器操作變得復(fù)雜時(shí),編寫控制器的程序員需要了解更多的知識(shí),這些操作是由哪些聚合、領(lǐng)域服務(wù)、基礎(chǔ)設(shè)施服務(wù)等構(gòu)造元素組裝起來的呢,調(diào)用順序如何?
這是表現(xiàn)層應(yīng)該關(guān)心的問題嗎?
表現(xiàn)層應(yīng)該只關(guān)心界面展示,這是表現(xiàn)層的基本職責(zé)所在,其它職責(zé)盡量轉(zhuǎn)移到后方。
應(yīng)用服務(wù)將細(xì)粒度操作打包為粗粒度操作,讓編寫表現(xiàn)層的程序員不再東張西望,他只需要記得一件事:需要訪問任何后端的操作,找應(yīng)用服務(wù)就對(duì)了。 - 應(yīng)用服務(wù)為多個(gè)表現(xiàn)層減少冗余代碼
如果需要多個(gè)表現(xiàn)層,比如需要開發(fā)一個(gè)WPF程序,功能與MVC完全一樣,這時(shí)候,你會(huì)發(fā)現(xiàn)表現(xiàn)層充斥大量重復(fù)代碼。
引入應(yīng)用服務(wù)后,表現(xiàn)層代碼更簡潔,冗余代碼更少,創(chuàng)建多個(gè)表現(xiàn)層更加輕松。 - 應(yīng)用服務(wù)集成和裝配領(lǐng)域?qū)优c基礎(chǔ)設(shè)施層
為了保持領(lǐng)域?qū)拥募儍?,我們采用依賴倒置原則,領(lǐng)域?qū)又粨碛胁僮鹘涌?,具體實(shí)現(xiàn)卻在基礎(chǔ)設(shè)施層,最終要能運(yùn)行起來,必須將它們組裝到一起。
如果沒有應(yīng)用服務(wù),控制器將需要自己進(jìn)行裝配,這增加了表現(xiàn)層的負(fù)擔(dān),應(yīng)用服務(wù)承接了這項(xiàng)工作,讓表現(xiàn)層專心干自己的本職工作。 - 應(yīng)用服務(wù)配合DTO優(yōu)化遠(yuǎn)程調(diào)用性能
當(dāng)采用了分布式架構(gòu),為了提升性能,需要盡量減少遠(yuǎn)程調(diào)用次數(shù)。
應(yīng)用服務(wù)在分布式環(huán)境充當(dāng)了遠(yuǎn)程外觀,配合DTO一起解決這個(gè)問題。
應(yīng)用服務(wù)與BLL的比較
對(duì)于長時(shí)間采用傳統(tǒng)三層架構(gòu)的朋友,對(duì)于應(yīng)用服務(wù)這個(gè)構(gòu)造是無比的親切,初步用起來以后,你發(fā)現(xiàn)這個(gè)應(yīng)用服務(wù)就是BLL的翻版。
BLL是傳統(tǒng)三層架構(gòu)的業(yè)務(wù)邏輯層,很多人直接用BLL來命名業(yè)務(wù)邏輯層的服務(wù)類,我暫時(shí)就用這個(gè)稱呼。
BLL是業(yè)務(wù)邏輯放置的場(chǎng)所,BLL將結(jié)果保存到Model實(shí)體類中,然后將實(shí)體類傳遞給其它層。
應(yīng)用服務(wù)用于協(xié)調(diào)領(lǐng)域?qū)优c基礎(chǔ)設(shè)施層的操作,應(yīng)用服務(wù)本身不應(yīng)該完成復(fù)雜的業(yè)務(wù)邏輯。
應(yīng)用服務(wù)與BLL的關(guān)鍵區(qū)別在于,BLL直接完成業(yè)務(wù)邏輯,而應(yīng)用服務(wù)會(huì)將業(yè)務(wù)邏輯委托給領(lǐng)域?qū)嶓w或領(lǐng)域服務(wù)。
從代碼上看,BLL主要通過操作Model的屬性進(jìn)行業(yè)務(wù)邏輯的處理,而應(yīng)用服務(wù)通過調(diào)用領(lǐng)域?qū)嶓w或領(lǐng)域服務(wù)的方法完成所有業(yè)務(wù)操作。
領(lǐng)域服務(wù)介紹
從前面的介紹,你應(yīng)該已經(jīng)發(fā)現(xiàn)應(yīng)用服務(wù)是有用的,下面再來看看領(lǐng)域服務(wù)。
在最理想的情況下,所有的業(yè)務(wù)邏輯都應(yīng)該高度內(nèi)聚到聚合中。但對(duì)于更復(fù)雜的業(yè)務(wù),一般都有比較復(fù)雜的流程,這些流程上的控制,放入聚合并不合適,因?yàn)檫@些行為不屬于某個(gè)聚合。
如果把這些行為放到應(yīng)用服務(wù)中,可能導(dǎo)致應(yīng)用服務(wù)非常復(fù)雜,業(yè)務(wù)邏輯也從領(lǐng)域?qū)用撾x出來,使業(yè)務(wù)邏輯的管理更加困難。
為了把業(yè)務(wù)邏輯重新移回領(lǐng)域?qū)?,需要添加一個(gè)領(lǐng)域服務(wù)。
現(xiàn)在我們有了應(yīng)用服務(wù)和領(lǐng)域服務(wù)兩個(gè)構(gòu)造元素,那么,對(duì)于一個(gè)復(fù)雜操作,應(yīng)該將哪一部分放入應(yīng)用服務(wù),哪些又放到領(lǐng)域服務(wù)呢?
書上介紹,業(yè)務(wù)邏輯可分為領(lǐng)域邏輯和應(yīng)用邏輯,領(lǐng)域邏輯應(yīng)該完全放到領(lǐng)域?qū)樱荒苄孤兜狡渌鼘尤?,而?yīng)用邏輯應(yīng)該放到應(yīng)用層服務(wù)。
可以看出,說得相當(dāng)抽象,哪種東西算得上領(lǐng)域邏輯,哪種又是應(yīng)用邏輯?這個(gè)很難精確劃分。
一個(gè)經(jīng)典的應(yīng)用邏輯例子是事務(wù)控制,事務(wù)控制不是客戶的需求,但卻非常重要,所以把這種東西放到應(yīng)用層,而不是領(lǐng)域?qū)印?br>
那么發(fā)郵件的操作是領(lǐng)域邏輯,還是應(yīng)用邏輯??有人說是領(lǐng)域邏輯,有人說是應(yīng)用邏輯,還有人說要看情況??芍^眾說紛紜,讓你摸不著頭腦。
不過我們學(xué)習(xí)的目的不是進(jìn)行學(xué)術(shù)辯論,而是切實(shí)的提升項(xiàng)目可維護(hù)性,所以不用太理會(huì)那些考智商的概念,下面談?wù)勎业挠梅ā?br>
對(duì)于應(yīng)用服務(wù),我總是需要它,因?yàn)樗梢院喕憩F(xiàn)層開發(fā)。
對(duì)于某些比較簡單的業(yè)務(wù)邏輯,如果發(fā)現(xiàn)建立領(lǐng)域服務(wù)并未產(chǎn)生太大價(jià)值,我就直接放入應(yīng)用服務(wù)中。比如判斷一個(gè)實(shí)體的名稱不能重復(fù),這是一個(gè)業(yè)務(wù)需求,首先考慮這個(gè)業(yè)務(wù)邏輯可以放入實(shí)體中嗎?不能,因?yàn)閷?shí)體本身無法了解自己的名稱有無重復(fù)。其次考慮需要建立領(lǐng)域服務(wù)嗎?對(duì)于這種簡單業(yè)務(wù)邏輯,創(chuàng)建一個(gè)領(lǐng)域服務(wù)有點(diǎn)大炮打蚊子的感覺。我會(huì)直接將重名檢測(cè)放入應(yīng)用服務(wù)中,雖然導(dǎo)致有一點(diǎn)業(yè)務(wù)邏輯散落到應(yīng)用層,但我未發(fā)現(xiàn)對(duì)可維護(hù)性造成影響。
如果業(yè)務(wù)邏輯比較復(fù)雜,我一般會(huì)采用TDD方式推進(jìn)業(yè)務(wù)開發(fā),這種情況下,建立領(lǐng)域服務(wù)是一個(gè)極好的選擇,因?yàn)轭I(lǐng)域?qū)右蕾嚨停?strong>單元測(cè)試更方便。
不論應(yīng)用服務(wù),還是領(lǐng)域服務(wù),開發(fā)的基本原則是,盡量將業(yè)務(wù)邏輯委托給領(lǐng)域?qū)嶓w,服務(wù)僅做調(diào)度的工作。
基礎(chǔ)設(shè)施服務(wù)比較好理解,就是提供一些基礎(chǔ)支持服務(wù),比如發(fā)送郵件等操作。
需要為服務(wù)抽取接口嗎
學(xué)習(xí)設(shè)計(jì)模式時(shí),要求我們面向接口編程,但一些對(duì)象大師建議不要過度設(shè)計(jì),以免導(dǎo)致不必要的復(fù)雜性,在《實(shí)現(xiàn)領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》一書中提出,應(yīng)用服務(wù)使用獨(dú)立接口沒有什么好處。
雖然接口有不少作用,但一般來講,驅(qū)動(dòng)創(chuàng)建接口的真正需求來自多態(tài)。在一般的項(xiàng)目開發(fā)中,特別是前期,服務(wù)很少具有多態(tài)需求,那我們還要不要為服務(wù)建立接口?
在沒有使用Resharper的年代,我使用接口非常小心謹(jǐn)慎,因?yàn)閺慕涌诙ㄎ粚?shí)現(xiàn)類非常困難,但自從用上了Resharper,接口不再成為障礙。
另一方面,IOC框架的使用,讓接口的使用進(jìn)一步普及,我只需要將實(shí)現(xiàn)類綁定到接口,在程序中就可以自由使用了。雖然IOC可以直接綁定實(shí)現(xiàn)類,但總感覺有點(diǎn)奇怪。
最后,對(duì)于跨層訪問,采用接口互聯(lián)也符合分層設(shè)計(jì)原則。
對(duì)于采用了Ioc框架,并安裝了Resharper這樣強(qiáng)大的工具,接口不會(huì)給你造成任何不便,至于是否導(dǎo)致復(fù)雜性,則需要自己體會(huì)。
代碼中采用接口編程,可能在大多情況下都未產(chǎn)生實(shí)質(zhì)的好處,但在需求發(fā)生變化時(shí),只需修改Ioc配置替換相關(guān)實(shí)現(xiàn),這個(gè)擴(kuò)展性在不花成本的情況下還是劃得來的。
服務(wù)命名約定
當(dāng)同時(shí)需要應(yīng)用服務(wù)和領(lǐng)域服務(wù),會(huì)發(fā)現(xiàn)服務(wù)取名也很困難。比如用戶管理,應(yīng)用服務(wù)叫UserService,如果需要領(lǐng)域服務(wù),也叫UserService就發(fā)生沖突 ,但命名為其它可能又不合適。
我的辦法是應(yīng)用服務(wù)統(tǒng)一以Service結(jié)尾,領(lǐng)域服務(wù)統(tǒng)一以Manager結(jié)尾,這樣就可以簡單的解決這個(gè)問題。對(duì)于和我一樣的英文菜鳥,可能特別有用。
領(lǐng)域服務(wù)(Domain Service)總結(jié)
- 對(duì)應(yīng)領(lǐng)域中的一個(gè)有意義的跨多個(gè)領(lǐng)域?qū)ο蟮牟僮骰騽?dòng)作,如資金轉(zhuǎn)賬
- 以動(dòng)詞開頭來命名,如TransferService
- 協(xié)調(diào)多個(gè)領(lǐng)域?qū)ο?/strong>完成一個(gè)操作
- 無業(yè)務(wù)狀態(tài)
- 封裝業(yè)務(wù)邏輯,避免領(lǐng)域邏輯泄露到應(yīng)用層
- DDD中的三種服務(wù):應(yīng)用層服務(wù)、領(lǐng)域服務(wù)、基礎(chǔ)服務(wù)
區(qū)分三種服務(wù)的例子
比如應(yīng)用層有一個(gè)資金轉(zhuǎn)帳的服務(wù),該應(yīng)用層服務(wù)主要做以下事情:
- 獲取輸入(如一個(gè)XML請(qǐng)求)
- 發(fā)送消息給領(lǐng)域?qū)臃?wù),要求其實(shí)現(xiàn)轉(zhuǎn)帳的業(yè)務(wù)邏輯
- 領(lǐng)域?qū)臃?wù)處理成功,則調(diào)用基礎(chǔ)層服務(wù)發(fā)送Email通知
領(lǐng)域?qū)拥姆?wù)做以下事情:
- 獲取源帳號(hào)和目標(biāo)帳號(hào),分別通知源帳號(hào)和目標(biāo)帳號(hào)進(jìn)行扣除金額和增加金額的操作
- 提供返回結(jié)果給應(yīng)用層
基礎(chǔ)層服務(wù)做以下事情:
- 按照應(yīng)用層的請(qǐng)求,發(fā)送Email通知
示例
IRoleAppService.cs
using Rdf.Application.Act.Dtos;
using Rdf.Application.Services.Dto;
using System.Threading.Tasks;
namespace Rdf.Application.Act
{
public interface IRoleAppService
{
Task<JsonMessage> Create(CreateRoleInput model);
Task<JsonMessage> Update(UpdateRoleInput model);
Task<JsonMessage> Delete(DeleteRoleInput model);
Task<JsonMessage> GetInfo(GetRoleInfoInput model);
JsonMessage GetAll(GetAllRoleInput model);
JsonMessage BatchDelete(BatchDeleteInput model);
JsonMessage GetPage(PageInput model);
}
}
RoleAppService.cs
using AutoMapper;
using Rdf.Application.Act.Dtos;
using Rdf.Application.Services.Dto;
using Rdf.Domain.Entities.Act;
using Rdf.Domain.Repositories;
using Rdf.Domain.Uow;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Rdf.Application.Act
{
public class RoleAppService : IRoleAppService
{
private readonly IUnitOfWork _uow;
private readonly IRepositoryBase<Role> _roleRepository;
public RoleAppService(IUnitOfWork uow, IRepositoryBase<Role> roleRepository)
{
this._roleRepository = roleRepository;
this._uow = uow;
}
public async Task<JsonMessage> Create(CreateRoleInput model)
{
var entity = Mapper.Map<CreateRoleInput, Role>(model);
entity.Id = Guid.NewGuid();
await _roleRepository.InsertAsync(entity);
await _uow.SaveChangesAsync();
return GetErrorMsgByErrCode(0);
}
public async Task<JsonMessage> Update(UpdateRoleInput model)
{
var entity = Mapper.Map<UpdateRoleInput, Role>(model);
await _roleRepository.UpdateAsync(entity);
await _uow.SaveChangesAsync();
return GetErrorMsgByErrCode(0);
}
public async Task<JsonMessage> Delete(DeleteRoleInput model)
{
await _roleRepository.DeleteAsync(b => b.Id == model.Id);
_uow.SaveChanges();
return GetErrorMsgByErrCode(0);
}
public async Task<JsonMessage> GetInfo(GetRoleInfoInput model)
{
var entity = await _roleRepository.SingleAsync(b => b.Id == model.Id);
JsonMessage jsonMsg = GetErrorMsgByErrCode(0);
jsonMsg.Result = Mapper.Map<Role, RoleDto>(entity);
return jsonMsg;
}
public JsonMessage GetAll(GetAllRoleInput model)
{
var entity = _roleRepository.GetAll().OrderBy(item => item.DisOrder).ToList();
JsonMessage jsonMsg = GetErrorMsgByErrCode(0);
jsonMsg.Result = Mapper.Map<List<RoleDto>>(entity);
return jsonMsg;
}
public JsonMessage BatchDelete(BatchDeleteInput model)
{
return null;
}
public JsonMessage GetPage(PageInput model)
{
if (string.IsNullOrEmpty(model.OrderBy))
model.OrderBy = "DisOrder";
object entity;
int total = 0;
if (!string.IsNullOrEmpty(model.Keyword))
{
entity = _roleRepository.Get(p => p.Name.Contains(model.Keyword), model.OrderBy, model.PageIndex, model.PageSize);
total = _roleRepository.Count(p => p.Name.Contains(model.Keyword));
}
else
{
entity = _roleRepository.Get(model.OrderBy, model.PageIndex, model.PageSize);
total = _roleRepository.Count();
}
JsonMessage jsonMsg = GetErrorMsgByErrCode(0);
jsonMsg.Result = new PageOutput() { Total = total, Records = Mapper.Map<List<RoleDto>>(entity) };
return jsonMsg;
}
private static List<JsonMessage> ErrMsgList = new List<JsonMessage>()
{
new JsonMessage() { ErrCode = 0, ErrMsg = "請(qǐng)求成功"}
};
public JsonMessage GetErrorMsgByErrCode(int errCode)
{
var model = ErrMsgList.FirstOrDefault(p => p.ErrCode == errCode);
return new JsonMessage()
{
ErrCode = model.ErrCode,
ErrMsg = model.ErrMsg
};
}
}
}