- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
大家好, 去年我发布了一篇 OnionArch - 采用DDD+CQRS+.Net 7.0实现的洋葱架构 。 很多程序员都比较感兴趣,给我要源代码。 这次我把OnionArch进行了升级,改进了一些特性,并放出源代码, iamxiaozhuang/OnionArch2 (github.com) 欢迎大家研究使用.
我在OninArch1.0实现了对删除的实体自动生成和发布领域事件,并通过订阅这个领域事件,将删除的实体数据备份至回收站表中,以备审计和数据恢复.
本次我改进了这个特性,对实体数据的新增,修改和删除都会自动生成和发布领域事件。我认为尽量不要通过修改代码来新增和发布领域事件,这会导致新增的业务功能也需要修改代码而不是新增代码来实现,不符合对修改关闭和对扩展开放的设计原则。应该对实体数据的任何变动都自动发布领域事件,然后在事件Handler中筛选需要的领域事件并进行处理.
我基于这个特性实现了按配置自动审计记录和和按配置自动发布集成事件功能.
我们可以通过配置的方式来实现那些实体,那种修改类型,那个字段需要审计。请看如下配置:
"EntityChangedAuditLogsConfig" : [ { "EntityFullName": "OnionArch.Domain.ProductInventory.ProductInventory" , "ChangeType": "Added" }, { "EntityFullName": "OnionArch.Domain.ProductInventory.ProductInventory" , "ChangeType": "Modified" , "Properties": "InventoryAmount" }, { "EntityFullName": "OnionArch.Domain.ProductInventory.ProductInventory" , "ChangeType": "Deleted" } ],
可以按照实体的全名,修改类型(新增,修改,删除),甚至是实体的修改字段来配置是否需要进行数据审计,如过需要审计则会自动保存审计日志到审计表,审计表包含实体的原值和当前值,修改人和修改时间等.
我们可以通过配置来实现该微服务的那些领域事件需要转为集成事件发布出去,供其它的微服务订阅使用。这样我们在微服务中新增集成事件订阅的时候就不需要修改源微服务的代码,只需要在源微服务中增加配置即可.
我们需要配置Dapr的发布订阅名称,事件Topic和这个Topic的发布条件,例如,在产品仓库实体的库存数量被修改后发布Topic为“ProductInventoryAmountChanged”集成事件.
"EntityChangedIntegrationEventConfig" : { "PubsubName": "pubsub" , "Topics" : [ { "TopicName": "ProductInventoryAmountChanged" , "EntityFullName": "OnionArch.Domain.ProductInventory.ProductInventory" , "ChangeType": "Modified" , "Properties": "InventoryAmount" } ] },
然后就可通过Dapr在其它的微服务中订阅并处理该集成事件,通过Dapr发布集成事件的代码请查看源代码.
已发布的集成事件也会自动保存至集成事件记录表中,以备对该事件进行后续执行跟踪和重发.
该特性我在 根据MediatR的Contract Messages自动生成Minimal WebApi接口 中做过介绍。因为OninArch通过MediatR实现了CQRS和其它AOP功能,例如业务实体验证,异常处理、工作单元等特性.
本次将OninArch1.0的GRPC接口替换成了自动生成的WebAPI接口。并对自动生成WebAPI接口做了改进,可以指定生成的WebAPI的Http方法,地址、介绍和详细说明。自动对接口按命名空间分类,将Get方法参数自动映射到Query参数等.
[MediatorWebAPIConfig(HttpMethod = HttpMethodToGenerate.Post, HttpUrl = " /productinventory " , Summary = " 创建产品库存 " , Description = " 创建产品库存 Description " )] public class CreateProductInventory : ICommand<Unit> { public string ProductCode { get ; set ; } public int InventoryAmount { get ; set ; } } [MediatorWebAPIConfig(HttpMethod = HttpMethodToGenerate.Patch, HttpUrl = " /productinventory/increase " , Summary = " 增加产品库存 " )] public class IncreaseProductInventory : ICommand<Unit> { public Guid Id { get ; set ; } public int Amount { get ; set ; } }
生成的WebAPI:
。
我在OninArch1.0中并没有刻意强调充血模型,本次按照我对充血模型的理解改进了仓储代码,即,仓储服务只实现实体的Add,Remove和Query,不实现实体的Create和Modify。实体的创建和修改必须放入实体中实现。也就是说,实体字段的set都是私有的,只能在实体内部对实体的字段进行修改,以保证将业务逻辑封装到实体中,并提高系统的稳定性和业务逻辑重用性.
public static ProductInventory Create<TModel> (TModel model) { // var entity = new ProductInventory(); var entity = model.Adapt<ProductInventory> (); return entity; } public ProductInventory Update<TModel> (ProductInventory entity, TModel model) { model.Adapt(entity); return entity; } public int InventoryAmount { get ; private set ; } public void IncreaseInventory( int amount) { this .InventoryAmount += amount; }
仓储接口新增了Edit方法,以获取实体对象,再调用实体对象内部方法进行实体数据的修改.
仓库接口的Query方法不直接返回实体对象,而是直接返回Model对象(Dto、VO),提高数据库查询性能(通过Mapster的ProjectToType方法实现).
仓储服务代码如下:
using MediatR; using OnionArch.Domain.Common.Entities; using OnionArch.Domain.Common.Paged; using System.Linq.Expressions; namespace OnionArch.Domain.Common.Repositories { public class RepositoryService<TEntity> where TEntity : BaseEntity { private readonly IMediator _mediator; public RepositoryService(IMediator mediator) { _mediator = mediator; } /// <summary> /// 创建单个实体 /// </summary> /// <param name="entity"></param> /// <returns></returns> public async Task<TEntity> Add(TEntity entity) { return await _mediator.Send( new AddEntityRequest<TEntity> (entity)); } /// <summary> /// 创建多个实体 /// </summary> /// <param name="entities"></param> /// <returns></returns> public async Task Add( params TEntity[] entities) { await _mediator.Send( new AddEntitiesRequest<TEntity> (entities)); } /// <summary> /// 删除单个实体 /// </summary> /// <param name="Id"></param> /// <returns></returns> public async Task<TEntity> Remove(Guid Id) { return await _mediator.Send( new RemoveEntityRequest<TEntity> (Id)); } /// <summary> /// 删除多个实体 /// </summary> /// <param name="whereLambda"></param> /// <returns></returns> public async Task< int > Remove(Expression<Func<TEntity, bool >> whereLambda) { return await _mediator.Send( new RemoveEntitiesRequest<TEntity> (whereLambda)); } /// <summary> /// 获取单个实体以更新实体字段 /// </summary> /// <param name="Id"></param> /// <returns></returns> public async Task<TEntity> Edit(Guid Id) { return await _mediator.Send( new EditEntityRequest<TEntity> (Id)); } public async Task<IQueryable<TEntity>> Edit(Expression<Func<TEntity, bool >> whereLambda) { return await _mediator.Send( new EditEntitiesRequest<TEntity> (whereLambda)); } /// <summary> /// 查询单个实体(不支持更新实体) /// </summary> /// <param name="Id"></param> /// <returns></returns> public async Task<TModel> Query<TModel> (Guid Id) { return await _mediator.Send( new QueryEntityRequest<TEntity,TModel> (Id)); } /// <summary> /// 查询多个实体 /// </summary> public async Task<IQueryable<TModel>> Query<TModel>(Expression<Func<TEntity, bool >> whereLambda) { return await _mediator.Send( new QueryEntitiesRequest<TEntity,TModel> (whereLambda)); } /// <summary> /// 分页查询多个实体 /// </summary> /// <typeparam name="TOrder"></typeparam> /// <param name="whereLambda"></param> /// <param name="pageOption"></param> /// <param name="orderbyLambda"></param> /// <param name="isAsc"></param> /// <returns></returns> public async Task<PagedResult<TModel>> Query<TOrder,TModel>(Expression<Func<TEntity, bool >> whereLambda, PagedOption pagedOption, Expression<Func<TEntity, TOrder>> orderbyLambda, bool isAsc = true ) { return await _mediator.Send( new QueryPagedEntitiesRequest<TEntity, TOrder,TModel> (whereLambda, pagedOption, orderbyLambda, isAsc)); } /// <summary> /// 判断是否有存在 /// </summary> /// <param name="whereLambda"></param> /// <returns></returns> public async Task< bool > Any(Expression<Func<TEntity, bool >> whereLambda) { return await _mediator.Send( new AnyEntitiesRequest<TEntity> (whereLambda)); } } }
如上仓库服务代码,我并没有创建仓库接口并实现,而是完全基于MediatR直接实现仓库服务。这个我在 MediatRPC - 基于MediatR和Quic通讯实现的RPC框架,比GRPC更简洁更低耦合,开源发布第一版 的MediatR编程思想中做过介绍,本次是实现这个编程思想,即不通过接口和依赖注入,而是通过MediatR来实现控制反转。如果大家不喜欢这种方式也可以修改回接口的方式.
鉴于篇幅所限,不能一一说明本次升级的所有改动,请大家下载代码自行研究,下面又到了找工作时间(是的,我还在找工作).
▪ 博主有15年以上的软件技术经验(曾担任架构师和技术 Leader),擅长云原生、微服务和领域驱动软件架构设计,.Net Core 开发。 ▪ 博主有15年以上的项目交付经验(曾担任项目经理和产品经理),专注于敏捷(Scrum )项目管理,业务分析和产品设计。 ▪ 博主熟练配置和使用 Microsoft Azure 和Microsoft 365云(曾担任微软顾问)。 ▪ 博主为人诚恳,工作认真负责,态度积极乐观.
我家在广州,也可以去深圳工作。做架构师、产品经理、项目经理都可以。有工作机会推荐的朋友可以加我微信 15920128707,微信名字叫Jerry.
。
最后此篇关于OnionArch2.0-基于DDD的洋葱架构改进版开源的文章就讲到这里了,如果你想了解更多关于OnionArch2.0-基于DDD的洋葱架构改进版开源的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
从开发者的角度来看,Mac 版 Safari 和 Windows 版 Safari 有何不同? 我认为可以归结为评估两者之间的差异(如果我遗漏了什么,请更正): - 布局渲染 - Javascript
正如标题所说:Android 版 Chrome 和 iOS 版 Chrome 有什么区别。 我对两者进行了一些研究,但找不到关于该主题的任何最新信息。进行这项研究的原因是因为我正在研究某些 Web A
我有以下脚本可以获取您的地理位置并重定向您到 Google map : (function(){ navigator.geolocation.getCurrentPosition(function(p
我负责修复导航栏显示比应有的低 1 像素的问题。 查看网站后,我无法找到所报告的问题,直到我在 Mac 上进行了检查。 Firefox、Safari 等在 Mac 上运行良好,但 Chrome 是导致
我是典型的 .NET 开发人员(C# 是我的第一语言),几年前转向 ASP.NET MVC。现在是我职业生涯发生重大变化的新时期。如果我们看看 Web 开发的前景,我们可以看到新技术如何占领世界,而其
Grails 2.0 项目目前带有资源插件 1.1.5,它似乎有几个依赖问题(例如,参见 this post 的答案)。我正在使用 IntelliJ,虽然我将 BuildConfig.groovy 更
我有一个支持 android 2.3.3 的 android 项目。 但它也支持 sdk 版本 17。当我创建一个新 Activity 时,它会创建一个特定于版本 17 的 Activity 。 如何
有没有人有在 Android 设备上使用 pjsip 的经验?我看到几个非商业/测试项目使用它,所以我假设它可以完成,但没有一个有很好的记录。我认为 pjsip-jni 项目是一个不错的起点,但基本上
谁能告诉我在 Xcode (iPhone) 中执行以下操作的最佳方法是什么。 我有一个主导航屏幕,上面有一些按钮。当用户单击任何按钮时,他们将被带到带有更多选项按钮的子导航屏幕。在这里,他们单击任意一
我正在使用 JBoss Embedded beta3.SP10 版本,我正面临一个应该在某些 Hibernate 版本中修复的持久性错误。可悲的是,我不知道我的 JBoss Embedded 中使用的
我想在 android 中使用简单的 snmp get。我找到了 java 的代码并尝试在 android 中使用它。我还附加了 snmp4j.jar 文件用于 android。但是我得到了 Null
我的实现目标是: 可以通过一个或多个关键词搜索到文章。 可以通过文章的关键词列表查询到其相关文章。 查询到的结果依据相关程度降序排列。 查询速度要够快。(理论上关键词检索比全文检索要快很多的
我正在尝试创建一个允许我将视频从 iPhone 流式传输到服务器的应用程序。我目前关于如何做到这一点的理论是创建一系列 FFMpeg 文件并将它们发送到服务器。据我所知,我已经编译了 FFMpeg图书
这个问题在这里已经有了答案: Login failed in github for window (5 个回答) 7年前关闭。 当我安装 GitHub 时,我无法使用我的帐户凭据登录。 我收到错误 L
我需要在我的 iPad 项目中使用 Three20。我想知道 iPhone 版本的 Three20 项目是否可以直接在 iPad 上使用,还是应该等待这个时间线完成: http://three20.i
有人能做到吗 http://www.surina.net/soundtouch/适用于 iPhone? 简单的 Xcode 演示会很有帮助。 我只想通过一些音调操作来播放音效。谢谢克里斯 最佳答案 使
如何在iPhone中使用“speex”进行音频编码/解码?我没有在项目中添加框架。 最佳答案 这个blog entry: Compile Speex For iPhone克利夫顿·克雷格(Clifto
我想知道bonjour是公共(public)API还是私有(private)API?我们可以直接在我们的应用程序中使用它吗? 最佳答案 Bonjour 由 NSNetServices 和 CFNetS
••••• 已解决•••••该应用程序可用。只是花了一些时间才出现。我之所以将其视为测试版,是因为我的 Google 帐户用于 alpha 测试。如果您遇到同样的问题,只需从测试人员中删除您的帐户并等
我是 Android 编程初学者。 我在使用 Android 下载文件时遇到问题 我使用了 Httpost、Httpget 和 hhtpurlconnection前两个根本不起作用第三个两次无法下载
我是一名优秀的程序员,十分优秀!