zoukankan      html  css  js  c++  java
  • 探讨DDD中角色权限与DCI的使用

    本文初衷

    之前在学习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

  • 相关阅读:
    pycharm突然变成了一个tab变成两个空格,查询无果
    79--JT项目17(Dubbo框架入门)
    79--JT项目17(SOA/RPC思想/zookeeper集群搭建)
    Java instanceof Operator
    12.21.4命名为Windows
    12.20.1汇总功能说明
    第24章分区
    Laravel 中间件的使用
    Laravel session的使用
    Laravel 数据分页
  • 原文地址:https://www.cnblogs.com/zhengqzheng/p/5785286.html
Copyright © 2011-2022 走看看