大家好,去年我发布了一篇 OnionArch - 采用DDD+CQRS+.Net 7.0实现的洋葱架构很多程序员都比较感兴趣,给我要源代码。这次我把OnionArch进行了升级,改进了一些特性,并放出源代码,iamxiaozhuang/OnionArch2 (github.com)   欢迎大家研究使用。

一、自动生成和发布领域事件

我在OninArch1.0实现了对删除的实体自动生成和发布领域事件,并通过订阅这个领域事件,将删除的实体数据备份至回收站表中,以备审计和数据恢复。

本次我改进了这个特性,对实体数据的新增,修改和删除都会自动生成和发布领域事件。我认为尽量不要通过修改代码来新增和发布领域事件,这会导致新增的业务功能也需要修改代码而不是新增代码来实现,不符合对修改关闭和对扩展开放的设计原则。应该对实体数据的任何变动都自动发布领域事件,然后在事件Handler中筛选需要的领域事件并进行处理。

我基于这个特性实现了按配置自动审计记录和和按配置自动发布集成事件功能。

按配置自动审计功能

我们可以通过配置的方式来实现那些实体,那种修改类型,那个字段需要审计。请看如下配置:

  1. "EntityChangedAuditLogsConfig": [
  2. {
  3. "EntityFullName": "OnionArch.Domain.ProductInventory.ProductInventory",
  4. "ChangeType": "Added"
  5. },
  6. {
  7. "EntityFullName": "OnionArch.Domain.ProductInventory.ProductInventory",
  8. "ChangeType": "Modified",
  9. "Properties": "InventoryAmount"
  10. },
  11. {
  12. "EntityFullName": "OnionArch.Domain.ProductInventory.ProductInventory",
  13. "ChangeType": "Deleted"
  14. }
  15. ],

可以按照实体的全名,修改类型(新增,修改,删除),甚至是实体的修改字段来配置是否需要进行数据审计,如过需要审计则会自动保存审计日志到审计表,审计表包含实体的原值和当前值,修改人和修改时间等。

按配置自动发布集成事件功能

我们可以通过配置来实现该微服务的那些领域事件需要转为集成事件发布出去,供其它的微服务订阅使用。这样我们在微服务中新增集成事件订阅的时候就不需要修改源微服务的代码,只需要在源微服务中增加配置即可。

我们需要配置Dapr的发布订阅名称,事件Topic和这个Topic的发布条件,例如,在产品仓库实体的库存数量被修改后发布Topic为“ProductInventoryAmountChanged”集成事件。

  1. "EntityChangedIntegrationEventConfig": {
  2. "PubsubName": "pubsub",
  3. "Topics": [
  4. {
  5. "TopicName": "ProductInventoryAmountChanged",
  6. "EntityFullName": "OnionArch.Domain.ProductInventory.ProductInventory",
  7. "ChangeType": "Modified",
  8. "Properties": "InventoryAmount"
  9. }
  10. ]
  11. },

然后就可通过Dapr在其它的微服务中订阅并处理该集成事件,通过Dapr发布集成事件的代码请查看源代码。

已发布的集成事件也会自动保存至集成事件记录表中,以备对该事件进行后续执行跟踪和重发。

二、自动生成Minimal WebApi接口

该特性我在 根据MediatR的Contract Messages自动生成Minimal WebApi接口 中做过介绍。因为OninArch通过MediatR实现了CQRS和其它AOP功能,例如业务实体验证,异常处理、工作单元等特性。

本次将OninArch1.0的GRPC接口替换成了自动生成的WebAPI接口。并对自动生成WebAPI接口做了改进,可以指定生成的WebAPI的Http方法,地址、介绍和详细说明。自动对接口按命名空间分类,将Get方法参数自动映射到Query参数等。

  1. [MediatorWebAPIConfig(HttpMethod = HttpMethodToGenerate.Post, HttpUrl = "/productinventory", Summary = "创建产品库存", Description = "创建产品库存 Description")]
  2. public class CreateProductInventory : ICommand<Unit>
  3. {
  4. public string ProductCode { get; set; }
  5. public int InventoryAmount { get; set; }
  6. }
  7. [MediatorWebAPIConfig(HttpMethod = HttpMethodToGenerate.Patch, HttpUrl = "/productinventory/increase", Summary = "增加产品库存")]
  8. public class IncreaseProductInventory : ICommand<Unit>
  9. {
  10. public Guid Id { get; set; }
  11. public int Amount { get; set; }
  12. }

