ChuanGoing 2019-08-06 

前言

  开篇之前,简单说明下随笔原因。在园子里游荡了好久,期间也起过要写一些关于.NET的随笔,因各种原因未能付诸实现。

前段时间拜读daxnet的系列文章,感受颇深,犹豫好久,终于决定开始记录本人的学习点滴。

系列说明

  本系列目的是构建一套基于领域驱动设计(DDD)的基础架构,渐进式实现CQRS/消息事件驱动型业务基础框架,中间会夹杂着其他的中间件的学习介绍,仅供学习交流用(.NET CORE/Standard 2.0)。

因为接触领域驱动设计时间不长,现实上述目标可能会比较曲折,有不规范的地方望读者指正。

  构建开始前,简单介绍下本篇的学习曲线:

1.引入Ioc/DI

2.简单型事件驱动总线(EventBus)实现(事件定义/订阅及派发,事件处理器等)

注:篇尾我会附上Github源码地址(开发工具是VS2017/19,.NET CORE 2.2)

Ioc/DI

  Asp.net Core 自带的Ioc容器用起来不大方便,本系列引入Autofac作为Ioc/DI容器,先简单介绍下几个常规用法

首先创建一个Asp.net core web api应用程序,新建一个.Net Standard项目Base.Ioc用于管理Ioc/DI操作,并添加下图的Nuget依赖

添加扩展类AutofacExtensions,添加如下方法(这里引入了AspectCore动态代理后续实现Aop会用到)

public static IServiceProvider UseAutofac<TModule>(this IServiceCollection services)
where TModule : Module, new()
{
ContainerBuilder builder = new ContainerBuilder();
builder.Populate(services); builder.RegisterModule<TModule>();
       //引入AspectCore.Extensions.Autofac
builder.RegisterDynamicProxy();
IContainer container = builder.Build();
return new AutofacServiceProvider(container);
}

在Startup.cs文件的ConfigureServices中替换Asp.net Core自带Ioc容器:

// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); //替换Ioc容器,并扩展Autofac模块注册
return services.UseAutofac<WebModule>();
}

上面替换Ioc容器的时候,引入了Autofac的模块自动注入功能

 public class WebModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//builder.RegisterType<AutoFacManager>();
//builder.RegisterType<Worker>().As<IPerson>();
        //扫描程序集自动注册
builder.RegisterAssembly(ThisAssembly);
        }
}

在Base.Ioc项目中添加AutofacInjectionExtensions.cs文件

/// <summary>
/// Autofac自动注入
/// </summary>
/// <param name="builder"></param>
/// <param name="assembly"></param>
public static void RegisterAssembly(this ContainerBuilder builder, Assembly assembly)
{
foreach (var type in assembly.ExportedTypes)
{
if (type.IsPublic && !type.IsAbstract && type.IsClass)
{
var interfaces = type.GetInterfaces();
IList<Type> transientList = new List<Type>();
IList<Type> scopeList = new List<Type>();
IList<Type> singletonList = new List<Type>();
foreach (var intrType in interfaces)
{
if (intrType.IsGenericType)
{
if (intrType.IsAssignableTo<IDependencyInstance>())
{
transientList.Add(intrType);
}
else if (intrType.IsAssignableTo<IScopeInstance>())
{
scopeList.Add(intrType);
}
else if (intrType.IsAssignableTo<ISingletonInstance>())
{
singletonList.Add(intrType);
}
}
else
{
if (intrType.IsAssignableTo<IDependencyInstance>())
{
transientList.Add(intrType);
}
else if (intrType.IsAssignableTo<IScopeInstance>())
{
scopeList.Add(intrType);
}
else if (intrType.IsAssignableTo<ISingletonInstance>())
{
singletonList.Add(intrType);
}
}
} if (type.IsGenericType)
{
if (transientList.Count > )
{
builder.RegisterGeneric(type).As(transientList.ToArray()).InstancePerDependency();
}
if (scopeList.Count > )
{
builder.RegisterGeneric(type).As(scopeList.ToArray()).InstancePerLifetimeScope();
}
if (singletonList.Count > )
{
builder.RegisterGeneric(type).As(singletonList.ToArray()).SingleInstance();
} //泛型
if (type.IsAssignableTo<IDependencyInstance>())
{
builder.RegisterGeneric(type).AsSelf().InstancePerDependency();
}
else if (type.IsAssignableTo<IScopeInstance>())
{
builder.RegisterGeneric(type).AsSelf().InstancePerLifetimeScope();
}
else if (type.IsAssignableTo<ISingletonInstance>())
{
builder.RegisterGeneric(type).AsSelf().SingleInstance();
}
}
else
{
if (transientList.Count > )
{
builder.RegisterType(type).As(transientList.ToArray()).InstancePerDependency();
}
if (scopeList.Count > )
{
builder.RegisterType(type).As(scopeList.ToArray()).InstancePerLifetimeScope();
}
if (singletonList.Count > )
{
builder.RegisterType(type).As(singletonList.ToArray()).SingleInstance();
}
//
if (type.IsAssignableTo<IDependencyInstance>())
{
builder.RegisterType(type).AsSelf().InstancePerDependency();
}
else if (type.IsAssignableTo<IScopeInstance>())
{
builder.RegisterType(type).AsSelf().InstancePerLifetimeScope();
}
else if (type.IsAssignableTo<ISingletonInstance>())
{
builder.RegisterType(type).AsSelf().SingleInstance();
}
}
}
}
}

