Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 1.添加领域事件转换为集成事件的转换器;2.自动生成领域事件订阅和集成事件发布代码 #56

Merged
merged 9 commits into from
Aug 31, 2024
33 changes: 33 additions & 0 deletions docs/content/domain/integration-converter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 集成事件转换器

集成事件转换器将领域事件发送成集成事件的转换工具。

通过集成事件转换器可以将要发送集成事件与其他领域事件处理器中业务逻辑解耦。

## 定义集成事件转换器

1. 安装nuget包 `NetCorePal.Extensions.DistributedTransactions.Abstractions`

```bash
dotnet add package NetCorePal.Extensions.DistributedTransactions.Abstractions
```

2. 定义集成事件转换器,需要:

+ 继承`NetCorePal.Extensions.DistributedTransactions.IIntegrationEventConverter`接口;
+ 实现IIntegrationEventConverter接口的Convert方法,该方法入参为领域事件IDomainEvent,返参为集成事件IntegrationEvent

​ 下面为一个示例:

```c#
// 定义集成事件转换器
using NetCorePal.Web.Application.IntegrationConvert;
namespace YourNamespace;

public class OrderCreatedIntegrationEventConverter : IIntegrationEventConverter<OrderCreatedDomainEvent,OrderPaidIntegrationEvent>{
public OrderPaidIntegrationEvent Convert(OrderCreatedDomainEvent domainEvent)
{
return new OrderPaidIntegrationEvent(domainEvent.Order.Id);
}
}
```
witskeeper marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ nav:
- 强类型ID: domain/strong-typed-id.md
- 领域模型: domain/domain-entity.md
- 领域事件: domain/domain-event.md
- 集成事件转换器: domain/integration-converter.md
# - 值对象: domain/domain-value-object.md
- ID生成器:
- Snowflake: id-generator/snowflake.md
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using NetCorePal.Extensions.Domain;

namespace NetCorePal.Extensions.DistributedTransactions;

