net core-EventBus(CAP)

一 微服务之间的相关通信就是通过EventBus实现的,调用接口也不现实,不然还得等接口返回结果,如果其他接口挂了呢,要等半天吗。

相关资料:

https://blog.51cto.com/u_15127692/3465750

https://github.com/dotnetcore/CAP

https://www.cnblogs.com/savorboard/p/cap.html

常见的一些服务:

AddScoped,添加服务,服务实例的生命周期为Scoped。
AddTransient,添加服务,服务实例的生命周期为Transient(每次被使用都创建新对象)。
AddSingleton,添加服务,服务实例的生命周期为单例。

AddMvc,添加所有MVC所需的服务。
AddMvcCore,仅添加核心必要的MVC所需的服务。
AddControllers,添加启用Controller 所需要的服务,不包括View和Pages所需要的服务。
AddControllersWithViews,添加启用 Controller 以及 Razor 页面所需要的服务。
AddRazorPages,添加 Razor Pages 所需要的服务。

AddAntiforgery,添加防止CSRF攻击的服务。
AddAuthentication,添加启用Authentication中间件所需的服务。
AddAuthenticationCore,添加启用Authentication中间件所需的核心服务。
AddAuthorization,添加启用Authorization中间件所需的服务。
AddAuthorizationCore,添加启用Authorization中间件所需的核心服务。
AddAuthorizationPolicyEvaluator,添加 Authorization 策略评估服务。
AddCertificateForwarding,添加CertificateForwarding中间件所需的服务。
AddConnections,添加 http://ASP.NET Core Connection Handlers 所需的服务。
AddCors,添加CORS中间件 所需的服务。
AddDataProtection,添加 http://ASP.NET Core Data Protection 所需的服务。
AddDirectoryBrowser,添加 DirectoryBrowser 中间件所需的服务。
AddDistributedMemoryCache,添加分布式缓冲服务IDistributedCache,默认的实现将缓冲保存在内存中,要实现实际上的分布式缓冲你需要提供一个保存缓存的实现(Redis或数据库,如AddStackExchangeRedisCache和AddDistributedSqlServerCache)。
AddHealthChecks,添加HealthChecks中间件所需的服务。
AddHostedService,添加宿主服务,如持续运行的服务。
AddHostFiltering,添加HostFiltering中间件所需的服务。
AddHsts,添加HSTS中间件所需的服务。
AddHttpClient,添加IHttpClientFactory服务用于获取在服务器端发起Http请求的HttpClient对象。
AddHttpContextAccessor,添加Http上下文访问器服务,在例如Controller里有HttpContext属性的地方优先使用HttpContext,但如果在一个自定义的服务里你就需要IHttpContextAccessor服务来获取Http上下文。
AddHttpsRedirection,为HttpsRedirection中间件添加所需的服务。
AddIdentity,添加默认的身份系统,并制定 Role和User类型。
AddIdentityCore,添加默认身份执行的核心部分,并制定User类型。
AddLocalization,添加本地化服务。
AddLogging,添加日志服务。
AddMemoryCache,添加非分布式的内存缓存服务。
AddOptions,添加 Option 服务。
AddResponseCaching,为ResponseCaching中间件添加所需的服务。
AddResponseCompression,为ResponseCompression中间件添加所需的服务。
AddRouting,添加Routing中间件所需的服务。
AddSignalR,添加SignalR所需的服务。
AddSignalRCore,添加SignalR所需的核心服务。
AddServerSideBlazor,添加 Server-Side Blazor所需的服务。
AddWebEncoders,添加 HtmlEncoder,JavaScriptEncoder,UrlEncoder 三个服务

 

二 实际使用

1 appsetting:

"RabbitMQ": {
    "HostName": "127.0.0.1",
    "UserName": "guest",
    "Password": "guest",
    "VirtualHost": "blog",
    "ExchangeName": "blog_queue"
  },
"ConnectionStrings": {
    "postgres":"PORT=5432;DATABASE=demo;HOST=127.0.0.1;PASSWORD=1234;USER ID=postgres"
}

2 Program.cs

var services = builder.Services;
services.AddCap(options => {
    options.UsePostgreSql(builder.Configuration.GetConnectionString("postgres"));
    options.UseRabbitMQ(options =>
        {
            builder.Configuration.GetSection("RabbitMQ").Bind(options);
        });
});

3 发布,这样是到数据库里,但没有到RabbitMQ中。

