Skip to content

Commit

Permalink
Replace assembly scanning for registration with codegeneration
Browse files Browse the repository at this point in the history
  • Loading branch information
ThorstenThiel authored and Enterprize1 committed Sep 18, 2024
1 parent a5653f8 commit bedbf72
Show file tree
Hide file tree
Showing 47 changed files with 999 additions and 184 deletions.
4 changes: 2 additions & 2 deletions src/Benchmark/Bench.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public void Setup()
{
var sc = new ServiceCollection();
sc.AddEventSourcing();
sc.AddPoliciesFrom(typeof(Bench).Assembly);
sc.AddPolicy<AllowAllPolicy>();

_sp = sc.BuildServiceProvider();
}
Expand Down Expand Up @@ -56,7 +56,7 @@ public void SetupHeavyRead()
{
var sc = new ServiceCollection();
sc.AddEventSourcing();
sc.AddPoliciesFrom(typeof(Bench).Assembly);
sc.AddPolicy<AllowAllPolicy>();

_sp = sc.BuildServiceProvider();

Expand Down
12 changes: 3 additions & 9 deletions src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,10 @@ namespace Fluss.PostgreSQL;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddPostgresEventSourcingRepository(this IServiceCollection services,
string connectionString, Assembly? upcasterSourceAssembly = null)
string connectionString)
{
ArgumentNullException.ThrowIfNull(services);

if (upcasterSourceAssembly is not null)
{
services
.AddUpcasters(upcasterSourceAssembly)
.AddHostedService<Upcaster>();
}

return services
.AddBaseEventRepository<PostgreSQLEventRepository>()
.AddFluentMigratorCore()
Expand All @@ -31,7 +24,8 @@ public static IServiceCollection AddPostgresEventSourcingRepository(this IServic
.AddLogging(lb => lb.AddFluentMigratorConsole())
.AddSingleton(new PostgreSQLConfig(connectionString))
.AddSingleton<Migrator>()
.AddHostedService(sp => sp.GetRequiredService<Migrator>());
.AddHostedService(sp => sp.GetRequiredService<Migrator>())
.AddHostedService<Upcaster>();
}
}

Expand Down
112 changes: 112 additions & 0 deletions src/Fluss.Regen/Generators/RegistrationSyntaxGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Text;
using Fluss.Regen.Helpers;
using Microsoft.CodeAnalysis.Text;

namespace Fluss.Regen.Generators;

public sealed class RegistrationSyntaxGenerator : IDisposable
{
private readonly string _moduleName;
private readonly string _ns;
private StringBuilder _sb;
private CodeWriter _writer;
private bool _disposed;

public RegistrationSyntaxGenerator(string moduleName, string ns)
{
_moduleName = moduleName;
_ns = ns;
_sb = StringBuilderPool.Get();
_writer = new CodeWriter(_sb);
}

public void WriteHeader()
{
_writer.WriteFileHeader();
_writer.WriteLine();
}

public void WriteBeginNamespace()
{
_writer.WriteIndentedLine("namespace {0} {{", _ns);
_writer.IncreaseIndent();
}

public void WriteEndNamespace()
{
_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");
}

public void WriteBeginClass()
{
_writer.WriteIndentedLine("public static partial class {0}ServiceCollectionExtensions {{", _moduleName);
_writer.IncreaseIndent();
}

public void WriteEndClass()
{
_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");
}

public void WriteBeginRegistrationMethod()
{
_writer.WriteIndentedLine(
"public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection Add{0}(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection sc) {{",
_moduleName);
_writer.IncreaseIndent();
}

public void WriteEndRegistrationMethod()
{
_writer.WriteIndentedLine("return sc;");
_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");
}

public void WriteAggregateValidatorRegistration(string aggregateValidatorType)
{
_writer.WriteIndentedLine("global::Fluss.Validation.ValidationServiceCollectionExtension.AddAggregateValidator<{0}>(sc);", aggregateValidatorType);
}

public void WriteEventValidatorRegistration(string eventValidatorType)
{
_writer.WriteIndentedLine("global::Fluss.Validation.ValidationServiceCollectionExtension.AddEventValidator<{0}>(sc);", eventValidatorType);
}

public void WritePolicyRegistration(string policyType)
{
_writer.WriteIndentedLine("global::Fluss.Authentication.ServiceCollectionExtensions.AddPolicy<{0}>(sc);", policyType);
}

public void WriteSideEffectRegistration(string sideEffectType)
{
_writer.WriteIndentedLine("global::Fluss.SideEffects.SideEffectsServiceCollectionExtension.AddSideEffect<{0}>(sc);", sideEffectType);
}

public void WriteUpcasterRegistration(string upcasterType)
{
_writer.WriteIndentedLine("global::Fluss.ServiceCollectionExtensions.AddUpcaster<{0}>(sc);", upcasterType);
}

public override string ToString()
=> _sb.ToString();

public SourceText ToSourceText()
=> SourceText.From(ToString(), Encoding.UTF8);

public void Dispose()
{
if (_disposed)
{
return;
}

StringBuilderPool.Return(_sb);
_sb = default!;
_writer = default!;
_disposed = true;
}
}
29 changes: 17 additions & 12 deletions src/Fluss.Regen/Generators/SelectorSyntaxGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,23 @@ public void WriteClassHeader()