上面一段代码用到了IDependencyInstance/IScopeInstance/ISingletonInstance三个接口,分别用于瞬时/Scope/单例的服务标识。

大概说明下这段代码的作用:通过扫描传入的程序集获取外部可见的Public类型的Type(这里我们指的是类),扫描该类的所有继承了服务标识接口,并注册为对应的服务

至此,Ioc容器已替换为Autofac

EventBus

  事件总线实现发布/订阅功能,首先定义IEvent/IEventHandler,IEventHandler 定义了事件处理方法

public interface IEvent
{
Guid Id { get; }
/// <summary>
/// 时间戳
/// </summary>
long Timestamp { get; }
}
public interface IEventHandler
{
/// <summary>
/// 处理事件
/// </summary>
/// <param name="event"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<bool> HandleAsync(IEvent @event, CancellationToken cancellationToken = default(CancellationToken));

/// <summary>
  /// 可否处理
  /// </summary>
  /// <param name="event"></param>
  /// <returns></returns>
  bool CanHandle(IEvent @event);

    }
接着定义发布/订阅及事件总线接口
public interface IEventPublisher
{
/// <summary>
/// 发布事件
/// </summary>
/// <typeparam name="TEvent"></typeparam>
/// <param name="event"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task PublishAsync<TEvent>(TEvent @event, CancellationToken cancellationToken = default(CancellationToken))
where TEvent : IEvent;
}
public interface IEventSubscriber
{
/// <summary>
/// 事件订阅
/// </summary>
void Subscribe();
}
public interface IEventBus : IEventSubscriber, IEventPublisher
{
}

EventBus实现发布/订阅器,并在事件发布的同时通知相应的事件处理器进行相关处理。

这里引入了"消息队列"的概念(当然目前我们只是用来模拟消息队列,后续会引入RabbitMQ来实现)