[HttpGet("AdonetWithTransaction")]
    public IActionResult AdonetWithTransaction()
    {
        using (var connection = new NpgsqlConnection(ConnectionString))
        {
            using (var transaction = connection.BeginTransaction(_capBus, autoCommit: true))
            {
                //your business logic code

                _capBus.Publish("xxx.services.show.time", DateTime.Now);

                // Publish delay message
                _capBus.PublishDelayAsync(TimeSpan.FromSeconds(delaySeconds), "xxx.services.show.time", DateTime.Now);
            }
        }

        return Ok();
    }

接收:CapSubscribe特性

[HttpGet("CheckReceivedMessage")]
   [CapSubscribe("xxx.services.show.time")]
    public void CheckReceivedMessage(DateTime datetime)
    {
        Console.WriteLine(datetime);
    }

二 应用层:Application处理权限,数据权限,返回值封装单个url处理逻辑封装等

 

三 领域层 包含实体(充血模型)

 1 领域数据校验,如下图是在Customer内部创建数据的时候进行校验。

public static Customer CreateRegistered(
            string email, 
            string name,
            ICustomerUniquenessChecker customerUniquenessChecker)
        {
            CheckRule(new CustomerEmailMustBeUniqueRule(customerUniquenessChecker, email));

            return new Customer(email, name);
        }

放在领域实体

 public abstract class Entity
    {
        private List<IDomainEvent> _domainEvents;

        /// <summary>
        /// Domain events occurred.
        /// </summary>
        public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();

        /// <summary>
        /// Add domain event.
        /// </summary>
        /// <param name="domainEvent"></param>
        protected void AddDomainEvent(IDomainEvent domainEvent)
        {
            _domainEvents = _domainEvents ?? new List<IDomainEvent>();
            this._domainEvents.Add(domainEvent);
        }

        /// <summary>
        /// Clear domain events.
        /// </summary>
        public void ClearDomainEvents()
        {
            _domainEvents?.Clear();
        }

        protected static void CheckRule(IBusinessRule rule)
        {
            if (rule.IsBroken())
            {
                throw new BusinessRuleValidationException(rule);
            }
        }
    }
}

具体校验过程

public class CustomerEmailMustBeUniqueRule : IBusinessRule
    {
        private readonly ICustomerUniquenessChecker _customerUniquenessChecker;

        private readonly string _email;

        public CustomerEmailMustBeUniqueRule(
            ICustomerUniquenessChecker customerUniquenessChecker, 
            string email)
        {
            _customerUniquenessChecker = customerUniquenessChecker;
            _email = email;
        }

        public bool IsBroken() => !_customerUniquenessChecker.IsUnique(_email);

        public string Message => "Customer with this email already exists.";
    }

这个校验接口放在领域层_customerUniquenessChecker,实现放在数据持久层

public class CustomerUniquenessChecker : ICustomerUniquenessChecker
    {
        private readonly ISqlConnectionFactory _sqlConnectionFactory;

        public CustomerUniquenessChecker(ISqlConnectionFactory sqlConnectionFactory)
        {
            _sqlConnectionFactory = sqlConnectionFactory;
        }

        public bool IsUnique(string customerEmail)
        {
            var connection = this._sqlConnectionFactory.GetOpenConnection();

            const string sql = "SELECT TOP 1 1" +
                               "FROM [orders].[Customers] AS [Customer] " +
                               "WHERE [Customer].[Email] = @Email";
            var customersNumber = connection.QuerySingleOrDefault<int?>(sql,
                            new
                            {
                                Email = customerEmail
                            });

            return !customersNumber.HasValue;
        }
    }

返回结果:

public class BusinessRuleValidationException : Exception
    {
        public IBusinessRule BrokenRule { get; }

        public string Details { get; }

        public BusinessRuleValidationException(IBusinessRule brokenRule) : base(brokenRule.Message)
        {
            BrokenRule = brokenRule;
            this.Details = brokenRule.Message;
        }

        public override string ToString()
        {
            return $"{BrokenRule.GetType().FullName}: {BrokenRule.Message}";
        }
    }

 

 

四 领域事件 比如新增一个订单,可以通过事件通知给物流,已经短信处理等。按照实际业务,每一个操作后面都要相应的逻辑要同步。放在一起就是"事务代码",堆到一定程度就是shi山。下面这个实现逻辑是参考文章给的资料上的。

 1 这里先注册一下MediatR服务

// 注册中间者:MediatR 
services.AddMediatRServices();

 2 具体注册:

