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. ELK日志分析系统(4)-elasticsearch数据存储

    1. 概述 logstash把格式化的数据发送到elasticsearch以后,elasticsearch负责存储搜索日志数据 elasticsearch的搜索接口还是很强大的,这边不详细展开,因为k ...

  2. SpringMVC流程源码分析及DispatcherServlet核心源码

    一.源码分析前还是需要一张流程图作为指导,如下: 二.简单介绍以及源码定位 DispatcherServlet其实就是一个HttpServlet,他是HttpServlet的子类,所以它和普通的Htt ...

  3. Linux快速入门

    一.Linux介绍 1.Linux是基于Unix的开源免费的操作系统 2.Linux的分类: (1)Linux根据市场需求不同,基本分为两个方向: 1)图形化界面版:注重用户体验,类似window操作 ...

  4. SystemVerilog Assertion 设计、调试、测试总结(2)

    上一篇博客主要写了SVA的基本语法(详细),这一篇主要写SVA语法总结,以及如何查看SVA波形等. 断言assertion被放在verilog设计中,方便在仿真时查看异常情况.当异常出现时,断言会报警 ...

  5. web项目中登陆超时的功能实现(基于C#)

    当我们登陆进网站后,中途去看别的东西,没有再与该网站的服务器交互,就会弹出一个js窗口,登陆超时请重新登陆,并跳转到登陆页面. 步骤1.实现原理,在web.config中配置session的超时时间, ...

  6. PHP list的赋值

    List右边的赋值对象是一个以数值为索引的数组,左边的变量的位置和赋值对象的键值一一对应,有些位置的变量可以省略不写.非末尾的被赋值变量省略时,分隔的逗号不能省略.左边变量被赋值的顺序是从右到左的. ...

  7. Unix 线程共享创建进程打开的文件资源(1)

    执行环境:Linux ubuntu 4.4.0-31-generic #50-Ubuntu SMP Wed Jul 13 00:07:12 UTC 2016 x86_64 x86_64 x86_64 ...

  8. [Luogu2879][USACO07JAN]区间统计Tallest Cow

    题目描述 FJ's N (1 ≤ N ≤ 10,000) cows conveniently indexed 1..N are standing in a line. Each cow has a p ...

  9. Halcon一日一练:获取程序运行时间

    很多时候,我们需要知道每个函数的运算周期,以提高程序的运行效率.知道运行时间对于图像算法处理很重要 Halcon提供相关的算子,我们先来看代码: **获取图像处理时间 read_image(Image ...

  10. std::this_thread::yield/sleep_for

    std::this_thread::yield(): 当前线程放弃执行,操作系统调度另一线程继续执行.. std::this_thread::sleep_for(): 表示当前线程休眠一段时间,休眠期 ...