EventFlow
$ dotnet add package EventFlow
EventFlow是一个基本的CQRS+ES框架,旨在易于使用。
看看我们的入门指南,DO和NOTS和FAQ。
或者,加入我们的Discord服务器与社区互动。希望它可以重新启动,以启动即将发布的V1发行。
特征
- 易于使用:设计具有明智的默认和实现,使创建示例应用程序变得容易
- 高度可配置和可扩展的: EventFlow使用其核心每个部分的接口,从而易于使用自定义实现替换或扩展现有功能
- 不使用线程或背景工人
- 麻省理工学院许可易于理解和使用企业许可证
版本
1.0版的开发已经开始,并且主要是针对与Microsoft扩展抽象(主要是IserviceProvider和ilogger <>)替换EventFlow类型相关的更改的制动更改。
以下每个版本及其相关分支的关键特征(尚未正确配置)。
-
1.x
表示EventFlow的下一个迭代,该事件流将EventFlow与.NET的标准软件包对齐。在此处发布只会支持.NET标准,.NET Core和.NET版本6+。
- 发行
- 仍在发展
- 并非所有项目都迁移
阅读迁移指南,以查看打破变化的完整列表以及有关如何迁移的建议。
文档
版本1.x文档已将其拉到此存储库中,以使代码和文档更加关闭,并将文档更新以与任何代码更改相同的拉值更新。该文档的编译版本可在https://gete*ven**tflow.net/上获得。
Nuget软件包状态
- ?移植
- 新添加到1.0
- ?尚未移植到1.0
- ?对于作为1.0的一部分删除的软件包(有关详细信息,请参见《迁移指南》)
项目
- ? EventFlow
- EventFlow .aspnetcore
- EventFlow .autofac
- ? EventFlow 。依赖性
- EventFlow .elasticsearch
- EventFlow .EntityFramework
- EventFlow .EventStores.EventStore
- EventFlow .hangfire
- EventFlow .mongodb
- EventFlow .MSSQL
- EventFlow .OWIN
- EventFlow .postgresql
- EventFlow .Redis
- EventFlow .rabbitmq
- EventFlow .sql
- EventFlow .sqlite
- EventFlow .TestHelpers
分支
- 开发-V1:开发分支,拉力请求应在此处完成
- Release-V1:发行分支,合并提交是从开发-V1的该分支完成的,以创建发行版。通常每个提交代表发布
-
0.x(遗产)
当前稳定版本的EventFlow版本已有将近六年的EventFlow版本。 0.x版本通过额外的Nuget软件包具有.NET框架支持和对Microsoft扩展程序包的支持有限。
在对社区感兴趣的同时,仍将完成功能和错误修复版本。
分支
- 开发-V0:开发分支,拉力请求应在此处完成
- Release-V0:发行分支,合并提交是从开发-V0完成此分支以创建发行版的。通常每个提交代表发布
文档
版本0.x文档(尽管有点过时)在https://docs.ge*t*eventf*low.net/上进行。
与EventFlow直接相关的谈话
- Rasmus实用事件采购使用EventFlow ,Goto Aarhus 2022
例子
- 完成:显示了如何将EventFlow与内存事件存储一起使用的完整示例,并以相对较少的代码行读取模型
- 运输:要获得一个更完整的示例,说明如何使用EventFlow ,请查看代码库中此处找到的运输示例。该示例基于埃里克·埃文斯(Eric Evans)的“域驱动设计 – 解决软件中心的复杂性”一书中的运输示例。它的过程中,但应提供有关如何在更大范围内使用EventFlow灵感。如果您有想法和/或评论,请创建拉动请求或问题
外部示例
不同社区成员创建的示例列表。请注意,其中许多示例将使用EventFlow 0.x。
创建一个拉动请求,以从这里链接您的检查。
-
Recetimes:显示了EventFlow的某些功能,这些功能在完整的示例中未介绍。它具有实体,一个用于实体的读取模型,在读取模型,规格和快照上删除。
-
Azure功能的Recetime:扩展上述示例,以支持HTTP访问通过Azure函数
-
Azure功能和事件网格的Recetime:进一步扩展了Azure函数示例以发布到事件网格,遵循RabbitMQ模式
-
-
.NET CORE:使用事件流的Web API运行.NET CORE 2.2。它使用完整示例中的预定义命令/实体/事件。有终点可以创建一个新的示例事件,获取数据模型并重播所有数据模型。
-
Elasticsearch/.NET Core:它配置了EventFlow ,Elasticsearch,EventStore和RabbitMQ。有关#384,请参见“ withrabbitmq”分支。
-
车辆跟踪:以基于docker为基础的.NET CORE 2.2上的微服务,您可以使用Docker-Compose进行服务,该项目使用各种工具来提高服务。 Linux Docker基于.NET Core,RabbitMQ,带有SQL Server的EntityFramework以及使用cqrs-es架构之后的EventFlow ,所有微服务都可以通过使用Ocelot的Apigateway访问
-
RESTAIRLINE:基于EventFlow的HyperMedia API项目,具有CQRS-ES的经典DDD。它针对ASP.NET Core 2.2,可以部署到Docker和K8。
-
完整示例: .NET Core 2.2上的控制台应用程序。您可以使用Docker-Compose文件来提高服务。 Docker-Compose文件包括EventStore,RabbitMQ,MongoDB和PostgreSQL。它包括以下EventFlow概念:
- 聚合
- 命令巴士和命令
- 同步订户
- 活动商店(GES)
- 内存读取模型。
- 快照(mongodb)
- 萨加斯
- 事件出版(内存,兔子)
- 元数据
- 命令总线装饰器,自定义值对象,自定义执行结果,…
概述
这是EventFlow概念的列表。使用链接导航到文档。
- 聚合:域对象,以保证每个聚合中进行更改的一致性
- 命令总线和命令:所有命令/操作执行的入口点。
-
事件存储:聚合的事件流的存储。当前有对这些存储类型的支持。
- 内存 – 仅用于测试
- 文件 – 仅用于测试
- Microsoft SQL Server
- 实体框架核心
- sqlite
- Postgresql
- EventStore-主页
- 订户:对特定领域事件行动的听众。如果需要在域事件进行后需要触发特定的操作,则有用。
-
读取模型:针对快速阅读的聚合事件的典型化表示。当前有对这些读取模型存储类型的支持。对于SQL存储类型,查询是通过引用的列和表名称自动生成的。
- Elasticsearch
- 内存 – 仅用于测试
- Microsoft SQL Server
- 实体框架核心
- sqlite
- Postgresql
-
快照:可以每次都可以创建一个快照,而不是每次读取整个事件流,而是包含聚集状态的每隔一段时间。 EventFlow支持升级现有快照,这对于长寿命骨料很有用。 EventFlow中的快照是选择加入的, EventFlow具有支持
- 内存 – 仅用于测试
- Microsoft SQL Server
- 实体框架核心
- sqlite
- Postgresql
- 传说:也称为过程管理者,坐标和路由有限上下文和聚合之间的消息
- 查询:代表查询的值的值,而无需指定其执行的执行方式,请询问查询处理程序
-
作业:以后执行计划的任务,例如发布命令。 EventFlow为这些工作调度程序提供支持
- Hangfire-主页
- 事件升级:随着致力于活动商店的事件从未更改, EventFlow使用事件升级器的概念来贬低事件并在汇总负载期间将其替换为新事件。
-
事件发布:有时您希望其他应用程序或服务在域上消费并采取行动。对于此EventFlow支持事件发布。
- 兔子
- 元数据:每个聚合事件的其他信息,例如要发射的事件背后的用户IP。 EventFlow船与几个准备使用的提供商。
- 值对象:包含用于验证和保存域数据的类的数据,例如用户名或电子邮件。
完整的示例
这是一个完整的示例,讲述了如何使用默认内存事件商店以及内存读取模型。
该示例包括以下类,每个类别如下所示
- estepleaggregate:骨料根
- 示例ID:代表聚集根的身份的值对象
- 示例event:聚集根发射的事件
- extplecommand:值对象定义可以发布到聚集根的命令
- extplecommandhandler:命令处理程序使用其IOC容器解决哪个EventFlow并定义如何将命令特定应用于聚合根
- ExipplereadModel:内存读取模型,可轻松访问当前状态
注意:此示例是EventFlow测试套件的一部分,因此请查看代码并尝试一下。
EventFlow with all of our classes. Instead of adding events,
// commands, etc. explicitly, we could have used the the simpler
// AddDefaults(Assembly) instead.
var serviceCollection = new ServiceCollection()
.AddLogging()
.Add EventFlow (o => o
.AddEvents(typeof(ExampleEvent))
.AddCommands(typeof(ExampleCommand))
.AddCommandHandlers(typeof(ExampleCommandHandler))
.UseInMemoryReadStoreFor<ExampleReadModel>());
using (var serviceProvider = serviceCollection.BuildServiceProvider())
{
// Create a new identity for our aggregate root
var exampleId = ExampleId.New;
// Resolve the command bus and use it to publish a command
var commandBus = serviceProvider.GetRequiredService<ICommandBus>();
await commandBus.PublishAsync(
new ExampleCommand(exampleId, 42), CancellationToken.None);
// Resolve the query handler and use the built-in query for fetching
// read models by identity to get our read model representing the
// state of our aggregate root
var queryProcessor = serviceProvider.GetRequiredService<IQueryProcessor>();
var exampleReadModel = await queryProcessor.ProcessAsync(
new ReadModelByIdQuery<ExampleReadModel>(exampleId), CancellationToken.None);
// Verify that the read model has the expected magic number
exampleReadModel.MagicNumber.Should().Be(42);
}
}\”>
[ Test ] public async Task Example ( ) { // We wire up EventFlow with all of our classes. Instead of adding events, // commands, etc. explicitly, we could have used the the simpler // AddDefaults(Assembly) instead. var serviceCollection = new ServiceCollection ( ) . AddLogging ( ) . Add EventFlow ( o => o . AddEvents ( typeof ( ExampleEvent ) ) . AddCommands ( typeof ( ExampleCommand ) ) . AddCommandHandlers ( typeof ( ExampleCommandHandler ) ) . UseInMemoryReadStoreFor < ExampleReadModel > ( ) ) ; using ( var serviceProvider = serviceCollection . BuildServiceProvider ( ) ) { // Create a new identity for our aggregate root var exampleId = ExampleId . New ; // Resolve the command bus and use it to publish a command var commandBus = serviceProvider . GetRequiredService < ICommandBus > ( ) ; await commandBus . PublishAsync ( new ExampleCommand ( exampleId , 42 ) , CancellationToken . None ) ; // Resolve the query handler and use the built-in query for fetching // read models by identity to get our read model representing the // state of our aggregate root var queryProcessor = serviceProvider . GetRequiredService < IQueryProcessor > ( ) ; var exampleReadModel = await queryProcessor . ProcessAsync ( new ReadModelByIdQuery < ExampleReadModel > ( exampleId ) , CancellationToken . None ) ; // Verify that the read model has the expected magic number exampleReadModel . MagicNumber . Should ( ) . Be ( 42 ) ; } }
EventFlow
// provides several different methods for doing this, e.g. state objects,
// the Apply method is merely the simplest
public void Apply(ExampleEvent aggregateEvent)
{
_magicNumber = aggregateEvent.MagicNumber;
}
}\”>
// The aggregate root public class ExampleAggregate : AggregateRoot < ExampleAggregate , ExampleId > , IEmit < ExampleEvent > { private int ? _magicNumber ; public ExampleAggregate ( ExampleId id ) : base ( id ) { } // Method invoked by our command public void SetMagicNumber ( int magicNumber ) { if ( _magicNumber . HasValue ) throw DomainError . With ( \"Magic number already set\" ) ; Emit ( new ExampleEvent ( magicNumber ) ) ; } // We apply the event as part of the event sourcing system. EventFlow // provides several different methods for doing this, e.g. state objects, // the Apply method is merely the simplest public void Apply ( ExampleEvent aggregateEvent ) { _magicNumber = aggregateEvent . MagicNumber ; } }
// Represents the aggregate identity (ID) public class ExampleId : Identity < ExampleId > { public ExampleId ( string value ) : base ( value ) { } }
// A basic event containing some information public class ExampleEvent : AggregateEvent < ExampleAggregate , ExampleId > { public ExampleEvent ( int magicNumber ) { MagicNumber = magicNumber ; } public int MagicNumber { get ; } }
// Command for update magic number public class ExampleCommand : Command < ExampleAggregate , ExampleId > { public ExampleCommand ( ExampleId aggregateId , int magicNumber ) : base ( aggregateId ) { MagicNumber = magicNumber ; } public int MagicNumber { get ; } }
// Command handler for our command public class ExampleCommandHandler : CommandHandler < ExampleAggregate , ExampleId , ExampleCommand > { public override Task ExecuteAsync ( ExampleAggregate aggregate , ExampleCommand command , CancellationToken cancellationToken ) { aggregate . SetMagicNumber ( command . MagicNumber ) ; return Task . CompletedTask ; ; } }
// Read model for our aggregate public class ExampleReadModel : IReadModel , IAmReadModelFor < ExampleAggregate , ExampleId , ExampleEvent > { public int MagicNumber { get ; private set ; } public Task ApplyAsync ( IReadModelContext context , IDomainEvent < ExampleAggregate , ExampleId , ExampleEvent > domainEvent , CancellationToken _cancellationToken { MagicNumber = domainEvent . AggregateEvent . MagicNumber ; return Task . CompletedTask ; } }
EventFlow的状态
EventFlow仍在开发中,尤其是有关读取模型如何重新填充的部分。
EventFlow目前用于生产环境中,并且性能很好,但是在关键API稳定之前需要成熟。
EventFlow被极大地有用,但是可以通过注册不同的接口实现来为EventFlow的每个部分创建新的实现。
与EventFlow和DDD有关的有用文章
EventFlow中的许多技术设计决策都是基于文章。本节列出了其中一些。如果您与相关文章有链接,请通过与链接创建问题来分享。
-
域驱动的设计
- 埃里克·埃文斯(Eric Evans)由域驱动的设计参考
- DDD解码 – 有限上下文解释了
- 通过事件暴风雨和DDD进行微服务的“赛事优先”
-
一般CQRS+ES
- Microsoft出版的Microsoft的CQRS旅程
- 深入了解Mike Mogosanu的CQRS
- CQR,基于任务的UI,事件采购AGH!格雷格·杨(Greg Young)
- 破坏吉米·鲍加德(Jimmy Bogard)的一些CQRS神话
- 加布里埃尔·申克(Gabriel Schenker)应用的CQR
- DDD解码 – 解释的实体和价值对象
-
最终的一致性
- 如何确保Mike Mogosanu最终一致的DDD/CQRS应用程序中的势力
- DDD解码 – 不要担心最终的一致性
-
为什么不在DDD中实现“工作单位”
- 工作单位是Mike Mogosanu的新Singleton
- Mike Mogosanu的工作和交易单位
集成测试
EventFlow具有多个测试,可以验证其使用与其正确集成的系统的能力。
- Elasticsearch
- 事件店
- 兔子
- Microsoft SQL Server
- Postgresql
要设置本地测试环境,请在EventFlow的结帐目录中运行以下命令。
docker-compose pull docker-compose up
另外,您可以跳过以集成类别标记的Nunit测试。
谢谢
- 贡献者
- Jetbrains:OSS许可证
- Olholm:当前徽标
- IconMonstr:第一个徽标
- JC008:SQLite的Navicat Essentials许可证
执照
EventFlow最初是在我在eBay(2015年至2021年)和Schibsted(2021年及以前)工作的业余时间开发的。
EventFlow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the \”Software\”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED \”AS IS\”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.\”>
The MIT License (MIT) Copyright (c) 2015-2025 Rasmus Mikkelsen https://github**.co*m/EventFlow/ EventFlow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