相关代码如下:

 internal sealed class EventQueue
{
public event EventHandler<EventProcessedEventArgs> EventPushed; public EventQueue()
{ } public void Push(IEvent @event)
{
OnMessagePushed(new EventProcessedEventArgs(@event));
} private void OnMessagePushed(EventProcessedEventArgs e)
{
this.EventPushed?.Invoke(this, e);
}
}
/// <summary>
/// 消息事件参数
/// </summary>
public class EventProcessedEventArgs : EventArgs
{
public IEvent Event { get; } public EventProcessedEventArgs(IEvent @event)
{
Event = @event;
}
}
public class EventBus : IEventBus
{
private readonly EventQueue eventQueue = new EventQueue();
private readonly IEnumerable<IEventHandler> eventHandlers; public EventBus(IEnumerable<IEventHandler> eventHandlers)
{
this.eventHandlers = eventHandlers;
} /// <summary>
/// 发布事件到队列时触发处理事件
/// </summary>
/// <param name="sendere"></param>
/// <param name="e"></param>
private void EventQueue_EventPushed(object sendere, EventProcessedEventArgs e)
{
(from eh in this.eventHandlers
where
eh.CanHandle(e.Event)
select eh).ToList().ForEach(async eh => await eh.HandleAsync(e.Event));
} public Task PublishAsync<TEvent>(TEvent @event, CancellationToken cancellationToken = default(CancellationToken))
where TEvent : IEvent
=> Task.Factory.StartNew(() => eventQueue.Push(@event)); /// <summary>
/// 事件订阅(订阅队列上的事件)
/// </summary>
public void Subscribe()
{
eventQueue.EventPushed += EventQueue_EventPushed;
}

上面的代码中EventQueue的Push方法被调用时,会触发EventPushed事件,在EventBus中,我们注册了EventQueue的EventPushed事件,即最终会触发EventBus的EventQueue_EventPushed事件,进而通过事件处理器来处理(这块详细说明,请阅读DaxNet-事件驱动型架构实现一

到此,消息总线机制处理完成,接下来我们创建一个Web API应用程序来演示消息发布/订阅及处理

上面我们定义了IEvent/IEventHandler,这里我们先在WebAPI 项目中来实现

public class CustomerCreatedEvent : IEvent
{
public CustomerCreatedEvent(string customerName)
{
this.Id = Guid.NewGuid();
this.Timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();
this.CustomerName = customerName;
} public Guid Id { get; } public long Timestamp { get; } public string CustomerName { get; }
}
public class CustomerCreatedEventHandler : IEventHandler<CustomerCreatedEvent>
{
public bool CanHandle(IEvent @event)
=> @event.GetType().Equals(typeof(CustomerCreatedEvent)); public Task<bool> HandleAsync(CustomerCreatedEvent @event, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(true);
} public Task<bool> HandleAsync(IEvent @event, CancellationToken cancellationToken = default(CancellationToken))
=> CanHandle(@event) ? HandleAsync((CustomerCreatedEvent)@event, cancellationToken) : Task.FromResult(false);
}

值得说明下的是,在实现IEventHandler的时候,利用了泛型接口IEventHandler<T>来建立IEventHandler对于IEvent的依赖,因为事件处理器最终处理的必然是某个事件。

OK,现在新建一个Controller

[Route("api/[controller]")]
public class CustomersController : Controller
{private readonly IEventBus _eventBus; public CustomersController(IEventBus eventBus)
{
_eventBus = eventBus;
}
// 创建新的客户信息
[HttpPost]
public async Task<IActionResult> Create([FromBody] CustomerDto model)
{
var name = model.Name;
if (string.IsNullOrEmpty(name))
{
return BadRequest();
}
       //这里其他业务处理...
        await _eventBus.PublishAsync(new CustomerCreatedEvent(name));
 } }

上面的CustomersController构造函数中由Ioc注入了eventBus,需要注意的是,引用eventBus前,需要在Startup.cs中注册对应的服务,我们这里用到的是Autofac的模块化注册。

利用Web API下的WebModule.cs引用SimpleEventBus中的EventBusModule

 public class WebModule : Module
{
protected override void Load(ContainerBuilder builder)
{
       //扫描程序集自动注册
builder.RegisterAssembly(ThisAssembly);
       //注册模块化EventBusModule
builder.RegisterModule<EventBusModule>();
}
}
public class EventBusModule : Module
{
protected override void Load(ContainerBuilder builder)
{
       //扫描程序集自动注册
builder.RegisterAssembly(ThisAssembly);
}
}

关于扫描程序集自动注册,上面Ioc段落有详细说明,这里就不再啰嗦。

到此为止,编码工作告一段落,运行Web API,利用Postman或PowerShell 自带的命令 Invoke-WebRequest模拟http请求,结果如下图:

请求进来,数据加载到Dto中

EventBus的事件发布时调用EventQueue的Push函数,同时会触发EventPushed事件,通过对应的时间处理器处理事件

最终,消息在事件处理器中进行相关处理

回顾

  回顾一下,本篇开头介绍了Autofac替换Asp.Net Core自带Ioc容器,Autofac的模块注册/泛型/服务注册等;然后介绍了事件总线的工作流程:事件发布到总线中,通过消息队列触发注册到总线中的事件处理器处理事件消息;最后,我们利用Web API 展示了程序的运行过程。

  因为篇幅有限,代码中关于Storage的部分,涉及到了仓储的概念,我想到时放到领域设计部分一起介绍。

源码

  本篇涉及的源码在Github的https://github.com/ChuanGoing/Start.git  的SimpleEventBus分支可以找到。

Asp.net Core 系列之--1.事件驱动初探:简单事件总线实现(SimpleEventBus)的更多相关文章

  1. Asp.net Core 系列之--2.ORM初探:Dapper实现MySql数据库各类操作

    ChuanGoing 2019-09-10 距离上一篇近一个月时间,断断续续才把本篇码完,后面将加快进度,争取年度内把本系列基本介绍完成,同时督促本人持续学习. 本篇学习曲线: 1.初识Dapper ...

  2. Asp.net Core 系列之--4.事务、日志及错误处理

    ChuanGoing 2019-11-17 这篇原本时想把事务处理.日志处理.错误处理.授权于鉴权一并介绍完的,授权和鉴权我想结合自定义权限来介绍,全部放到这里篇幅可能太长,因此权限部分将会在下篇来介 ...

  3. Asp.net Core 系列之--3.领域、仓储、服务简单实现

    ChuanGoing 2019-11-11  距离上篇近两个月时间,一方面时因为其他事情耽搁,另一方面也是之前准备不足,关于领域驱动有几个地方没有想通透,也就没有继续码字.目前网络包括园子里大多领域驱 ...

  4. 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线

    重温.NET下Assembly的加载过程   最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后 ...

  5. Asp.net Core 系列之--5.认证、授权与自定义权限的实现

    ChuanGoing 2019-11-24 asp.net core系列已经来到了第五篇,通过之前的基础介绍,我们了解了事件订阅/发布的eventbus整个流程,初探dapper ORM实现,并且简单 ...

  6. 1.1专题介绍「深入浅出ASP.NET Core系列」

    大家好,我是IT人张飞洪,专注于.NET平台十年有余. 工作之余喜欢阅读和写作,学习的内容包括数据结构/算法.网络技术.Linux系统原理.数据库技术原理,设计模式.前沿架构.微服务.容器技术等等…… ...

  7. asp.net core系列 30 EF管理数据库架构--必备知识 迁移

    一.管理数据库架构概述 EF Core 提供两种主要方法来保持 EF Core 模型和数据库架构同步.一是以 EF Core 模型为基准,二是以数据库为基准. (1)如果希望以 EF Core 模型为 ...

  8. asp.net core系列 40 Web 应用MVC 介绍与详细示例

    一. MVC介绍 MVC架构模式有助于实现关注点分离.视图和控制器均依赖于模型. 但是,模型既不依赖于视图,也不依赖于控制器. 这是分离的一个关键优势. 这种分离允许模型独立于可视化展示进行构建和测试 ...

  9. asp.net core系列 39 Web 应用Razor 介绍与详细示例

    一. Razor介绍 在使用ASP.NET Core Web开发时, ASP.NET Core MVC 提供了一个新特性Razor. 这样开发Web包括了MVC框架和Razor框架.对于Razor来说 ...

随机推荐

  1. zend studio 13.6 导入项目及其他设置

    1. 先创建一个新的项目:file -> new -> project 2. 创建新项目之后,在左侧的项目目录上右键 -> import 到此导入项目, 完成! 设置编码utf-8 ...

  2. 02-12 Logistic(逻辑)回归

    目录 逻辑回归 一.逻辑回归学习目标 二.逻辑回归引入 三.逻辑回归详解 3.1 线性回归与逻辑回归 3.2 二元逻辑回归的假设函数 3.2.1 让步比 3.2.2 Sigmoid函数图像 3.3 二 ...

  3. 02-15 Logistic回归(鸢尾花分类)

    目录 Logistic回归(鸢尾花分类) 一.导入模块 二.获取数据 三.构建决策边界 四.训练模型 4.1 C参数与权重系数的关系 五.可视化 更新.更全的<机器学习>的更新网站,更有p ...

  4. 【JZOJ5248】花花的聚会

    Description 注意测试数据中道路是 到 的单向道路,与题面恰好相反. Input Output Sample Input 7 7 1 3 1 2 6 7 3 6 3 5 3 4 7 2 3 ...

  5. 文本分类(TFIDF/朴素贝叶斯分类器/TextRNN/TextCNN/TextRCNN/FastText/HAN)

    目录 简介 TFIDF 朴素贝叶斯分类器 贝叶斯公式 贝叶斯决策论的理解 极大似然估计 朴素贝叶斯分类器 TextRNN TextCNN TextRCNN FastText HAN Highway N ...

  6. java并发基础及原理

    java并发基础知识导图   一 java线程用法 1.1 线程使用方式 1.1.1 继承Thread类 继承Thread类的方式,无返回值,且由于java不支持多继承,继承Thread类后,无法再继 ...

  7. (图解)windows下自定义Eclipse主题

    之前用过一些编辑器如SublimeText,IDE如phpstorm,IDEA等:这些工具给我的感觉就是,除了给予这些工具应有的功能外,给予开发者更友好,更舒服的界面.Eclipse很早就开始使用了, ...

  8. mongoDB4.0数据库

    下载:https://www.mongodb.com/ 安装:略 注意:使用前修改bin目录下配置文件mongodb.cfg,删除最后一行的'mp'字段 1. 启动服务与终止服务 net start ...

  9. 那些你不知道的HTML知识,快来学习一下吧

    前言 HTML作为前端三大基础知识点之一,是每一个前端开发人员都要掌握的部分.今天这篇文章我们来看看一些平时不太会注意,却在面试时可能会问到的题目,来看看你都会吗? 如何使用div模拟实现textar ...

  10. Cocos Creator实现1024游戏,免费提供代码。

    效果预览 ​ 获取代码 私信或留言.  游戏介绍 ● Github上的代码,不能进行合并操作,修改以后,功能类似2048,空白块赏随机位置,生成2,可以往左.右.上.下滑动,数字会朝着指定方向运动,相 ...