DDD 领域驱动设计-领域模型中的用户设计
上一篇:《DDD 领域驱动设计-如何控制业务流程?》
开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新,并增加了应用层代码)
在 JsPermissionApply 领域模型中,User 被设计为值对象,也就是 JsPermissionApply 实体中的 UserId 属性,这个没啥问题,但后来再实现代码的时候,就出现了一些问题,在 JS 权限申请和审核系统中,用户的一些操作如下:
- 申请:根据当前 LoginName 获取 UserId,UserId 存储在 JsPermissionApply 实体。
- 验证:根据 UserId 判断此用户是否拥有博客。
- 权限:根据当前 LoginName,判断此用户是否拥有审核权限。
- 审核:循环遍历每个申请,根据其 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 领域模型会更加健壮。
技术是设计的实现,不能用技术来影响设计。
DDD 领域驱动设计-领域模型中的用户设计的更多相关文章
- DDD 领域驱动设计-两个实体的碰撞火花
上一篇:<DDD 领域驱动设计-领域模型中的用户设计?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新) 在 ...
- DDD 领域驱动设计-如何完善 Domain Model(领域模型)?
上一篇:<DDD 领域驱动设计-如何 DDD?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新) 阅读目录: ...
- DDD 领域驱动设计-看我如何应对业务需求变化,领域模型调整?
写在前面 上一篇:DDD 领域驱动设计-看我如何应对业务需求变化,愚蠢的应对? "愚蠢的应对",这个标题是我后来补充上的,博文中除了描述需求变化.愚蠢应对和一些思考,确实没有实质性 ...
- DDD 领域驱动设计-“臆想”中的实体和值对象
其他博文: DDD 领域驱动设计-三个问题思考实体和值对象 DDD 领域驱动设计-三个问题思考实体和值对象(续) 以下内容属于博主"臆想",如有不当,请别当真. 扯淡开始: 诺兰的 ...
- 浅谈我对DDD领域驱动设计的理解
从遇到问题开始 当人们要做一个软件系统时,一般总是因为遇到了什么问题,然后希望通过一个软件系统来解决. 比如,我是一家企业,然后我觉得我现在线下销售自己的产品还不够,我希望能够在线上也能销售自己的产品 ...
- DDD 领域驱动设计-商品建模之路
最近在做电商业务中,有关商品业务改版的一些东西,后端的架构设计采用现在很流行的微服务,有关微服务的简单概念: 微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成.系统中的各个微服务可被独 ...
- DDD 领域驱动设计-如何控制业务流程?
上一篇:<DDD 领域驱动设计-如何完善 Domain Model(领域模型)?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sa ...
- DDD 领域驱动设计-三个问题思考实体和值对象(续)
上一篇:DDD 领域驱动设计-三个问题思考实体和值对象 说实话,整理现在这一篇博文的想法,在上一篇发布出来的时候就有了,但到现在才动起笔来,而且写之前又反复读了上一篇博文的内容及评论,然后去收集资料, ...
- DDD 领域驱动设计-三个问题思考实体和值对象
消息场景:用户 A 发送一个消息给用户 B,用户 B 回复一个消息给用户 A... 现有设计:消息设计为实体并为聚合根,发件人.收件人设计为值对象. 三个问题: 实体最重要的特性是什么? Messag ...
随机推荐
- 【每日一linux命令3】参数(或称选项)顺序
一般除了特殊情况,参数是没有顺序的.举例而言,输入"–a –v"与输入"–v –a"以及"–av" 的执行效果是相同的.但若该参数后指定了要 ...
- .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
不知不觉,“.NET平台开源项目速览“系列文章已经15篇了,每一篇都非常受欢迎,可能技术水平不高,但足够入门了.虽然工作很忙,但还是会抽空把自己知道的,已经平时遇到的好的开源项目分享出来.今天就给大家 ...
- 算法与数据结构(九) 查找表的顺序查找、折半查找、插值查找以及Fibonacci查找
今天这篇博客就聊聊几种常见的查找算法,当然本篇博客只是涉及了部分查找算法,接下来的几篇博客中都将会介绍关于查找的相关内容.本篇博客主要介绍查找表的顺序查找.折半查找.插值查找以及Fibonacci查找 ...
- 关于Android避免按钮重复点击事件
最近测试人员测试我们的APP的时候,喜欢快速点击某个按钮,出现一个页面出现多次,测试人员能不能禁止这样.我自己点击了几下,确实存在这个问题,也感觉用户体验不太好.于是乎后来我搜了下加一个方法放在我们U ...
- 在 Windows7 上按照 MySQL5.7
在 Windows7 上按照 MySQL5.7 1.从官网下载最新版本的 MySQL,这里下载的是 mysql-5.7.17-win32: 2.将下载的 mysql-5.7.17-win32.zip ...
- OpenSUSE下编译安装OpenFoam
在不是Ubuntu系统下安装OpenFoam,需要采用编译安装的方式.以下以OpenSuSE为例进行编译安装. 1 软件包准备 需要下载两个程序包: OpenFOAM-4.x-version-4.1. ...
- 递归实现n(经典的8皇后问题)皇后的问题
问题描述:八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后, 使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行.纵行或斜线上 ...
- MonoDevelop 4.0.9 on CentOS 6.3 安装笔记
前言 Mono的前东家Novell公司旗下的SUSE Linux系列对Mono及MonoDevelop提供内置支持,所以在SUSE/OpenSUSE这些Linux系统中安装MonoDevelop是非常 ...
- 一个标签的72变,打造一个纯CSS图标库
每次要用到图标的时候都会到 icono 去copypaste,但每次用到的时候尺寸都各不一样,总是要调整参数,巨烦.当然你可以会想到用zoom.scale来做缩放,但是这样的缩放会使得线宽也变粗了,不 ...
- MemCache超详细解读
MemCache是什么 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高 ...