生成的WebAPI:

三、对充血模型的支持

我在OninArch1.0中并没有刻意强调充血模型,本次按照我对充血模型的理解改进了仓储代码,即,仓储服务只实现实体的Add,Remove和Query,不实现实体的Create和Modify。实体的创建和修改必须放入实体中实现。也就是说,实体字段的set都是私有的,只能在实体内部对实体的字段进行修改,以保证将业务逻辑封装到实体中,并提高系统的稳定性和业务逻辑重用性。

  1. public static ProductInventory Create<TModel>(TModel model)
  2. {
  3. //var entity = new ProductInventory();
  4. var entity = model.Adapt<ProductInventory>();
  5. return entity;
  6. }
  7.  
  8. public ProductInventory Update<TModel>(ProductInventory entity, TModel model)
  9. {
  10. model.Adapt(entity);
  11. return entity;
  12. }
  13.  
  14. public int InventoryAmount { get; private set; }
  15.  
  16. public void IncreaseInventory(int amount)
  17. {
  18. this.InventoryAmount += amount;
  19. }

仓储接口新增了Edit方法,以获取实体对象,再调用实体对象内部方法进行实体数据的修改。

仓库接口的Query方法不直接返回实体对象,而是直接返回Model对象(Dto、VO),提高数据库查询性能(通过Mapster的ProjectToType方法实现)。

仓储服务代码如下:

  1. using MediatR;
  2. using OnionArch.Domain.Common.Entities;
  3. using OnionArch.Domain.Common.Paged;
  4. using System.Linq.Expressions;
  5.  
  6. namespace OnionArch.Domain.Common.Repositories
  7. {
  8. public class RepositoryService<TEntity> where TEntity : BaseEntity
  9. {
  10. private readonly IMediator _mediator;
  11.  
  12. public RepositoryService(IMediator mediator)
  13. {
  14. _mediator = mediator;
  15. }
  16.  
  17. /// <summary>
  18. /// 创建单个实体
  19. /// </summary>
  20. /// <param name="entity"></param>
  21. /// <returns></returns>
  22. public async Task<TEntity> Add(TEntity entity)
  23. {
  24. return await _mediator.Send(new AddEntityRequest<TEntity>(entity));
  25. }
  26. /// <summary>
  27. /// 创建多个实体
  28. /// </summary>
  29. /// <param name="entities"></param>
  30. /// <returns></returns>
  31. public async Task Add(params TEntity[] entities)
  32. {
  33. await _mediator.Send(new AddEntitiesRequest<TEntity>(entities));
  34. }
  35.  
  36. /// <summary>
  37. /// 删除单个实体
  38. /// </summary>
  39. /// <param name="Id"></param>
  40. /// <returns></returns>
  41. public async Task<TEntity> Remove(Guid Id)
  42. {
  43. return await _mediator.Send(new RemoveEntityRequest<TEntity>(Id));
  44. }
  45. /// <summary>
  46. /// 删除多个实体
  47. /// </summary>
  48. /// <param name="whereLambda"></param>
  49. /// <returns></returns>
  50. public async Task<int> Remove(Expression<Func<TEntity, bool>> whereLambda)
  51. {
  52. return await _mediator.Send(new RemoveEntitiesRequest<TEntity>(whereLambda));
  53. }
  54.  
  55. /// <summary>
  56. /// 获取单个实体以更新实体字段
  57. /// </summary>
  58. /// <param name="Id"></param>
  59. /// <returns></returns>
  60. public async Task<TEntity> Edit(Guid Id)
  61. {
  62. return await _mediator.Send(new EditEntityRequest<TEntity>(Id));
  63. }
  64.  
  65. public async Task<IQueryable<TEntity>> Edit(Expression<Func<TEntity, bool>> whereLambda)
  66. {
  67. return await _mediator.Send(new EditEntitiesRequest<TEntity>(whereLambda));
  68. }
  69.  
  70. /// <summary>
  71. /// 查询单个实体(不支持更新实体)
  72. /// </summary>
  73. /// <param name="Id"></param>
  74. /// <returns></returns>
  75. public async Task<TModel> Query<TModel>(Guid Id)
  76. {
  77. return await _mediator.Send(new QueryEntityRequest<TEntity,TModel>(Id));
  78. }
  79. /// <summary>
  80. /// 查询多个实体
  81. /// </summary>
  82. public async Task<IQueryable<TModel>> Query<TModel>(Expression<Func<TEntity, bool>> whereLambda)
  83. {
  84. return await _mediator.Send(new QueryEntitiesRequest<TEntity,TModel>(whereLambda));
  85. }
  86. /// <summary>
  87. /// 分页查询多个实体
  88. /// </summary>
  89. /// <typeparam name="TOrder"></typeparam>
  90. /// <param name="whereLambda"></param>
  91. /// <param name="pageOption"></param>
  92. /// <param name="orderbyLambda"></param>
  93. /// <param name="isAsc"></param>
  94. /// <returns></returns>
  95. public async Task<PagedResult<TModel>> Query<TOrder,TModel>(Expression<Func<TEntity, bool>> whereLambda, PagedOption pagedOption, Expression<Func<TEntity, TOrder>> orderbyLambda, bool isAsc = true)
  96. {
  97. return await _mediator.Send(new QueryPagedEntitiesRequest<TEntity, TOrder,TModel>(whereLambda, pagedOption, orderbyLambda, isAsc));
  98. }
  99.  
  100. /// <summary>
  101. /// 判断是否有存在
  102. /// </summary>
  103. /// <param name="whereLambda"></param>
  104. /// <returns></returns>
  105. public async Task<bool> Any(Expression<Func<TEntity, bool>> whereLambda)
  106. {
  107. return await _mediator.Send(new AnyEntitiesRequest<TEntity>(whereLambda));
  108. }
  109. }
  110. }