public void WriteEndNamespace()
{
_writer.WriteIndentedLine("""
private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList<global::Fluss.UnitOfWorkRecordingProxy.EventListenerTypeWithKeyAndVersion>? EventListeners);
private static async ValueTask<bool> MatchesEventListenerState(IUnitOfWork unitOfWork, CacheEntryValue value) {
foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty<global::Fluss.UnitOfWorkRecordingProxy.EventListenerTypeWithKeyAndVersion>()) {
if (!(await eventListenerData.IsStillUpToDate(unitOfWork))) {
return false;
}
}

return true;
}
""");
_writer.WriteIndentedLine("private record CacheEntryValue(object? Value, global::System.Collections.Generic.IReadOnlyList<global::Fluss.UnitOfWorkRecordingProxy.EventListenerTypeWithKeyAndVersion>? EventListeners);");
_writer.WriteLine();
_writer.WriteIndented("private static async global::System.Threading.Tasks.ValueTask<bool> MatchesEventListenerState(global::Fluss.IUnitOfWork unitOfWork, CacheEntryValue value) ");
using (_writer.WriteBraces())
{
_writer.WriteIndented("foreach (var eventListenerData in value.EventListeners ?? global::System.Array.Empty<global::Fluss.UnitOfWorkRecordingProxy.EventListenerTypeWithKeyAndVersion>()) ");
using (_writer.WriteBraces())
{
_writer.WriteIndented("if (!await eventListenerData.IsStillUpToDate(unitOfWork)) ");
using (_writer.WriteBraces())
{
_writer.WriteIndentedLine("return false;");
}
}

_writer.WriteIndentedLine("return true;");
}

_writer.DecreaseIndent();
_writer.WriteIndentedLine("}");
Expand Down
32 changes: 32 additions & 0 deletions src/Fluss.Regen/Inspectors/AggregateValidatorInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Fluss.Regen.Inspectors;

public sealed class AggregateValidatorInfo(
INamedTypeSymbol classSymbol,
ClassDeclarationSyntax classSyntax)
: ISyntaxInfo
{
public INamedTypeSymbol Type { get; } = classSymbol;
private ClassDeclarationSyntax ClassSyntax { get; } = classSyntax;

public bool Equals(AggregateValidatorInfo? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return ClassSyntax.Equals(other.ClassSyntax);
}

public bool Equals(ISyntaxInfo? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other is AggregateValidatorInfo info && Equals(info);
}

public override bool Equals(object? obj) =>
ReferenceEquals(this, obj) || (obj is AggregateValidatorInfo other && Equals(other));

public override int GetHashCode() => ClassSyntax.GetHashCode();
}
29 changes: 29 additions & 0 deletions src/Fluss.Regen/Inspectors/AggregateValidatorInspector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Fluss.Regen.Inspectors;

public sealed class AggregateValidatorInspector : ISyntaxInspector
{
public bool TryHandle(
GeneratorSyntaxContext context,
[NotNullWhen(true)] out ISyntaxInfo? syntaxInfo)
{
if (context.Node is ClassDeclarationSyntax classSyntax)
{
var symbol = context.SemanticModel.GetDeclaredSymbol(classSyntax);
if (symbol is INamedTypeSymbol classSymbol &&
classSymbol.AllInterfaces.Any(i => i.ToDisplayString().StartsWith("Fluss.Validation.AggregateValidator")))
{
syntaxInfo = new AggregateValidatorInfo(classSymbol, classSyntax);
return true;
}
}

syntaxInfo = null;
return false;
}
}
32 changes: 32 additions & 0 deletions src/Fluss.Regen/Inspectors/EventValidatorInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Fluss.Regen.Inspectors;

public sealed class EventValidatorInfo(
INamedTypeSymbol classSymbol,
ClassDeclarationSyntax classSyntax)
: ISyntaxInfo
{
public INamedTypeSymbol Type { get; } = classSymbol;
private ClassDeclarationSyntax ClassSyntax { get; } = classSyntax;

public bool Equals(EventValidatorInfo? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return ClassSyntax.Equals(other.ClassSyntax);
}

public bool Equals(ISyntaxInfo? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other is EventValidatorInfo info && Equals(info);
}

public override bool Equals(object? obj) =>
ReferenceEquals(this, obj) || (obj is EventValidatorInfo other && Equals(other));

public override int GetHashCode() => ClassSyntax.GetHashCode();
}
29 changes: 29 additions & 0 deletions src/Fluss.Regen/Inspectors/EventValidatorInspector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Fluss.Regen.Inspectors;

public sealed class EventValidatorInspector : ISyntaxInspector
{
public bool TryHandle(
GeneratorSyntaxContext context,
[NotNullWhen(true)] out ISyntaxInfo? syntaxInfo)
{
if (context.Node is ClassDeclarationSyntax classSyntax)
{
var symbol = context.SemanticModel.GetDeclaredSymbol(classSyntax);
if (symbol is INamedTypeSymbol classSymbol &&
classSymbol.AllInterfaces.Any(i => i.ToDisplayString().StartsWith("Fluss.Validation.EventValidator<")))
{
syntaxInfo = new EventValidatorInfo(classSymbol, classSyntax);
return true;
}
}

syntaxInfo = null;
return false;
}
}
32 changes: 32 additions & 0 deletions src/Fluss.Regen/Inspectors/PolicyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Fluss.Regen.Inspectors;

public sealed class PolicyInfo(
INamedTypeSymbol classSymbol,
ClassDeclarationSyntax classSyntax)
: ISyntaxInfo
{
public INamedTypeSymbol Type { get; } = classSymbol;
private ClassDeclarationSyntax ClassSyntax { get; } = classSyntax;

public bool Equals(PolicyInfo? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return ClassSyntax.Equals(other.ClassSyntax);
}

public bool Equals(ISyntaxInfo? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other is PolicyInfo info && Equals(info);
}

public override bool Equals(object? obj) =>
ReferenceEquals(this, obj) || (obj is PolicyInfo other && Equals(other));

public override int GetHashCode() => ClassSyntax.GetHashCode();
}
29 changes: 29 additions & 0 deletions src/Fluss.Regen/Inspectors/PolicyInspector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Fluss.Regen.Inspectors;

public sealed class PolicyInspector : ISyntaxInspector
{
public bool TryHandle(
GeneratorSyntaxContext context,
[NotNullWhen(true)] out ISyntaxInfo? syntaxInfo)
{
if (context.Node is ClassDeclarationSyntax classSyntax)
{
var symbol = context.SemanticModel.GetDeclaredSymbol(classSyntax);
if (symbol is INamedTypeSymbol classSymbol &&
classSymbol.AllInterfaces.Any(i => i.ToDisplayString() == "Fluss.Authentication.Policy"))
{
syntaxInfo = new PolicyInfo(classSymbol, classSyntax);
return true;
}
}

syntaxInfo = null;
return false;
}
}
1 change: 0 additions & 1 deletion src/Fluss.Regen/Inspectors/SelectorInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,4 @@ public override int GetHashCode()
return hashCode;
}
}

}
Loading

0 comments on commit bedbf72

Please sign in to comment.