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. SoapUI 的几种常用参数化方式

    今天给大家来梳理下soapui这款工具关于参数化的几种方式以及具体的应用场景 1.properties 官方文档:https://www.soapui.org/docs/functional-test ...

  2. Web性能优化:雅虎35条

    对web性能优化,一直知道是个很重要的方面,平时有注意到,但是对于雅虎35条是第一次听说,查了一下,发现平时都有用过,只是没有总结到一块,今天就总结一下吧. 雅虎35条: 1.[内容]尽量减少HTTP ...

  3. python编程基础之三十一

    面向对象:一开始接触面向对象其实感觉不好用,但是对于一些复杂的问题,使用面向对象其实更加容易,逻辑不容易混乱 它的核心是:类 和 对象 类:对一系列事物的抽象概念,可以视为一张图纸, 对象:就是对类这 ...

  4. LeetCode_20-Valid Parentheses

    给定一个字符串,其中包含字符’(’,’)’,’[’,’]’,’{‘,’}’,左括号必须匹配右括号,一对匹配的括号不能单独出现单个左括号或者右括号.如:(()[])有效,[(])无效空字符串也算是有效的 ...

  5. springboot 自定义LocaleResolver切换语言

    springboot 自定义LocaleResolver切换语言 我们在做项目的时候,往往有很多项目需要根据用户的需要来切换不同的语言,使用国际化就可以轻松解决. 我们可以自定义springboor中 ...

  6. tcpdump使用详情及案例

    转载http://starsliao.blog.163.com/blog/static/89048201062333032563/ TCPdump抓包命令tcpdump是一个用于截取网络分组,并输出分 ...

  7. python学习(内置函数)

    1.id()返回对象的内存地址 a = 1 print id(a) print id(1) 2.int()用于将数据类型转换为整型 a = " b = 2 print int(a) + b ...

  8. Oier们的幸运数字

    题目描述 JerryC对数字痴迷到了一种非正常的境界.每天JerryC都有喜欢的一些数字.第 iii 天JerryC就喜欢Ai−BiA_i-B_iAi​−Bi​中的数字.但是他觉得这样并不是很有趣,于 ...

  9. [网络流 24 题] luoguP2763 试题库问题

    [返回网络流 24 题索引] 题目描述 假设一个试题库中有 nnn 道试题.每道试题都标明了所属类别.同一道题可能有多个类别属性.现要从题库中抽取 mmm 道题组成试卷.并要求试卷包含指定类型的试题. ...

  10. 8种常见的SQL错误用法

    常见SQL错误用法 1. LIMIT 语句 分页查询是最常用的场景之一,但也通常也是最容易出问题的地方.比如对于下面简单的语句,一般DBA想到的办法是在type, name, create_time字 ...