public interface IIntegrationEventConverter<in TDomainEvent, out TIntegrationEvent>
where TDomainEvent : IDomainEvent
where TIntegrationEvent : notnull
{
public TIntegrationEvent Convert(TDomainEvent domainEvent);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ namespace NetCorePal.Extensions.DependencyInjection
{
public static class ServiceCollectionExtensions
{

/// <summary>
/// 存储注册的IIntegrationEventConverter类型
/// </summary>
public static IIntegrationEventServicesBuilder AddIIntegrationEventConverter( this IIntegrationEventServicesBuilder builder,
params Type[] typeFromAssemblies)
{
var types = typeFromAssemblies.Select(p => p.Assembly).SelectMany(assembly => assembly.GetTypes());
var handlers = types.Where(t =>
t is { IsClass: true, IsAbstract: false } && Array.Exists(t.GetInterfaces(), p =>
p.IsGenericType && p.GetGenericTypeDefinition() == typeof(IIntegrationEventConverter<,>)));
foreach (var handler in handlers)
{
builder.Services.TryAddScoped(handler);
}

return builder;
}

/// <summary>
/// 存储注册的EventHandler类型
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace NetCorePal.Extensions.DistributedTransactions.CAP.SourceGenerators
{
[Generator]
public class CapIntegrationConvertDomainEventHandlerSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.RootNamespace",
out var rootNamespace);
if (rootNamespace == null)
{
return;
}

var compilation = context.Compilation;
foreach (var syntaxTree in compilation.SyntaxTrees)
{
if (syntaxTree.TryGetText(out var sourceText) &&
!sourceText.ToString().Contains("IIntegrationEventConvert"))
{
continue;
}

var semanticModel = compilation.GetSemanticModel(syntaxTree);
if (semanticModel == null)
{
continue;
}

var typeDeclarationSyntaxs =
syntaxTree.GetRoot().DescendantNodesAndSelf().OfType<TypeDeclarationSyntax>();
foreach (var tds in typeDeclarationSyntaxs)
{
var symbol = semanticModel.GetDeclaredSymbol(tds);
if (symbol is not INamedTypeSymbol) return;
INamedTypeSymbol namedTypeSymbol = (INamedTypeSymbol)symbol;
if (!namedTypeSymbol.IsImplicitClass &&
namedTypeSymbol.AllInterfaces.Any(p => p.Name == "IIntegrationEventConvert"))
{
Generate(context, namedTypeSymbol, rootNamespace);
}
}
}
}


private void Generate(GeneratorExecutionContext context, INamedTypeSymbol integrationConvertTypeSymbol,
string rootNamespace)
{
string className = integrationConvertTypeSymbol.Name;

//根据dbContextType继承的接口IIntegrationEventHandle<TIntegrationEvent> 推断出TIntegrationEvent类型
var convertNamespace = integrationConvertTypeSymbol.ContainingNamespace.ToString();
var usingNamespace = integrationConvertTypeSymbol.ContainingNamespace.ContainingNamespace.ToString();

var iinterface = integrationConvertTypeSymbol.AllInterfaces
.FirstOrDefault(i => i.Name == "IIntegrationEventConvert");
if (iinterface == null)
{
return;
}

var domainEvent = iinterface.TypeArguments[0].Name;

string source = $@"// <auto-generated/>
using {convertNamespace};
using NetCorePal.Extensions.DistributedTransactions;
using NetCorePal.Extensions.Domain;
using {rootNamespace};

namespace {usingNamespace}.DomainEventHandlers
{{
/// <summary>
/// {className}DomainEventHandlers
/// </summary>
public class {className}DomainEventHandler(IIntegrationEventPublisher integrationEventPublisher,
{className} convert) : IDomainEventHandler<{domainEvent}>
santubeikawhi marked this conversation as resolved.
Show resolved Hide resolved
{{
/// <summary>
/// {className}DomainEventHandler
/// </summary>
/// <param name=""notification"">notification</param>
/// <param name=""cancellationToken"">cancellationToken</param>
public async Task Handle({domainEvent} notification, CancellationToken cancellationToken){{
// 发出转移操作集成事件
var integrationEvent = convert.Convert(notification);
await integrationEventPublisher.PublishAsync(integrationEvent, cancellationToken);
}}

}}
}}
";
context.AddSource($"{className}DomainEventHandler.g.cs", source);
}

public void Initialize(GeneratorInitializationContext context)
{
// Method intentionally left empty.
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using NetCorePal.Extensions.DistributedTransactions;
using NetCorePal.Web.Application.IntegrationEventHandlers;

namespace NetCorePal.Web.Application.IntegrationConvert;

/// <summary>
/// OrderCreatedIntegrationConvert
/// </summary>
public class OrderCreatedIntegrationEventConverter : IIntegrationEventConverter<OrderCreatedDomainEvent,OrderPaidIntegrationEvent>
{
/// <summary>
///
/// </summary>
/// <param name="domainEvent"></param>
/// <returns></returns>
public OrderPaidIntegrationEvent Convert(OrderCreatedDomainEvent domainEvent)
{
return new OrderPaidIntegrationEvent(domainEvent.Order.Id);
}
}
3 changes: 2 additions & 1 deletion test/NetCorePal.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
#endregion

builder.Services.AddScoped<OrderQuery>();

#region 基础设施

builder.Services.AddContext().AddEnvContext().AddCapContextProcessor();
Expand All @@ -108,6 +108,7 @@
builder.Services.AddMySqlTransactionHandler();
builder.Services.AddIntegrationEventServices(typeof(Program))
.UseCap(typeof(Program))
.AddIIntegrationEventConverter(typeof(Program))
.AddContextIntegrationFilters()
.AddEnvIntegrationFilters();
//.AddTransactionIntegrationEventHandlerFilter();
Expand Down
4 changes: 4 additions & 0 deletions test/NetCorePal.Web/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"profiles": {
"Generators": {
"commandName": "DebugRoslynComponent",
"targetProject": "../NetCorePal.Web/NetCorePal.Web.csproj"
},
"http": {
"commandName": "Project",
"launchBrowser": true,
Expand Down
Loading