之前我們已經完成了服務層,因為當時展現層還沒有出來,所以只做了簡單介紹。傳送門:項目架構開發:服務層(上)
這次我們通過一個維護系統用戶的場景來介紹一下服務層真正的設計用意。
1、新增用戶場景
新增用戶可能會有以下步驟
實現以上需求,開發人員一般情況下可能就是以上 藍紅黑紫綠 幾種選擇
1、有些寫在Controllers、有些寫在Application
2、都寫在Controllers或都寫在Application
3、有些寫在DAL層、甚至存儲過程
特別是新手以及那些拿來主義的人,他們不會花更多時間去思考。
分不清各層的職責以及界限,迷信那句:不管黑貓白貓,抓到老鼠就是好貓
最后造成劣質的代碼滿天飛,到處(無論哪個公司或項目)都充斥着這種隨心所欲的代碼
分層已經失去意義,因為整個系統在局外人看來已經只有一層,那就是業務邏輯層,上至html、下至數據庫存儲過程
都充滿業務邏輯。聯想一下,這只是用戶注冊而已,還有更加復雜的業務場景還沒說出來。不知道最后會怎樣
這樣的系統已經完完全全一團面條了,后續維護已經與個人技能無關了,就算請一線的NB程序員,想來他也只會說一句,去年買了個表
好了,吐槽了一下現狀(不管你們同不同意,反正我是不會收回的,哈)
有沒有方法解決上面這種窘境呢,當然是有的,比如我就覺得那種藍色的方案勉強還可以
在UI層解決數據合法性校驗,在邏輯層完成功能性問題,分工還算明確,這種方案對付一般的簡單業務場景是足夠了
但是如果業務場景足夠復雜,那就不行了,因為這會造成2個明顯的問題
1、UI層不夠薄,這層只是調用后台的方法而已,按道理校驗的事情是無需管的(我說的是cs層的校驗,不是js)
這就像去銀行辦業務,用戶不管錢假不假(因為可能不知道),夠不夠(可能也不知道,比如他想存一分錢)
這些銀行員工告訴他就可以了
2、業務邏輯不能重用,比如場景1要求注冊用戶需要完成1-4項,場景2要求完成2-5項,場景3、場景4。。。。
這就蛋疼了,我們要為每一個場景實現校驗到存儲的所有過程,代碼冗余很大,到處都是相似的代碼。
但是有的開發會說,難道我不能再一個方法里邊寫if else switch碼?那樣不就可以兼容其他場景了?
當然可以那樣,這就是那個好貓了,從此以后整個項目就開始墮落,前面一撥人胡亂搞完拍拍屁股走了,
后邊接手的也只能往上邊扔更多的扭紋柴了,只到開始改動一個逗號“,”,都會影響其他功能,項目已經徹底走不下去了
那怎么辦?用老板的方法:很簡單嘛,項目停掉暫且用着,然后招一批新人從新開發系統,邊上邊用。把業務都轉移到新系統上
然后新的開發做完系統(可能還沒到上線)就拍拍屁股走了,后邊的接手的人。。。。然后一個循環的怪圈就開始了。。。
話題轉回來,這么樣解決這種窘境,比較合適的業界比較推崇的方法就是加入服務層,來看看加入服務層后的邏輯分層
2、加入服務層后的邏輯分層
如何實施呢?我的想法是,UI層只做調用的操作,在構造函數中注入service或直接在Action中調用遠程服務方法就好了
當然也可以做一些比如String.IsNullOrEmpty、“value”.ToInt(1);的操作,不過這並不代表服務層就不需要做
服務層還是需要重復校驗輸入參數是否合法的,所以上邊的操作基本沒啥卵用,因為服務層從設計角度來說他是不信任任何輸入的
不然單元測試都過不去,那服務層到底做些什么呢?可以大概分為以下幾個方面
1、解耦UI層與業務邏輯的強耦合
2、較少業務邏輯調用次數
3、層次更加分明,代碼簡潔
4、部署靈活,以后做集群很方便
如果沒有服務層,UI層Controller是怎么寫的?大概如下:
RegisterController.cs
1 public class RegisterController : Controller 2 { 3 public RegisterController() { } 4 5 public bool Register() 6 { 7 var name = "name"; 8 var password = "password"; 9 var app = new UserApplication(); 10 11 if (!app.CheckName(name)) return false; 12 if (!app.CheckPassword(password)) return false; 13 if (!app.CheckIP()) return false; 14 if (!app.CheckAuthenticationCode()) return false; 15 if (!app.CheckRoleId()) return false; 16 if (!app.CheckOrganizationId()) return false; 17 18 app.Add(name, password); 19 app.Copy(); 20 app.SendMail(); 21 22 return true; 23 } 24 }
UserApplication.cs
1 public class UserApplication 2 { 3 public bool CheckName(string name) 4 { 5 return true; 6 } 7 public bool CheckPassword(string password) 8 { 9 return true; 10 } 11 public bool CheckIP() 12 { 13 return true; 14 } 15 public bool CheckAuthenticationCode() 16 { 17 return true; 18 } 19 public bool CheckRoleId() 20 { 21 return true; 22 } 23 public bool CheckOrganizationId() 24 { 25 return true; 26 } 27 public bool Copy() 28 { 29 return true; 30 } 31 public bool SendMail() 32 { 33 return true; 34 } 35 public bool Add(string name, string password) 36 { 37 return true; 38 } 39 }
如上邊代碼所示,Controller層太重(很多判斷我沒寫出來,實際情況肯定沒有那么簡潔的),而且如果是客戶端遠程部署(不一定是WEB,有可能是C/S、手機等),
那性能肯定很差,因為UI層需要穿透壁壘損耗性能,而且攜帶大量數據遠程傳輸,多次遠程訪問等等;而Application也很雜亂,
有些方法比如SendMail這個應該是屬於功能性的需求,不是業務性的需求,CheckIp也是,這些不應該寫在一起,因為從語義上講,他們都應該不屬於同一個地方
所以要想性能高,Business層要分2層,一層處理業務邏輯(Application),一層處理非業務邏輯(組織相關業務邏輯,即Service),
那就要改了,Controller層和應用層就不能厚,一定要薄,。。
3、加入分層后的代碼分布
Controller.cs,控制器變了,直接調用WCF,原來是在構造函數中注入Application的
1 public ActionResult Add(LoginUserCURequest entity) 2 { 3 var userClient = new WCFUserService.UserServiceClient(); 4 var addRequest = new LoginUserCURequest() 5 { 6 Id = Guid.NewGuid(), 7 LoginName = entity.LoginName, 8 Password = entity.Password, 9 IsEnabled = entity.IsEnabled, 10 Ip = entity.Ip, 11 Phone = entity.Phone, 12 Mail = entity.Mail, 13 Roles = entity.Roles 14 }; 15 var result = userClient.Add(addRequest); 16 17 //// 因為是演示,就不出來返回結果了 18 //if (result == "success") 19 //{ } 20 21 return RedirectToAction("Index"); 22 }
DTO也變了很多,加了所以注冊需要用到的參數屬性
1 public class LoginUserCURequest 2 { 3 /// <summary>Id</summary> 4 public Guid Id { get; set; } 5 6 /// <summary>登錄賬戶名</summary> 7 public string LoginName { get; set; } 8 9 /// <summary>登錄密碼</summary> 10 public string Password { get; set; } 11 12 /// <summary>是否有效</summary> 13 public short? IsEnabled { get; set; } 14 15 /// <summary>Ip</summary> 16 public string Ip { get; set; } 17 18 /// <summary>驗證碼</summary> 19 public string AuthenticationCode { get; set; } 20 21 /// <summary>Phone</summary> 22 public string Phone { get; set; } 23 24 /// <summary>Mail</summary> 25 public string Mail { get; set; } 26 27 /// <summary>所屬角色</summary> 28 public IEnumerable<Guid> Roles { get; set; } 29 30 public override string ToString() 31 { 32 return string.Format("Id:{0},LoginName:{1},Password:{2},IsEnabled:{3}", 33 Id.ToString(), LoginName, Password, IsEnabled.ToString()); 34 } 35 }
我們看看WCF的實現
1 public class UserService : TestBase, IUserService 2 { 3 private ILoginUserApplication loginUserApplication; 4 private IRoleApplication roleApplication; 5 private LoginUserCURequest loginUserCURequest; 6 7 public UserService() 8 { 9 this.loginUserApplication = container.Resolve<ILoginUserApplication>(); 10 this.roleApplication = container.Resolve<IRoleApplication>(); 11 } 12 13 public string Add(LoginUserCURequest entity) 14 { 15 this.loginUserCURequest = entity; 16 17 if (!this.CheckPassword()) 18 { 19 Logger.Warn("密碼不符合規范!"); 20 return "warn, password error"; 21 } 22 23 if (!this.CheckIP()) 24 { 25 Logger.Warn("你所在IP段被禁止注冊!"); 26 return "warn, ip error"; 27 } 28 29 if (!this.CheckAuthenticationCode()) 30 { 31 Logger.Warn("你填寫的驗證碼不正確!"); 32 return "warn, code error"; 33 } 34 35 if (!this.CheckAuthenticationCode()) 36 { 37 Logger.Warn("你填寫的驗證碼不正確!"); 38 return "warn, code error"; 39 } 40 41 //=======================分割線======================== 42 43 if (this.loginUserApplication.CheckName(this.loginUserCURequest.LoginName)) 44 { 45 Logger.Warn("登錄名稱已被占用!"); 46 return "warn, user exists"; 47 } 48 49 if (this.loginUserApplication.CheckPhoneRepeat(this.loginUserCURequest.Phone)) 50 { 51 Logger.Warn("手機已經被注冊!"); 52 return "warn, user exists"; 53 } 54 55 if (this.loginUserApplication.CheckMailRepeat(this.loginUserCURequest.Mail)) 56 { 57 Logger.Warn("郵箱已經被注冊!"); 58 return "warn, user exists"; 59 } 60 61 bool roleNotExists = false; 62 foreach (var roleId in this.loginUserCURequest.Roles) 63 { 64 if (this.roleApplication.Get(roleId) == null) 65 { 66 roleNotExists = true; 67 break; 68 } 69 } 70 71 if (roleNotExists) 72 { 73 Logger.Warn("所選角色已經無效!"); 74 return "warn, role error"; 75 } 76 77 if (this.loginUserApplication.Add(this.loginUserCURequest)) 78 { 79 //this.otherLoginUserApplication.Add(this.loginUserCURequest); 80 this.SendMail(); 81 82 return "success"; 83 } 84 else 85 { 86 return "fail"; 87 } 88 } 89 90 public bool CheckPassword() 91 { 92 return true; 93 } 94 95 public bool CheckIP() 96 { 97 return true; 98 } 99 100 public bool CheckAuthenticationCode() 101 { 102 return true; 103 } 104 105 public bool SendMail() 106 { 107 return true; 108 } 109 110 public UserList GetAll() 111 { 112 return null; 113 } 114 }
可以看到,分割線上是屬於數據合法性校驗,之下屬於業務邏輯層面的校驗
至於LoginUserApplication,之前我們已經實現了,是屬於單元(原子)業務層面的功能,只有CRUD
4、看看界面運行效果
好了,自此,服務層功能算是介紹完畢了!