DDD 领域驱动设计-领域模型中的用户设计
2016-04-27 18:33
267 查看
上一篇:《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 实体中,直接增加如下值对象:
这样实现也没什么问题,但 JsPermissionApply 实体的构造函数参数赋值,就变的很麻烦,UserId 标识一个 User,那一个 User 也是标识一个 User,所以我们可以直接把 User 设计为值对象,示例代码:
JsonProperty 的作用是在 UserService 获取用户信息的时候,映射源属性名称,GetUserByLoginName 示例代码:
JsPermissionApply 实体代码:
JsPermissionApply 实体去除了 UserId 属性,并增加了 User 值对象,构造函数也相应进行了更新,如果实体进行这样设计,那数据库存储该如何设计呢?EF 不需要添加任何的映射代码,直接用 EF Migration 应用更新就可以了,生成 JsPermissionApplys 表结构:
JsPermissionApplyDTO 示例代码:
使用
另外领域服务、应用服务和单元测试代码,也对应进行了更新,详细查看上面的开源地址。
UserId 换为 User 设计,大致有两个好处:
用户信息在申请的时候获取并存储,审核直接展示,减少不必要的请求开销。
有利于 User 的扩展,JsPermissionApply 领域模型会更加健壮。
技术是设计的实现,不能用技术来影响设计。
开源地址: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 领域模型会更加健壮。
技术是设计的实现,不能用技术来影响设计。
相关文章推荐
- 个人冲刺02
- 向openwrt 源码添加ap143支持
- 页游逆袭,WebGL让网页游戏终于用上本地显卡了!
- iOS应用架构谈-组件化方案
- sdut 2605
- 169. Majority Element
- Pixel Count LOD Strategy
- Vlan的配置
- leetcode-17-Letter Combinations of a Phone Number
- Notepadpp批量转换文件格式
- android:duplicateParentState="true"
- untiy 3d ShaderLab_第 2 章Unity中Shader(着色器)的形态_1_Unity通过ShaderLab 来组织Shader
- js prototype
- 磁盘格式化
- Pixel Count LOD Strategy
- asp.net 数据验证控件的使用实例
- D3D11无双(2):渲染一个3D彩色立方体
- guava处理字符串与List之间,字符串与map之间的转换
- ubuntu 14.04 lxde + tightvncserver
- java--集合框架的Hashset和Treeset