public static IServiceCollection AddMediatRServices(this IServiceCollection services)
{
    // 注册事务流程管理类
    services.AddTransient(typeof(IPipelineBehavior<,>), typeof(DomainContextTransactionBehavior<,>));
    // package: MediatR.Extensions.Microsoft.Dependency
    return services.AddMediatR(typeof(Order).Assembly, typeof(Program).Assembly);
}

3 用来标志领域事件的接口:

/// <summary>
/// 领域事件接口
/// 用来标记我们某一个对象是否是领域事件
/// </summary>
public interface IDomainEvent : INotification
{
}

4 用来标记领域事件处理的接口:

/// <summary>
/// 领域事件处理器接口
/// </summary>
public interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent
{
    //这里我们使用了INotificationHandler的Handle方法来作为处理方法的定义,所以无需重新定义
    //Task Handle(TDomainEvent domainEvent, CancellationToken cancellationToken);
}

实际上就是在mediator的INotification和INotificationHandler 封装一层。

那么是否有必要封装呢?其实我们大多数使用别人的库,如果不是静态调用最好封装一层,这样可以让上层看来依赖于下层的IDomainEvent和IDomainEventHandler,而不是和某个框架耦合在一起。

比如说有一个框架是Mediator的升级版,兼容了Mediator的功能,但是暴露出来的接口是INotificationPlusHandler,如果上层去耦合的话,改动的地方就有点多,风险也就越高,这不符合稳定性。
 

/// <summary>
/// 实体抽象类(包含多个主键的实体接口)
/// </summary>
public abstract class Entity : IEntity
{
    public abstract object[] GetKeys();

    public override string ToString()
    {
        return $"[Entity:{GetType().Name}] Keys = {string.Join(",", GetKeys())}";
    }

    #region 领域事件定义处理 DomainEvents

    /// <summary>
    /// 领域事件集合
    /// </summary>
    private List<IDomainEvent> _domainEvents;

    /// <summary>
    /// 获取当前实体对象领域事件集合(只读)
    /// </summary>
    public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();

    /// <summary>
    /// 添加领域事件至当前实体对象领域事件结合中
    /// </summary>
    /// <param name="eventItem"></param>
    public void AddDomainEvent(IDomainEvent eventItem)
    {
        _domainEvents = _domainEvents ?? new List<IDomainEvent>();
        _domainEvents.Add(eventItem);
    }

    /// <summary>
    /// 移除指定领域事件
    /// </summary>
    /// <param name="eventItem"></param>
    public void RemoveDomainEvent(IDomainEvent eventItem)
    {
        _domainEvents?.Remove(eventItem);
    }

    /// <summary>
    /// 清空所有领域事件
    /// </summary>
    public void ClearDomainEvents()
    {
        _domainEvents?.Clear();
    }

    #endregion
}

/// <summary>
/// 实体抽象类(包含唯一主键Id的实体接口)
/// </summary>
/// <typeparam name="TKey">主键ID类型</typeparam>
public abstract class Entity<TKey> : Entity, IEntity<TKey>
{
    int? _requestedHasCode;
    public virtual TKey Id { get; protected set; }
    public override object[] GetKeys()
    {
        return new object[] { Id };
    }

    /// <summary>
    /// 对象是否想等
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is Entity<TKey>))
        {
            return false;
        }

        if (Object.ReferenceEquals(this, obj))
        {
            return true;
        }

        Entity<TKey> item = (Entity<TKey>)obj;

        if (item.IsTransient() || this.IsTransient())
        {
            return false;
        }
        else
        {
            return item.Id.Equals(this.Id);
        }
    }

    public override int GetHashCode()
    {
        if (!IsTransient())
        {
            if (!_requestedHasCode.HasValue)
            {
                _requestedHasCode = this.Id.GetHashCode() ^ 31; // TODO
            }
            return _requestedHasCode.Value;
        }
        else
        {
            return base.GetHashCode();
        }
    }

    /// <summary>
    /// 对象是否为全新创建的,未持久化的
    /// </summary>
    /// <returns></returns>
    public bool IsTransient()
    {
        return EqualityComparer<TKey>.Default.Equals(Id, default);
    }
    public override string ToString()
    {
        return $"[Entity:{GetType().Name}] Id = {Id}";
    }

    /// <summary>
    /// == 操作符重载
    /// </summary>
    /// <param name="left"></param>
    /// <param name="right"></param>
    /// <returns></returns>
    public static bool operator ==(Entity<TKey> left,Entity<TKey> right)
    {
        if (Object.Equals(left,null))
        {
            return (Object.Equals(right, null)) ? true : false;
        }
        else
        {
            return left.Equals(right);
        }
    }

    /// <summary>
    /// != 操作符重载
    /// </summary>
    /// <param name="left"></param>
    /// <param name="right"></param>
    /// <returns></returns>
    public static bool operator !=(Entity<TKey> left, Entity<TKey> right)
    {
        return !(left == right);
    }
}

