网站建设预算表,邢台做wap网站找谁,杭州 网站建设,国内网店平台有哪些注#xff1a;科比今天要退役了#xff0c;我是 60 亿分之一#xff0c;满腹怀念#xff5e;??? 前几天看了园友的一篇文章《我眼中的领域驱动设计》#xff0c;文中有段话直击痛点#xff1a;有人误认为项目架构中加入 Repository#xff0c;Domain#xff0c;Valu… 注科比今天要退役了我是 60 亿分之一满腹怀念??? 前几天看了园友的一篇文章《我眼中的领域驱动设计》文中有段话直击痛点有人误认为项目架构中加入 RepositoryDomainValueObject 就变成了 DDD 架构。没错我就是这样不过准确的来说并不能称为 DDD 架构而是我之前经常说的“伪 DDD”设计后来我还抽离出了一个伪 DDD 设计框架DDD.Sample大家有兴趣的可以瞧瞧在实际项目的开发中我用它做过了几个不太大的项目我个人觉得用起来很顺手当然并没有真正的 DDD 设计只不过用了一个空的架构而已然后冠以”DDD 开发“的名号。 因为之前有过 DDD 设计的痛苦经历短消息系统具体表现就是如果真正 DDD 设计需要花费大量的时间和精力可能几天都在思考一个问题或者考虑几行代码的编写但最后也可能没什么结论或结果并且这个过程是很艰难和痛苦的所以我后来就变懒了懒的去思考项目所表现的业务也不再考虑如何去设计领域模型只是在考虑如何让框架用起来更爽DDD.Sample 前两个应用的实际项目我都是在完善这个框架比如 Repository 和 UnitOfWork 的设计等等所以关于领域模型的设计就是一堆贫血模型。不过后来应用的第三个项目也就是上一个实际项目我觉得不能再这样下去了因为没啥意义框架一遍一遍的套用而 DDD 却和它没半毛钱关系所以我就花了点时间去思考只是简单的思考我做这个项目的核心业务是什么我该如何提炼出核心业务提炼出核心业务之后其他的任何实现都是为核心业务服务的所以你可以把这个核心业务看成领域模型。 关于第三个应用项目实际上就是我们园子的“提到我”系统现在已经应用在新闻评论了大家可以去瞧瞧类似为微博的“提到我”相对比较简单的一个系统你可以在评论中 一个人然后另一个人会接受通知那这个系统的核心业务是什么其实就是上面那句话只不过你需要抽离出一些内容如果领域专家和开发人员进行交流这个系统的设计那领域专家的表述就是你可以在评论中 一个人然后另一个人会接受通知领域专家可能不懂代码设计他的这个表述就是最直接和最精准的业务所以我们需要针对这段话再和领域专家深入探讨下所蕴含的业务 你可以在评论中 一个人 - 一个人 - 怎么能得到并确认这个“一个人” - 匹配规则另一个人会接受通知 - 通知 - 通知所 的人所以匹配规则和通知所 的人是“提到我”系统的核心业务确定好核心业务了那就该具体的实现了关于这个我没有深入的去考虑就直接放在了 Mention提到中大致代码 namespace CNBlogs.Mention.Domain
{public class Mention : IAggregateRoot{private static readonly string SplitRegex : ,;private static readonly Regex MentionsRegex new Regex($([^{SplitRegex}]), RegexOptions.Compiled);public int Id { get; set; }public string Content { get; set; }public int ContentId { get; set; }public AppType AppType { get; set; }...//抽离public async TaskListint Extract(){...}//通知public async Task Notify(){...}}
} 看起来很简单就是把两个方法放在了 Mention 中但这简单的操作却好像给 Mention 领域模型生命一样不再那么贫血对于复杂系统的业务变化往往是核心业务的变化其他的都是为核心业务服务的业务流程并不能真正称为业务比如 Application 层的代码现在领域专家说 一个人的规则需要改变或者通知规则需要变化我们只需要修改 Mention 领域模型的代码就行了其他的代码并不需要修改这就是 DDD 设计最浅显的体现。 大致贴下 Application 层的伪代码 namespace CNBlogs.Mention.Application.Services
{public class MentionService : IMentionService{private IMentionRepository _mentionRepository;private IUnitOfWork _unitOfWork;public MentionService(IUnitOfWork unitOfWork,IMentionRepository mentionRepository){_unitOfWork unitOfWork;_mentionRepository mentionRepository;}public async TaskSubmitResult Submit(string content ,int contentId, AppType appType){var notifyMentions new ListDomain.Mention();var existingQuery _mentionRepository.Get(contentId, appType);var mention new Domain.Mention(){Content content,ContentId contentId,AppType appType};var userIds await mention.Extract();foreach (var userId in userIds){var userQuery existingQuery.Where(x x.UserId userId);if (await userQuery.AnyAsync()){await userQuery.UpdateAsync(x new Domain.Mention { Content content, DateUpdated DateTime.Now });}else{mention.UserId userId;_unitOfWork.RegisterNew(mention);notifyMentions.Add(mention);}}if (await _unitOfWork.CommitAsync()){foreach (var notifyMention in notifyMentions){await notifyMention.Notify();}return new SubmitResult();}return new SubmitResult { IsSucceed false };}}
} 可以看到Submit 中的操作基本上都是工作流程先抽取用户再进行判断更新然后进行持久化最后进行通知没有任何业务体现所以如果核心业务发生了变化这部分的代码并不需要随之改变。 如何 DDD 引自《Implementing DDD Reading - Strategic Design》 如何 DDD其实答案都在上面的图中图中的设计在《实现领域驱动设计》书中被定义为战略建模Strategic Modeling主要包含领域、核心域、子域、通用子域、支撑子域、限界上下文、协作上下文、上下文映射图等等概念我之前的几篇《IDDD 实现领域驱动设计》系统文章也有过相关的介绍说实话我只是当时读过写过有些记忆现在让我再说任何一个概念基本上我说不上来对于我个人来说战略建模是一种宏观的建模方式你需要站在高处去俯瞰整个系统并需要抽离出系统所包含的业务并将它们一一划分这个工作是非常难的推荐几篇战略建模相关的文章 初窥 Bounded Context如何识别 Bounded Context领域驱动设计实战--战略建模除了战略建模还有一种建模方式叫战术建模Tactical Modeling主要包含聚合Aggregate、实体Entity、值对象Value Objects、资源库Repository、领域服务Domain Services、领域事件Domain Events、模块Modules等等概念。 在《实现领域驱动设计》书中Scrum 团队一个实际项目的开发团队一开始就是采用的战术建模并且在开发的过程中他们并不知道战略建模是什么最后导致了很多问题书中有个节点就专门讲了“战略设计为什么重要”但我个人觉得战略建模的重要也只是相对而言它在应对大型复杂性的业务系统设计中可以充分发挥它的特点但针对一些相对简单的系统还不如直接进行战术建模比如上面说的“提到我”系统。 所以目前来说进行战术建模比较现实和有意义但在进行战术建模之前我觉得还有一个重要的工作就是和领域专家进行交流系统业务这个工作并不包含具体的战术建模该如何设计比如聚合、实体啥的和领域专家并不需要讨论这部分内容而是系统所包含的业务就像“提到我”系统中我问我自己“我做这个项目的核心业务是什么”。 领域专家并不是一个职位他可以是精通业务的任何人。他们可能了解更多的关于业务领域的背景知识他们可能是软件产品的设计者甚至有可能是销售员。 实际项目的实践 好概念说太多没什么意义实际应用才有价值我现在在开发一个 JS 权限申请和审核系统就是我们园子里的 JS 权限申请因为现在申请 JS 权限需要发邮件进行申请对于用户申请和我们处理来说都比较麻烦并且费时间所以为了解决这个问题我们想做把 JS 权限申请做成像申请博客一样园友填写申请内容然后我们进行后台审核效率可以提升很多大概就是这样的一个系统真实的不能再真实了毕竟园友和我们都会实际接触并使用这也是一个相对较小的系统我们就拿它来开刀看看 DDD 的这把刀锋不锋利。 对于 JS 权限申请和审核系统来说领域专家是谁应该是园友和管理员毕竟他们在使用这个系统没有人比他们更了解其中的业务了所以他们就是这个系统的领域专家需要强调的是虽然有时候领域专家是开发人员但在一开始探讨业务系统的时候一定不能牵扯到数据库和代码的设计我们应该忘掉数据库和代码只是单纯的站在领域专家的角度去探讨和思考业务系统那领域专家该如何表述这个系统的业务呢 下面我大致的表述下 用户填写 JS 权限申请内容管理员后台进行审核。 有没有搞错就这么简单好像又无言以对因为关于 JS 权限申请和审核系统最简单的表述就是这样但如何提炼出所蕴含的业务呢接下来需要我们深入的探讨下作为领域专家身份的我绘制了一张大致的业务流程图 上面这张图可以进行反复的修改每个领域专家都可以发表自己的意见和建议经过最激烈的探讨才会让业务系统更加准确当业务系统确定好之后我们就可以从中抽离出核心业务了上面这张图哪些是核心业务哪些又是业务流程呢我大致圈一下 方框圈的是核心业务圆形圈的是实体的状态变化核心业务一般包含在最简单的描述中比如“提到我”系统中的表述“抽离”和“通知”还有一种区分方式判断其是否经常发生变化对于业务流程来说一般是不会发生变化的变化的是核心业务DDD 的设计应对的就是这个变化再大致总结下 核心业务验证用户信息可以称为申请状态改为“待审核”的前提条件主要是判断用户是否符合要求前面的“验证申请状态”也属于这一类。实体状态变化JS 权限申请就是一个实体它有自己的生命周期并且对于用户来说它是唯一存在的从上面的图中我们可以看到 JS 权限申请的状态变化这是领域所关心的能改变实体的状态就是业务。那领域模型关心的是哪些业务其实就是能影响 JS 权限申请状态变化的条件暂时不看用户申请的部分先看下管理员审核的部分因为是人工审核的所以这就有人为因素的产生这部分我们在领域模型设计的时候就没有办法把控所以可以把这部分排除在领域模型之外后面 JS 权限申请状态的改变也是由人为进行导致的也就是说对于领域模型来说我们没有办法进行控制 JS 权限申请状态的改变所以后面的状态改变我们可以看作是业务流程或者工作流程有人可能会问“开通 JS 权限”和“消息通知用户”算不算是业务其实这部分可以算是业务因为它是状态改变后的一种行为我们可以使用领域事件实现它。 跟踪变化最实用的方法是领域事件和事件存储。我们为领域专家所关心的所以状态改变都创建单独的事件类型事件的名字和属性表明发生了什么样的事件。当命令操作执行完后系统发出这些领域事件。事件的订阅方可以接收发生在模型上的所有事件。在接收到事件后订阅方将事件保存在事件存储中。 有点越说越乱的感觉先暂时概括下我们设计领域模型所包含的东西 聚合根和实体JS 权限申请命名为 JsPermissionApply具有唯一性。值对象申请状态命名为 Status包含待审核、审核通过、审核不通过等。领域事件处理 JsPermissionApply 状态改变后的一些工作开通 JS 权限和发送消息通知。领域服务UserAuthenticationService验证用户是否合法以及验证此用户是否能创建 JsPermissionApply。实体验证基本验证由 JsPermissionApply 自身负责在 JsPermissionApply 的构造函数中处理。实体行为管理员的审核处理 Process领域事件在这里触发。UserAuthenticationService 所做的工作就是上面图中第一个圈和第一个方框的内容总的概述就是验证用户是否能创建 JsPermissionApply我之前考虑用工厂实现但感觉还是不太妥因为工厂是为创建复杂实体服务的内部会有一些复杂的操作对于一些简单的实体创建我们直接用实体的构造函数进行创建就行比如 JsPermissionApply 的创建既然用工厂实现不合适那直接将操作放在 JsPermissionApply 中会怎样呢验证自己能否被创建想想还是有些别扭所以还是用 UserAuthenticationService 领域服务实现吧况且领域服务的定义就是如此。 领域服务表示一个无状态的操作它用于实现特定于某个领域的任务当某个操作不适合放在聚合和值对象上时最好的方式便是使用领域服务。 另外关于实体、值对象、领域事件、领域服务和仓储接口的实现最好在不同的项目中如果再同一个项目中的话可能会造成循环引用的情况比如仓储接口引用了实体领域服务引用了仓储接口如果实体和领域服务实现在同一个项目就会出现循环引用的问题。 再来总结下我们分析系统和设计领域模型的步骤先和领域专家探讨业务系统经过反反复复的研究抽离出业务系统的核心业务然后用战术建模的方式设计领域模型最后用代码进行实现。领域模型设计好了之后下面就开始用代码实现了在代码实现的时候最好解决方案中只有领域层、基础设施层和单元测试并且一开始设计的时候先编写领域层的代码然后再编写单元测试最后进行不断的测试和完善关于数据的持久化现在最好不要关注尽量用 Mock 的方式模拟数据。 我先贴一下 JsPermissionApply 实体的部分代码 using CNBlogs.Apply.Domain.DomainEvents;
using CNBlogs.Apply.Domain.ValueObjects;
using System;
using Microsoft.Practices.Unity;
using CNBlogs.Apply.Infrastructure.IoC.Contracts;namespace CNBlogs.Apply.Domain
{public class JsPermissionApply : IAggregateRoot{private IEventBus eventBus;public JsPermissionApply(){ }public JsPermissionApply(string reason, int userId, string ip){if (string.IsNullOrEmpty(reason)){throw new ArgumentException(申请内容不能为空);}if (reason.Length 3000){throw new ArgumentException(申请内容超出最大长度);}if (userId 0){throw new ArgumentException(用户Id为0);}this.Reason reason;this.UserId userId;this.Ip ip;this.Status Status.Wait;}public int Id { get; private set; }public string Reason { get; private set; }public int UserId { 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 void Process(string replyContent, Status status){this.ReplyContent replyContent;this.Status status;this.ApprovedTime DateTime.Now;eventBus IocContainer.Default.ResolveIEventBus();if (this.Status Status.Pass){eventBus.Publish(new JsPermissionOpenedEvent() { UserId this.UserId });eventBus.Publish(new MessageSentEvent() { Title 系统通知, Content 审核通过, RecipientId this.UserId });}else if (this.Status Status.Deny){eventBus.Publish(new MessageSentEvent() { Title 系统通知, Content 审核不通过, RecipientId this.UserId });}}}
} JsPermissionApplyTest 单元测试的代码 using CNBlogs.Apply.Domain.DomainServices;
using CNBlogs.Apply.Domain.ValueObjects;
using CNBlogs.Apply.Infrastructure.Interfaces;
using CNBlogs.Apply.Infrastructure.IoC.Contracts;
using CNBlogs.Apply.Repository.Interfaces;
using System;
using System.Data.Entity;
using System.Threading.Tasks;
using Xunit;
using Microsoft.Practices.Unity;namespace CNBlogs.Apply.Domain.Tests
{public class JsPermissionApplyTest{private IUserAuthenticationService _userAuthenticationService;private IJsPermissionApplyRepository _jsPermissionApplyRepository;private IUnitOfWork _unitOfWork;public JsPermissionApplyTest(){CNBlogs.Apply.BootStrapper.Startup.Configure();_userAuthenticationService IocContainer.Default.ResolveIUserAuthenticationService();_jsPermissionApplyRepository IocContainer.Default.ResolveIJsPermissionApplyRepository();_unitOfWork IocContainer.Default.ResolveIUnitOfWork();}[Fact]public async Task Apply(){var userId 1;var verfiyResult await _userAuthenticationService.Verfiy(userId);Console.WriteLine(verfiyResult);Assert.Empty(verfiyResult);var jsPermissionApply new JsPermissionApply(我要申请JS权限, userId, );_unitOfWork.RegisterNew(jsPermissionApply);Assert.True(await _unitOfWork.CommitAsync());}[Fact]public async Task ProcessApply(){var userId 1;var jsPermissionApply await _jsPermissionApplyRepository.GetByUserId(userId).FirstOrDefaultAsync();Assert.NotNull(jsPermissionApply);jsPermissionApply.Process(审核通过, Status.Pass);_unitOfWork.RegisterDirty(jsPermissionApply);Assert.True(await _unitOfWork.CommitAsync());}}
} 代码我就不分析了基本上是按照我们上面的设计方案实现的本来想在仓储层模拟数据的但时间有限还是使用的 EF 进行数据的持久化和访问完整的解决方案目录 开源地址https://github.com/yuezhongxin/CNBlogs.Apply.Sample 上面提交的代码只是一开始的实现有些地方可能没有考虑全面代码也可能会有些粗糙但因为不是简简单单的示例 Demo而是实际项目所以后面我会不断的进行完善大家如果有什么意见或建议欢迎探讨 实现领域驱动设计的方式有很多种你可以战略设计、你也可以战术设计、你可以直接面向对象编写代码、你也可以和领域专家只画一张白板图、又或者写一篇分析业务系统的文章但就像埃文斯Eric Evans的书名一样《领域驱动设计软件核心复杂性应对之道》领域驱动设计的核心目的就是应对软件业务系统的复杂性所以不管哪种实现方式只要能达到这个目的那就是好的实现领域驱动设计的方式。 JS 权限申请和审核系统还没完下面又要加一个 Blog 地址更改申请和审核系统它们会碰撞什么样的火花呢拭目以待吧。? 转载于:https://www.cnblogs.com/xishuai/p/how-to-implement-ddd.html