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

我在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发布集成事件的代码请查看源代码。

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

二、自动生成Minimal WebApi接口

该特性我在 根据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:

洋葱架构理论_洋葱架构理论_02

 三、对充血模型的支持

我在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方法实现)。

仓储服务代码如下:

洋葱架构理论_开发语言_03

洋葱架构理论_架构_04

洋葱架构理论_开发语言_05

四