DDD 领域驱动设计-领域模型中的用户设计

简介:

上一篇:《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 领域模型会更加健壮。

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





本文转自田园里的蟋蟀博客园博客,原文链接:http://www.cnblogs.com/xishuai/p/domain-model-with-user-design.html,如需转载请自行联系原作者

相关文章
|
3月前
|
领域建模
架构设计 DDD领域建模 核心概念
【1月更文挑战第6天】架构设计 DDD领域建模 核心概念
|
10月前
|
领域建模 数据库 数据安全/隐私保护
DailyMart03:如何基于DDD设计商城的领域模型?
DailyMart03:如何基于DDD设计商城的领域模型?
567 0
|
存储 自然语言处理 前端开发
领域驱动设计(DDD)-基础思想
一、序言     领域驱动设计是一种解决业务复杂性的设计思想,不是一种标准规则的解决方法。在领域驱动设计理念上,各路大侠的观点也是各有不同,能力有限、欢迎留言讨论。 二、领域驱动设计 DDD是什么 wiki释义:     领域驱动设计(英语:Domain-driven design,缩写 DDD)是一种通过将实现连接到持续进化的模型[1]来满足复杂
7307 0
|
前端开发 Java 数据库连接
领域驱动设计(DDD):分层架构
在应用系统开发中,采用严格的、单一的、真正的的分层架构是可以的,但实际上我们已经采用了多种架构模式设计系统。当多种不同范式的架构混合在一起,你会不会出现“指鹿为马”的现象呢?
领域驱动设计(DDD):分层架构
|
9月前
|
消息中间件 架构师 搜索推荐
DDD领域驱动设计的概念解析
DDD领域驱动设计的概念解析
160 0
|
11月前
|
消息中间件 JavaScript 小程序
领域驱动设计(DDD)的几种典型架构介绍
领域驱动设计(DDD)的几种典型架构介绍
|
设计模式 运维 前端开发
DDD(领域驱动设计)分层架构
DDD(领域驱动设计)分层架构
980 0
DDD(领域驱动设计)分层架构
|
JSON 前端开发 数据格式
再议DDD分层
之前整理过《DDD分层》[1] 以及《分层架构》[2] 最近看网友讨论,整理一些有亮点的地方 现在分层架构+整洁架构似乎是个万金油组合了 之前DDD的标准分层结构:
254 0
再议DDD分层
DDD领域驱动设计实战(三)-深入理解实体(下)
DDD领域驱动设计实战(三)-深入理解实体(下)
241 0
|
前端开发 Java 数据库连接
DDD领域驱动设计实战(03)-深入理解实体
DDD领域驱动设计实战(03)-深入理解实体
344 0
DDD领域驱动设计实战(03)-深入理解实体