四、对采用MediatR代替接口的探索

如上仓库服务代码,我并没有创建仓库接口并实现,而是完全基于MediatR直接实现仓库服务。这个我在MediatRPC - 基于MediatR和Quic通讯实现的RPC框架,比GRPC更简洁更低耦合,开源发布第一版 的MediatR编程思想中做过介绍,本次是实现这个编程思想,即不通过接口和依赖注入,而是通过MediatR来实现控制反转。如果大家不喜欢这种方式也可以修改回接口的方式。

鉴于篇幅所限,不能一一说明本次升级的所有改动,请大家下载代码自行研究,下面又到了找工作时间(是的,我还在找工作)。

五、找工作

博主有15年以上的软件技术经验(曾担任架构师和技术 Leader),擅长云原生、微服务和领域驱动软件架构设计,.Net Core  开发。
博主有15年以上的项目交付经验(曾担任项目经理和产品经理),专注于敏捷(Scrum )项目管理,业务分析和产品设计。
博主熟练配置和使用 Microsoft Azure 和Microsoft 365云(曾担任微软顾问)。
博主为人诚恳,工作认真负责,态度积极乐观。

我家在广州,也可以去深圳工作。做架构师、产品经理、项目经理都可以。有工作机会推荐的朋友可以加我微信 15920128707,微信名字叫Jerry。

