zoukankan      html  css  js  c++  java
  • 领域模型中的用户设计

    领域模型中的用户设计

    上一篇:《DDD 领域驱动设计-如何控制业务流程?

    开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新,并增加了应用层代码)

    在 JsPermissionApply 领域模型中,User 被设计为值对象,也就是 JsPermissionApply 实体中的 UserId 属性,这个没啥问题,但后来再实现代码的时候,就出现了一些问题,在 JS 权限申请和审核系统中,用户的一些操作如下:

    1. 申请:根据当前 LoginName 获取 UserId,UserId 存储在 JsPermissionApply 实体。
    2. 验证:根据 UserId 判断此用户是否拥有博客。
    3. 权限:根据当前 LoginName,判断此用户是否拥有审核权限。
    4. 审核:循环遍历每个申请,根据其 UserId 获取其他的用户信息。

    对于上面的四个用户操作,因为每个请求都会耗费时间,所以我们需要尽量简化其操作,尤其是第四个操作,如果管理员要审核 10 个申请,那么就得请求用户服务 10 次,那怎么省掉这个操作呢?就是用户在申请 JS 权限的时候,我们先获取用户信息,然后存在 JsPermissionApply 实体中,如何这样设计,那么第二个用户验证操作,也可以省掉。

    代码如何实现?我之前想在 JsPermissionApply 实体中,直接增加如下值对象:

    public int UserId { get; set; }
    
    public string UserLoginName { get; set; }
    
    public string UserDisplayName { get; set; }
    
    public string UserEmail { get; set; }
    
    public string UserAlias { get; set; }

    这样实现也没什么问题,但 JsPermissionApply 实体的构造函数参数赋值,就变的很麻烦,UserId 标识一个 User,那一个 User 也是标识一个 User,所以我们可以直接把 User 设计为值对象,示例代码:

    namespace CNBlogs.Apply.Domain.ValueObjects
    {
        public class User
        {
            public string LoginName { get; set; }
    
            public string DisplayName { get; set; }
    
            public string Email { get; set; }
    
            public string Alias { get; set; }
    
            [JsonProperty("SpaceUserID")]
            public int Id { get; set; }
        }
    }

    JsonProperty 的作用是在 UserService 获取用户信息的时候,映射源属性名称,GetUserByLoginName 示例代码:

    namespace CNBlogs.Apply.ServiceAgent
    {
        public class UserService
        {
            private static string userHost = "";
    
            public static async Task<User> GetUserByLoginName(string loginName)
            {
                using (var httpCilent = new HttpClient())
                {
                    httpCilent.BaseAddress = new System.Uri(userHost);
                    var response = await httpCilent.GetAsync($"/users?loginName={Uri.EscapeDataString(loginName)}");
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        return await response.Content.ReadAsAsync<CNBlogs.Apply.Domain.ValueObjects.User>();
                    }
                    return null;
                }
            }
        }
    }

    JsPermissionApply 实体代码:

    namespace CNBlogs.Apply.Domain
    {
        public class JsPermissionApply : IAggregateRoot
        {
            private IEventBus eventBus;
    
            public JsPermissionApply()
            { }
    
            public JsPermissionApply(string reason, User user, string ip)
            {
                if (string.IsNullOrEmpty(reason))
                {
                    throw new ArgumentException("申请内容不能为空");
                }
                if (reason.Length > 3000)
                {
                    throw new ArgumentException("申请内容超出最大长度");
                }
                if (user == null)
                {
                    throw new ArgumentException("用户为null");
                }
                if (user.Id == 0)
                {
                    throw new ArgumentException("用户Id为0");
                }
                this.Reason = HttpUtility.HtmlEncode(reason);
                this.User = user;
                this.Ip = ip;
                this.Status = Status.Wait;
            }
    
            public int Id { get; private set; }
    
            public string Reason { get; private set; }
    
            public virtual User User { get; private set; }
    
            public Status Status { get; private set; } = Status.Wait;
    
            public string Ip { get; private set; }
    
            public DateTime ApplyTime { get; private set; } = DateTime.Now;
    
            public string ReplyContent { get; private set; }
    
            public DateTime? ApprovedTime { get; private set; }
    
            public bool IsActive { get; private set; } = true;
    
            public async Task<bool> Pass()
            {
                if (this.Status != Status.Wait)
                {
                    return false;
                }
                this.Status = Status.Pass;
                this.ApprovedTime = DateTime.Now;
                this.ReplyContent = "恭喜您!您的JS权限申请已通过审批。";
                eventBus = IocContainer.Default.Resolve<IEventBus>();
                await eventBus.Publish(new JsPermissionOpenedEvent() { UserId = this.User.Id });
                return true;
            }
    
            public bool Deny(string replyContent)
            {
                if (this.Status != Status.Wait)
                {
                    return false;
                }
                this.Status = Status.Deny;
                this.ApprovedTime = DateTime.Now;
                this.ReplyContent = replyContent;
                return true;
            }
    
            public bool Lock()
            {
                if (this.Status != Status.Wait)
                {
                    return false;
                }
                this.Status = Status.Lock;
                this.ApprovedTime = DateTime.Now;
                this.ReplyContent = "抱歉!您的JS权限申请没有被批准,并且申请已被锁定,具体请联系contact@cnblogs.com。";
                return true;
            }
    
            public async Task Passed()
            {
                if (this.Status != Status.Pass)
                {
                    return;
                }
                eventBus = IocContainer.Default.Resolve<IEventBus>();
                await eventBus.Publish(new MessageSentEvent() { Title = "您的JS权限申请已批准", Content = this.ReplyContent, RecipientId = this.User.Id });
            }
    
            public async Task Denied()
            {
                if (this.Status != Status.Deny)
                {
                    return;
                }
                eventBus = IocContainer.Default.Resolve<IEventBus>();
                await eventBus.Publish(new MessageSentEvent() { Title = "您的JS权限申请未通过审批", Content = this.ReplyContent, RecipientId = this.User.Id });
            }
    
            public async Task Locked()
            {
                if (this.Status != Status.Lock)
                {
                    return;
                }
                eventBus = IocContainer.Default.Resolve<IEventBus>();
                await eventBus.Publish(new MessageSentEvent() { Title = "您的JS权限申请未通过审批", Content = this.ReplyContent, RecipientId = this.User.Id });
            }
        }
    }

    JsPermissionApply 实体去除了 UserId 属性,并增加了 User 值对象,构造函数也相应进行了更新,如果实体进行这样设计,那数据库存储该如何设计呢?EF 不需要添加任何的映射代码,直接用 EF Migration 应用更新就可以了,生成 JsPermissionApplys 表结构:

    SELECT TOP 1000 [Id]
          ,[Reason]
          ,[Status]
          ,[Ip]
          ,[ApplyTime]
          ,[ReplyContent]
          ,[ApprovedTime]
          ,[IsActive]
          ,[User_LoginName]
          ,[User_DisplayName]
          ,[User_Email]
          ,[User_Alias]
          ,[User_Id]
      FROM [cnblogs_apply].[dbo].[JsPermissionApplys]

    JsPermissionApplyDTO 示例代码:

    namespace CNBlogs.Apply.Application.DTOs
    {
        public class JsPermissionApplyDTO
        {
            public int Id { get; set; }
    
            public string Reason { get; set; }
    
            public string Ip { get; set; }
    
            public DateTime ApplyTime { get; set; }
    
            public int UserId { get; set; }
    
            public string UserLoginName { get; set; }
    
            public string UserDisplayName { get; set; }
    
            public string UserEmail { get; set; }
    
            public string UserAlias { get; set; }
        }
    }

    使用.ProjectTo<JsPermissionApplyDTO>().ToListAsync()获取申请列表的时候,AutoMapper 也不需要添加任何对 JsPermissionApply 和 JsPermissionApplyDTO 的映射代码。

    另外领域服务、应用服务和单元测试代码,也对应进行了更新,详细查看上面的开源地址。

    UserId 换为 User 设计,大致有两个好处:

    • 用户信息在申请的时候获取并存储,审核直接展示,减少不必要的请求开销。
    • 有利于 User 的扩展,JsPermissionApply 领域模型会更加健壮。

    技术是设计的实现,不能用技术来影响设计。

  • 相关阅读:
    js float浮点数计算精度问题
    NLB多WEB站点访问共享文件解决方案
    Flash文件跨域访问
    博客地址
    在Ubuntu上安装Mysql
    Ajax调用NPOI导出Excel报表
    在Ubuntu上安装Mongodb
    获取月份的周时间段
    sql表结构和注释
    Highcharts使用指南
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/5454347.html
Copyright © 2011-2022 走看看