本文初衷
之前在学习DDD的时候,一直被权限与角色困扰。
我们知道在Asp.net MVC 的Controller或Action加上特性标签[Authorize],就可以实现权限控制。
[Authorize(Roles ="Manager")] public class MainController : Controller { // GET: Main public ActionResult Index() { return View(); } }
但是在应用层中,如何使用好角色?在领域层如何设计好角色?如何使角色系统变得更优雅,更易扩展?
上述问题将是本位探讨的内容。
问题引入
我们模拟开发一个简单的公司内部审批系统,有以下两种角色:
1.经理:审批申请单。
2.员工:提出请假申请单。
设计如下:
角色接口:
public interface IRole { }
a.经理接口:
public interface IManager : IRole { /// <summary> /// 批准申请单 /// </summary> /// <param name="applyOrder"></param> void ApprovalOrder(ApplyOrder applyOrder); }
b.员工接口:
/// <summary> /// 员工接口 /// </summary> public interface IEmployee : IRole { /// <summary> /// 提交申请 /// </summary> /// <param name="content"></param> /// <returns></returns> ApplyOrder ApplyNewOrder(string content); }
c.用户类:
public class User { public User() { Roles = new List<string>(); } public string Id { get; set; } /// <summary> /// 名字 /// </summary> public string Name { get; set; } /// <summary> /// 用户的角色字符串 /// </summary> public List<string> Roles { get; set; } }
有了以上的基础设计,我们可以继续往下开发系统的角色扮演这块的代码了。
我们先引入两个不优雅的设计,来讨论一下他们的缺陷。
不优雅的设计1:在User对象里面增加角色扮演方法
我们在User里面增加角色扮演方法:
public class User { //其他信息参考上面 public TRole ActAs<TRole>() where TRole : class,IRole { if(typeof(TRole) is IManager) {
//此处为了简化演示,直接在这里判定 if (Roles.Contains("Manager")) { var role = new Manager(this); return role as TRole; } throw new Exception("你不是经理"); } else if(typeof(TRole) is IEmployee) {
//此处为了简化演示,直接在这里判断 if (Roles.Contains("Employee")) { var role = new Employee(this); return role as TRole; } throw new Exception("你不是员工"); } throw new NotImplementedException("还未实现对应的角色转换"); } }
我们很容易看到上述代码的缺陷:对扩展不友好,一旦要增加其他角色,就要修改User类的代码。
不优雅的设计2:在User对象里面增加角色扮演方法
我们考虑让User类实现IManager接口和IEmployee接口
public class User : IEmployee,IManager { //其他信息参考上面
public TRole ActAs<TRole>() where TRole : class,IRole { if(typeof(TRole) is IManager) { if (Roles.Contains("Manager")) {
return this as TRole; } throw new Exception("你不是经理"); } else if(typeof(TRole) is IEmployee) { if (Roles.Contains("Employee")) { return this as TRole; } throw new Exception("你不是员工"); } throw new NotImplementedException("还未实现对应的角色转换"); } public ApplyOrder ApplyNewOrder(string content) { throw new NotImplementedException(); } public void ApprovalOrder(ApplyOrder applyOrder) { throw new NotImplementedException(); } }
我们也来分析一下上面代码不好的地方
1.如果系统有很多的角色,会造成User类要实现的角色非常多,不符合单一职责原则。
2.和设计1类似,对扩展不友好,一旦要增加其他角色,就要修改User类的代码。
好的设计
首先我们先修改IRole接口:
public interface IRole { User User { get; } }
引入角色扮演工厂接口:
public interface IRoleAct<T> where T :IRole { T ActAs(User user); }
定义经理的扮演工厂接口:
public interface IManagerRoleAct : IRoleAct<IManager> { }
定义经理的扮演工厂实现:
public class ManagerRoleAct : IManagerRoleAct { public IManager ActAs(User user) { if (!user.Roles.Contains("Manager")) { throw new Exception("当前用户不是Manager"); } IManager role = new Manager(user); return role; } }
Employee的实现同上。
进一步,我们引入服务定位模式:
此时为了保证聚合根的纯净,我们增加领域服务:
public class UserDomainService { /// <summary> /// 扮演角色 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="user"></param> /// <returns></returns> public T ActAs<T>(User user) where T : IRole { IRoleAct<T> act = (IRoleAct<T>)ServiceLocator.Current.GetService(typeof(IRoleAct<T>)); var obj = act.ActAs(user); return (T)obj; } }
引入IOC容器(容器使用Unity4),让容器帮我们自动扫描所有的角色和工厂,并注册到容器里面:
class Program { static void Main(string[] args) { UnityContainer container = new UnityContainer(); UnityServiceLocator locator = new UnityServiceLocator(container); ServiceLocator.SetLocatorProvider(() => locator); Assembly assembly = Assembly.GetAssembly(typeof(Program)); List<Type> types = assembly .GetTypes() .Where(s=> s.IsClass && s.GetInterfaces().Any(t=>t.Name.Contains("IRoleAct"))) .ToList(); foreach (var item in types) { foreach (var itemInterface in item.GetInterfaces()) { container.RegisterType(itemInterface, item); } } Console.ReadLine(); } }
看看应用层的调用:
private static void TestUserRole() { User managerUser = new User(); managerUser.Id = Guid.NewGuid().ToString("N"); managerUser.Name = "张三经理"; managerUser.Roles.Add("Manager"); User employeeUser = new User(); employeeUser.Id = Guid.NewGuid().ToString("N"); employeeUser.Name = "李四经理"; employeeUser.Roles.Add("Employee"); UserDomainService service = new UserDomainService(); IEmployee employee = service.UserActAs<IEmployee>(employeeUser); ApplyOrder order = employee.ApplyNewOrder("请病假"); IManager manager = service.UserActAs<IManager>(managerUser); manager.ApprovalOrder(order); }
使用DCI
我们引入DCI,将审批动作放入场景里面:
public class ApplyOrderApprovalContext { public ApplyOrderApprovalContext(User user,ApplyOrder order) { this.User = user; this.Order = order; } public User User { get; private set; } public ApplyOrder Order { get; private set; } public void Interaction() { UserDomainService service = new UserDomainService(); IManager manager = service.UserActAs<IManager>(User); manager.ApprovalOrder(Order); } }
欢迎大家讨论我上述设计的优缺点!
最后:特别感谢汤神的博客和dax.net的博客,还有园里的蟋蟀,腾飞等大大们的领域驱动设计相关的文章。
DCI分析参考汤神的文章:http://www.cnblogs.com/netfocus/archive/2011/09/18/2180656.html