之前我们已经完成了服务层,因为当时展现层还没有出来,所以只做了简单介绍。传送门:项目架构开发:服务层(上)
这次我们通过一个维护系统用户的场景来介绍一下服务层真正的设计用意。
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、看看界面运行效果
好了,自此,服务层功能算是介绍完毕了!