那么我们在领域层中,我们的实体可以这样定义:

/// <summary>
/// 订单实体
/// </summary>
public class Order : Entity<long>, IAggregateRoot
{
    // 实体内字段的 set 方法都是 private 的
    // 实体类型相关的数据操作,都应该是由我们实体来负责,而不是被外部的对象去操作
    // 这样的好处是让我们的领域模型符合封闭开放的原则

    public string UserId { get; private set; }
    public string UserName { get; private set; }
    public Address Address { get; private set; }
    public int ItemCount { get; set; }

    protected Order()
    {
    }

    public Order(string userId, string userName, int itemCount, Address address)
    {
        this.UserId = userId;
        this.UserName = userName;
        this.ItemCount = itemCount;
        this.Address = address;

        // 构造新的Order对象的时候,添加一个创建Order领域事件
        this.AddDomainEvent(new OrderCreatedDomainEvent(this));
    }

    /// <summary>
    /// 修改收货地址
    /// </summary>
    /// <param name="address"></param>
    public void ChangeAddress(Address address)
    {
        this.Address = address;

        // 同样的,在修改地址操作时,也该定义一个类似的修改地址领域事件
        //this.AddDomainEvent(new OrderAddressChangedDomainEvent(this));
    }
}

CreateCommand:

/// <summary>
/// 创建订单 Command
/// </summary>
public class CreateOrderCommand : IRequest<long>
{
    public long ItemCount { get; private set; }

    // public CreateOrderCommand() { }
    public CreateOrderCommand(int itemCount)
    {
        ItemCount = itemCount;
    }
}
/// <summary>
/// 领域事件:订单创建命令处理程序
/// 注:在创建完我们的领域模型并将其保存之后才会触发该处理程序
/// </summary>
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, long>
{
    IOrderRepository _orderRepository;
    ICapPublisher _capPublisher;

    public CreateOrderCommandHandler(IOrderRepository orderRepository, ICapPublisher capPublisher)
    {
        _orderRepository = orderRepository;
        _capPublisher = capPublisher;
    }

    /// <summary>
    /// 处理订单创建命令
    /// </summary>
    /// <param name="request"></param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task<long> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
    {
        var address = new Address("wen san lu", "hangzhou", "310000");
        var order = new Order("xiaohong1999", "小红", (int)request.ItemCount, address);

        _orderRepository.Add(order);
        await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
        return order.Id;
    }
}
 

创建订单:

/// <summary>
/// 创建订单
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
[HttpPost]
public async Task<long> CreateOrder([FromBody] CreateOrderCommand cmd)
{
    // 中间者,发送订单创建命令
    return await _mediator.Send(cmd, HttpContext.RequestAborted);
}
/// <summary>
/// 中间者,领域事件发布扩展类
/// </summary>
public static class MediatorExtension
{
    /// <summary>
    /// 领域事件发布,执行事件发送
    /// </summary>
    /// <param name="mediator"></param>
    /// <param name="ctx"></param>
    /// <returns></returns>
    public static async Task DispatchDomainEventsAsync(this IMediator mediator, DbContext ctx)
    {
        // 1. 从当前要保存的 EntityContext 里面去跟踪实体
        //    从跟踪到的实体对象中,获取到我们当前的 Event
        var domainEntities = ctx.ChangeTracker
                                .Entries<Entity>()
                                .Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());

        // Events 类型转换
        var domainEvents = domainEntities.SelectMany(x => x.Entity.DomainEvents).ToList();

        // 2. 将实体内的 Events 清除 
        domainEntities.ToList().ForEach(entity => entity.Entity.ClearDomainEvents());

        // 3. 将所有的 Event 通过中间者发送出去
        //    发出后,并找到对应的 handle 进行处理
        foreach (var domainEvent in domainEvents)
        {
            await mediator.Publish(domainEvent);
        }
    }
}

这里是在EFContext中的save中。

 

 

 

 

 

待续。。。

 

posted @ 2023-06-08 15:48  vba是最好的语言  阅读(123)  评论(0编辑  收藏  举报