OnionArch 2.0 - 基于DDD的洋葱架构改进版开源的更多相关文章

  1. 齐博软件 著名的老牌CMS开源系统 X1.0基于thinkphp开发的高性能免费开源PHP开放平台齐博x1.0基于thinkphp框架开发的高性能免费开源系统 主推圈子 论坛 预定拼团分销商城模块

    齐博X1--标签变量大全 1.网站名称: {$webdb.webname} 2.网址: {$webdb[www_url]} {:get_url('home')} 3.网站SEO关键词: 首页:{$we ...

  2. OnionArch - 采用DDD+CQRS+.Net 7.0实现的洋葱架构

    博主最近失业在家,找工作之余,看了一些关于洋葱(整洁)架构的资料和项目,有感而发,自己动手写了个洋葱架构解决方案,起名叫OnionArch.基于最新的.Net 7.0 RC1, 数据库采用Postgr ...

  3. 基于DDD的现代ASP.NET开发框架--ABP系列之3、ABP分层架构

    基于DDD的现代ASP.NET开发框架--ABP系列之3.ABP分层架构 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ABP的官方网站:ht ...

  4. 基于Java图片数据库Neo4j 3.0.0发布 全新的内部架构

    基于Java图片数据库Neo4j 3.0.0发布 全新的内部架构 Neo4j 3.0.0 正式发布,这是 Neo4j 3.0 系列的第一个版本.此版本对内部架构进行了全新的设计;提供给开发者更强大的生 ...

  5. 基于DDD的现代ASP.NET开发框架--ABP系列之2、ABP入门教程

    基于DDD的现代ASP.NET开发框架--ABP系列之2.ABP入门教程 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boi ...

  6. 基于DDD的现代ASP.NET开发框架--ABP系列之1、ABP总体介绍

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之1.ABP总体介绍 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)” ...

  7. EBI、DDD及其演变架构史

    一.引子 聊架构总离不开"领域驱动架构",大多能聊到DDD(Domain-Driven Design),实际上早期思想EBI架构 1992年就诞生了.核心价值点在于:关注核心业务领 ...

  8. 建筑材料系统 ASP.NET MVC4.0 + WebAPI + EasyUI + Knockout 的架构设计开发

    框架介绍: 1.基于 ASP.NET MVC4.0 + WebAPI + EasyUI + Knockout 的架构设计开发 2.采用MVC的框架模式,具有耦合性低.重用性高.生命周期成本低.可维护性 ...

  9. 高扩展的基于NIO的服务器架构

    当你考虑写一个扩展性良好的基于Java的服务器时,相信你会毫不犹豫地使用Java的NIO包.为了确保你的服务器能够健壮.稳定地运行,你可能会花大量的时间阅读博客和教程来了解线程同步的NIO selec ...

  10. 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获

    项目开发中的一些注意事项以及技巧总结   1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...

随机推荐

  1. 图学习【参考资料2】-知识补充与node2vec代码注解

    本项目参考: https://aistudio.baidu.com/aistudio/projectdetail/5012408?contributionType=1 *一.正题篇:DeepWalk. ...

  2. 【题解】CF45I TCMCF+++

    题面传送门 题目描述 有 \(n\) 个数 \(a_i\) 请你从中至少选出一个数,使它们的乘积最大 解决思路 对于正数,对答案一定有贡献(正数越乘越大),所以输入正数时直接输出即可. 对于负数,如果 ...

  3. Go语言核心36讲03

    [Go语言代码较多,建议配合文章收听音频.] 你好,我是郝林.从今天开始,我将和你一起梳理Go语言的整个知识体系. 在过去的几年里,我与广大爱好者一起见证了Go语言的崛起. 从Go 1.5版本的自举( ...

  4. centos8 telnet安装

    1. 装包 yum -y install telnet telnet-server 2. 启服务 systemctl enable telnet.socket --now 3. 防火墙开放端口 fir ...

  5. 基于python的数学建模---图论模型(Dijkstra)

    from collections import defaultdict from heapq import * # 堆--先进后出 inf = 99999 # 不连通值 mtx_graph = [[0 ...

  6. C#使用Task在Winform建立控件上的提示等待窗口,实现局部等待加载,不影响主线程(二)

    效果图: 源码:(处理了亿点点细节) 链接:https://pan.baidu.com/s/18S1IgQBOyXgeGvhnU3nrKQ?pwd=jpq9提取码:jpq9 作者:兮去博客出处:htt ...

  7. Oracle12c异常关闭后启动PDBORCL(ORA-01033)

    这个问题已经困扰了我好几天找解决方案,终于找到: 由于Oracle12c的特殊性,但许多用户并不想在创建用户时前面要加"C##" 那么就要创建PDBORCL数据库,来与Oracle ...

  8. MISC中的图片修改宽高问题

    在做CTF中MISC分类题目时,很常见的一个问题就是修改图片正确的宽与高 (此篇笔记中的内容以ctfshow中MISC入门分类为切入点,感兴趣的同学可以一边做一边有不会的看看,仅供参考,我是菜鸡) 曾 ...

  9. JUC面试点汇总

    JUC面试点汇总 我们会在这里介绍我所涉及到的JUC相关的面试点内容,本篇内容持续更新 我们会介绍下述JUC的相关面试点: 线程状态 线程池 Wait和Sleep Synchronized和Lock ...

  10. 医疗在线OLAP场景下基于Apache Hudi 模式演变的改造与应用

    背景 在 Apache Hudi支持完整的Schema演变的方案中(https://mp.weixin.qq.com/s/rSW864o2YEbHw6oQ4Lsq0Q), 读取方面,只完成了SQL o ...