From 931aa6c15b155615b1efa16de87e7f22247fdcae Mon Sep 17 00:00:00 2001 From: Enterprize1 Date: Thu, 29 Aug 2024 21:37:10 +0200 Subject: [PATCH] Remove net7.0 support (#6) --- .../AddExtensionMiddleware.cs | 49 +++---- .../AutoGenerateSchema/AutoGenerateSchema.cs | 76 ++--------- .../Fluss.HotChocolate.csproj | 11 +- src/Fluss.HotChocolate/NewEventNotifier.cs | 6 +- .../NewTransientEventNotifier.cs | 4 +- .../UnitOfWorkParameterExpressionBuilder.cs | 12 +- src/Fluss.PostgreSQL/Fluss.PostgreSQL.csproj | 21 +-- .../PostgreSQLEventRepository.cs | 22 ++-- .../PostgreSQLEventRepositorySubscriptions.cs | 4 +- .../ServiceCollectionExtensions.cs | 51 ++------ .../Attributes/SelectorAttribute.cs | 22 ++-- src/Fluss.Regen/Fluss.Regen.csproj | 3 +- src/Fluss.Regen/Inspectors/ISyntaxInfo.cs | 4 +- src/Fluss.Regen/Inspectors/SelectorInfo.cs | 34 ++--- .../Inspectors/SelectorInspector.cs | 1 - src/Fluss.Regen/Readme.md | 28 +++- src/Fluss.Testing/AggregateTestBed.cs | 11 +- src/Fluss.Testing/CanaryEvent.cs | 5 +- src/Fluss.Testing/EventRepositoryTestBase.cs | 10 +- src/Fluss.Testing/EventTestBed.cs | 7 +- src/Fluss.Testing/Fluss.Testing.csproj | 9 +- src/Fluss.Testing/PolicyTestBed.cs | 26 ++-- src/Fluss.Testing/ReadModelTestBed.cs | 7 +- .../ArbitraryUserUnitOfWorkExtensionTest.cs | 2 +- .../Core/Authentication/AuthContextTest.cs | 12 +- .../Core/Events/EventListenerFactoryTest.cs | 21 +-- .../Core/Events/InMemoryCacheTest.cs | 4 +- .../Events/InMemoryEventRepositoryTest.cs | 2 +- .../Core/Events/InMemoryListenerCacheTest.cs | 11 +- .../Core/Extensions/ValueTaskTest.cs | 57 -------- .../Core/SideEffects/DispatcherTest.cs | 18 ++- ...sientEventAwareEventListenerFactoryTest.cs | 8 +- .../TransientEventAwareEventRepositoryTest.cs | 36 +----- .../UnitOfWorkAndAuthorizationTest.cs | 12 +- .../Core/UnitOfWork/UnitOfWorkTest.cs | 36 +++--- .../Upcasting/EventUpcasterServiceTest.cs | 35 ++--- .../Core/Upcasting/UpcasterSorterTest.cs | 12 +- .../Core/Validation/RootValidatorTests.cs | 18 +-- src/Fluss.UnitTest/Fluss.UnitTest.csproj | 22 ++-- .../Regen/SelectorGeneratorTests.cs | 122 +++++++++--------- src/Fluss.UnitTest/Setup.cs | 2 +- src/Fluss.sln.DotSettings.user | 2 + .../ArbitraryUserUnitOfWorkExtension.cs | 10 +- src/Fluss/Authentication/AuthContext.cs | 21 +-- src/Fluss/Authentication/UserIdProvider.cs | 13 +- src/Fluss/Events/EventListener.cs | 14 +- src/Fluss/Events/EventListenerFactory.cs | 11 +- .../Events/EventMemoryArrayExtensions.cs | 10 +- src/Fluss/Events/EventRepository.cs | 4 +- src/Fluss/Events/InMemoryEventCache.cs | 22 ++-- src/Fluss/Events/InMemoryEventRepository.cs | 2 +- .../Events/TransientEvents/TransientEvent.cs | 8 +- ...TransientEventAwareEventListenerFactory.cs | 12 +- .../TransientEventAwareEventRepository.cs | 8 +- src/Fluss/Exceptions/RetryException.cs | 5 +- src/Fluss/Extensions/ValueTaskExtensions.cs | 42 ------ src/Fluss/Fluss.csproj | 19 +-- src/Fluss/Fluss.csproj.DotSettings | 4 +- src/Fluss/ReadModel/ReadModel.cs | 8 +- src/Fluss/ServiceCollectionExtensions.cs | 5 +- src/Fluss/SideEffects/SideEffect.cs | 4 +- src/Fluss/SideEffects/SideEffectDispatcher.cs | 10 +- src/Fluss/UnitOfWork/IWriteUnitOfWork.cs | 2 - src/Fluss/UnitOfWork/UnitOfWork.Aggregates.cs | 10 +- src/Fluss/UnitOfWork/UnitOfWork.ReadModels.cs | 2 +- src/Fluss/UnitOfWork/UnitOfWorkFactory.cs | 13 +- .../UnitOfWork/UnitOfWorkRecordingProxy.cs | 35 ++--- src/Fluss/Upcasting/EventUpcasterService.cs | 28 ++-- src/Fluss/Upcasting/IUpcaster.cs | 8 +- src/Fluss/Upcasting/UpcasterSorter.cs | 18 +-- src/Fluss/Validation/AggregateValidator.cs | 2 +- src/Fluss/Validation/EventValidator.cs | 2 +- src/Fluss/Validation/RootValidator.cs | 11 +- 73 files changed, 440 insertions(+), 778 deletions(-) delete mode 100644 src/Fluss.UnitTest/Core/Extensions/ValueTaskTest.cs delete mode 100644 src/Fluss/Extensions/ValueTaskExtensions.cs diff --git a/src/Fluss.HotChocolate/AddExtensionMiddleware.cs b/src/Fluss.HotChocolate/AddExtensionMiddleware.cs index d5ba394..e4d3fc1 100644 --- a/src/Fluss.HotChocolate/AddExtensionMiddleware.cs +++ b/src/Fluss.HotChocolate/AddExtensionMiddleware.cs @@ -8,31 +8,20 @@ namespace Fluss.HotChocolate; -public class AddExtensionMiddleware +public class AddExtensionMiddleware( + RequestDelegate next, + IServiceProvider rootServiceProvider, + ILogger logger) { private const string SubsequentRequestMarker = nameof(AddExtensionMiddleware) + ".subsequentRequestMarker"; - private readonly RequestDelegate _next; - - private readonly IServiceProvider _rootServiceProvider; - private readonly ILogger _logger; - - public AddExtensionMiddleware( - RequestDelegate next, - IServiceProvider rootServiceProvider, - ILogger logger - ) - { - _next = next ?? throw new ArgumentNullException(nameof(next)); - _rootServiceProvider = rootServiceProvider; - _logger = logger; - } + private readonly RequestDelegate _next = next ?? throw new ArgumentNullException(nameof(next)); public async ValueTask InvokeAsync(IRequestContext context) { await _next.Invoke(context); - if (!context.ContextData.ContainsKey(nameof(UnitOfWork))) + if (!context.ContextData.TryGetValue(nameof(UnitOfWork), out var unitOfWork)) { return; } @@ -50,7 +39,7 @@ public async ValueTask InvokeAsync(IRequestContext context) if (context.Result is QueryResult subsequentQueryResult) { context.Result = QueryResultBuilder.FromResult(subsequentQueryResult).AddContextData(nameof(UnitOfWork), - context.ContextData[nameof(UnitOfWork)]).Create(); +unitOfWork).Create(); } return; @@ -69,12 +58,12 @@ private async IAsyncEnumerable LiveResults(IReadOnlyDictionary LiveResults(IReadOnlyDictionary : ScalarType - where TId : struct where TScalarType : ScalarType where TNodeType : IValueNode +public abstract class StronglyTypedIdType(TScalarType scalarType) + : ScalarType(typeof(TId).Name) + where TId : struct + where TScalarType : ScalarType + where TNodeType : IValueNode { - private readonly TScalarType scalarType; - - protected StronglyTypedIdType(TScalarType scalarType) : base(typeof(TId).Name) - { - this.scalarType = scalarType; - } - protected override TId ParseLiteral(TNodeType valueSyntax) { var guid = (TCLRType)scalarType.ParseLiteral(valueSyntax)!; @@ -98,58 +93,9 @@ public override bool TrySerialize(object? runtimeValue, out object? resultValue) } } -public class StronglyTypedGuidIdType : StronglyTypedIdType where TId : struct -{ - public StronglyTypedGuidIdType() : base(new UuidType('D')) { } -} - -public class StronglyTypedLongIdType : StronglyTypedIdType where TId : struct -{ - public StronglyTypedLongIdType() : base(new LongType()) { } -} - -public class StronglyTypedIdFilterConventionExtension : FilterConventionExtension -{ - protected override void Configure(IFilterConventionDescriptor descriptor) - { - base.Configure(descriptor); - - var typesToGenerateFor = typeof(TAssemblyReference).Assembly.GetTypes().Where(t => - t.IsValueType && t.CustomAttributes.Any(a => - a.AttributeType == typeof(TypeConverterAttribute))); - +public class StronglyTypedGuidIdType() + : StronglyTypedIdType(new UuidType('D')) + where TId : struct; - foreach (var type in typesToGenerateFor) - { - var filterInputType = typeof(StronglyTypedGuidIdFilterInput<>).MakeGenericType(type); - var nullableType = typeof(Nullable<>).MakeGenericType(type); - descriptor.BindRuntimeType(type, filterInputType); - descriptor.BindRuntimeType(nullableType, filterInputType); - } - } -} - -public class StronglyTypedGuidIdFilterInput : StringOperationFilterInputType -{ - /*public override bool TrySerialize(object? runtimeValue, out object? resultValue) { - if (runtimeValue is TId id) { - resultValue = id.ToString(); - return true; - } - - resultValue = null; - return false; - } - - public override bool TryDeserialize(object? resultValue, out object? runtimeValue) { - var canParseGuid = Guid.TryParse(resultValue?.ToString(), out var parsedGuid); - if (!canParseGuid) { - runtimeValue = null; - return false; - } - - var tId = Activator.CreateInstance(typeof(TId), parsedGuid); - runtimeValue = tId; - return true; - }*/ -} +public class StronglyTypedLongIdType() : StronglyTypedIdType(new LongType()) + where TId : struct; diff --git a/src/Fluss.HotChocolate/Fluss.HotChocolate.csproj b/src/Fluss.HotChocolate/Fluss.HotChocolate.csproj index df513f9..18716fe 100644 --- a/src/Fluss.HotChocolate/Fluss.HotChocolate.csproj +++ b/src/Fluss.HotChocolate/Fluss.HotChocolate.csproj @@ -1,19 +1,20 @@  - net8.0; net7.0 + net8.0 enable enable + true - - - + + + - + diff --git a/src/Fluss.HotChocolate/NewEventNotifier.cs b/src/Fluss.HotChocolate/NewEventNotifier.cs index 80dc82f..b26ff8c 100644 --- a/src/Fluss.HotChocolate/NewEventNotifier.cs +++ b/src/Fluss.HotChocolate/NewEventNotifier.cs @@ -1,17 +1,16 @@ using Fluss.Events; -using Fluss.Extensions; namespace Fluss.HotChocolate; public class NewEventNotifier { private long _knownVersion; - private readonly List<(long startedAtVersion, SemaphoreSlim semaphoreSlim)> _listeners = new(); + private readonly List<(long startedAtVersion, SemaphoreSlim semaphoreSlim)> _listeners = []; private readonly SemaphoreSlim _newEventAvailable = new(0); public NewEventNotifier(IBaseEventRepository eventRepository) { - _knownVersion = eventRepository.GetLatestVersion().GetResult(); + _knownVersion = eventRepository.GetLatestVersion().AsTask().Result; eventRepository.NewEvents += EventRepositoryOnNewEvents; _ = Task.Run(async () => @@ -41,6 +40,7 @@ public NewEventNotifier(IBaseEventRepository eventRepository) } } } + // ReSharper disable once FunctionNeverReturns }); } diff --git a/src/Fluss.HotChocolate/NewTransientEventNotifier.cs b/src/Fluss.HotChocolate/NewTransientEventNotifier.cs index ff60976..844b0f2 100644 --- a/src/Fluss.HotChocolate/NewTransientEventNotifier.cs +++ b/src/Fluss.HotChocolate/NewTransientEventNotifier.cs @@ -1,10 +1,11 @@ using Fluss.Events.TransientEvents; +// ReSharper disable LoopCanBeConvertedToQuery namespace Fluss.HotChocolate; public class NewTransientEventNotifier { - private readonly List<(long startedAtVersion, TaskCompletionSource> task)> _listeners = new(); + private readonly List<(long startedAtVersion, TaskCompletionSource> task)> _listeners = []; private readonly TransientEventAwareEventRepository _transientEventRepository; private readonly SemaphoreSlim _newEventAvailable = new(0); @@ -48,6 +49,7 @@ public NewTransientEventNotifier(TransientEventAwareEventRepository transientEve } } } + // ReSharper disable once FunctionNeverReturns }); } diff --git a/src/Fluss.HotChocolate/UnitOfWorkParameterExpressionBuilder.cs b/src/Fluss.HotChocolate/UnitOfWorkParameterExpressionBuilder.cs index 217e033..5a23aa7 100644 --- a/src/Fluss.HotChocolate/UnitOfWorkParameterExpressionBuilder.cs +++ b/src/Fluss.HotChocolate/UnitOfWorkParameterExpressionBuilder.cs @@ -21,23 +21,15 @@ public class UnitOfWorkParameterExpressionBuilder : IParameterExpressionBuilder private static readonly MethodInfo ServiceUnitOfWorkMethod = typeof(IPureResolverContext).GetMethods().First( - method => method.Name == nameof(IPureResolverContext.Service) && - method.IsGenericMethod) + method => method is { Name: nameof(IPureResolverContext.Service), IsGenericMethod: true }) .MakeGenericMethod(typeof(UnitOfWork)); - private static readonly MethodInfo GetValueOrDefaultMethod = - typeof(CollectionExtensions).GetMethods().First(m => m.Name == nameof(CollectionExtensions.GetValueOrDefault) && m.GetParameters().Length == 2); - private static readonly MethodInfo WithPrefilledVersionMethod = typeof(UnitOfWork).GetMethods(BindingFlags.Instance | BindingFlags.Public) .First(m => m.Name == nameof(UnitOfWork.WithPrefilledVersion)); - private static readonly PropertyInfo ContextData = - typeof(IHasContextData).GetProperty( - nameof(IHasContextData.ContextData))!; - public bool CanHandle(ParameterInfo parameter) => typeof(UnitOfWork) == parameter.ParameterType - || typeof(IUnitOfWork) == parameter.ParameterType; + || typeof(IUnitOfWork) == parameter.ParameterType; /* * Produces something like this: context.GetOrSetGlobalState( diff --git a/src/Fluss.PostgreSQL/Fluss.PostgreSQL.csproj b/src/Fluss.PostgreSQL/Fluss.PostgreSQL.csproj index 88633ac..b2f6259 100644 --- a/src/Fluss.PostgreSQL/Fluss.PostgreSQL.csproj +++ b/src/Fluss.PostgreSQL/Fluss.PostgreSQL.csproj @@ -1,27 +1,28 @@  - net8.0; net7.0 + net8.0 enable enable + true - - - - - + + + + + - - ..\..\..\..\..\.nuget\packages\opentelemetry.api\1.6.0\lib\net6.0\OpenTelemetry.Api.dll - + + ..\..\..\..\..\.nuget\packages\opentelemetry.api\1.6.0\lib\net6.0\OpenTelemetry.Api.dll + - + diff --git a/src/Fluss.PostgreSQL/PostgreSQLEventRepository.cs b/src/Fluss.PostgreSQL/PostgreSQLEventRepository.cs index f304277..c11e431 100644 --- a/src/Fluss.PostgreSQL/PostgreSQLEventRepository.cs +++ b/src/Fluss.PostgreSQL/PostgreSQLEventRepository.cs @@ -1,7 +1,6 @@ using System.Collections.ObjectModel; using System.Data; using System.Diagnostics; -using EventSourcing.PostgreSQL; using Fluss.Events; using Fluss.Exceptions; using Newtonsoft.Json; @@ -13,13 +12,10 @@ namespace Fluss.PostgreSQL; public partial class PostgreSQLEventRepository : IBaseEventRepository { - private readonly PostgreSQLConfig config; private readonly NpgsqlDataSource dataSource; public PostgreSQLEventRepository(PostgreSQLConfig config) { - this.config = config; - var dataSourceBuilder = new NpgsqlDataSourceBuilder(config.ConnectionString); dataSourceBuilder.UseJsonNet(settings: new JsonSerializerSettings { @@ -28,7 +24,7 @@ public PostgreSQLEventRepository(PostgreSQLConfig config) MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead // While this is marked as a performance hit, profiling approves }); - this.dataSource = dataSourceBuilder.Build(); + dataSource = dataSourceBuilder.Build(); } private async ValueTask Publish(IEnumerable envelopes, Func eventExtractor, @@ -45,7 +41,7 @@ private async ValueTask Publish(IEnumerable envelopes, Fun await using var writer = connection.BeginBinaryImport( - @"COPY ""Events"" (""Version"", ""At"", ""By"", ""Event"") FROM STDIN (FORMAT BINARY)"); + """COPY "Events" ("Version", "At", "By", "Event") FROM STDIN (FORMAT BINARY)"""); activity?.AddEvent(new ActivityEvent("Got Writer")); @@ -131,7 +127,7 @@ public async ValueTask>> GetEve Version = reader.GetInt64(0), At = reader.GetDateTime(1), By = reader.IsDBNull(2) ? null : reader.GetGuid(2), - Event = reader.GetFieldValue(3) + Event = reader.GetFieldValue(3), }); } @@ -177,7 +173,7 @@ public async ValueTask ReplaceEvent(long at, IEnumerable newEn { // Deferring constraints to allow updating the primary key and shifting the versions await using var deferConstraintsCommand = - new NpgsqlCommand(@"SET CONSTRAINTS ""PK_Events"" DEFERRED;", connection); + new NpgsqlCommand("""SET CONSTRAINTS "PK_Events" DEFERRED;""", connection); await deferConstraintsCommand.ExecuteNonQueryAsync(); await using var versionUpdateCommand = @@ -216,10 +212,10 @@ public async ValueTask GetLatestVersion() public void Dispose() { - if (!_cancellationTokenSource.IsCancellationRequested) - { - _cancellationTokenSource.Cancel(); - _cancellationTokenSource.Dispose(); - } + GC.SuppressFinalize(this); + if (_cancellationTokenSource.IsCancellationRequested) return; + + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); } } diff --git a/src/Fluss.PostgreSQL/PostgreSQLEventRepositorySubscriptions.cs b/src/Fluss.PostgreSQL/PostgreSQLEventRepositorySubscriptions.cs index 52078f4..ad0fcca 100644 --- a/src/Fluss.PostgreSQL/PostgreSQLEventRepositorySubscriptions.cs +++ b/src/Fluss.PostgreSQL/PostgreSQLEventRepositorySubscriptions.cs @@ -41,7 +41,7 @@ private async Task InitializeTrigger() NotifyNewEvents(); }; - await using var listen = new NpgsqlCommand(@"LISTEN new_event", listenConnection); + await using var listen = new NpgsqlCommand("LISTEN new_event", listenConnection); await listen.ExecuteNonQueryAsync(_cancellationTokenSource.Token); while (!_cancellationTokenSource.Token.IsCancellationRequested) @@ -49,7 +49,7 @@ private async Task InitializeTrigger() await listenConnection.WaitAsync(_cancellationTokenSource.Token); } - await using var unlisten = new NpgsqlCommand(@"UNLISTEN new_event", listenConnection); + await using var unlisten = new NpgsqlCommand("UNLISTEN new_event", listenConnection); await unlisten.ExecuteNonQueryAsync(new CancellationToken()); } diff --git a/src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs b/src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs index 5ad2768..d84dc0f 100644 --- a/src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs +++ b/src/Fluss.PostgreSQL/ServiceCollectionExtensions.cs @@ -27,7 +27,7 @@ public static IServiceCollection AddPostgresEventSourcingRepository(this IServic .ConfigureRunner(rb => rb .AddPostgres() .WithGlobalConnectionString(connectionString) - .ScanIn(typeof(Fluss.PostgreSQL.PostgreSQLEventRepository).Assembly).For.Migrations()) + .ScanIn(typeof(PostgreSQLEventRepository).Assembly).For.Migrations()) .AddLogging(lb => lb.AddFluentMigratorConsole()) .AddSingleton(new PostgreSQLConfig(connectionString)) .AddSingleton() @@ -35,20 +35,12 @@ public static IServiceCollection AddPostgresEventSourcingRepository(this IServic } } -public class Migrator : BackgroundService +public class Migrator(ILogger logger, IServiceProvider serviceProvider) : BackgroundService { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; private bool _didFinish; private readonly SemaphoreSlim _didFinishChanged = new(0, 1); - public Migrator(ILogger logger, IServiceProvider serviceProvider) - { - _logger = logger; - _serviceProvider = serviceProvider; - } - public async Task WaitForFinish() { while (true) @@ -72,7 +64,7 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken) { try { - var scope = _serviceProvider.CreateScope(); + var scope = serviceProvider.CreateScope(); var migrationRunner = scope.ServiceProvider.GetRequiredService(); migrationRunner.MigrateUp(); @@ -81,54 +73,39 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken) } catch (Exception e) { - _logger.LogError(e, "Error while migrating"); + logger.LogError(e, "Error while migrating"); Environment.Exit(-1); } }, stoppingToken); } } -public class Upcaster : BackgroundService +public class Upcaster(EventUpcasterService upcasterService, Migrator migrator, ILogger logger) + : BackgroundService { - private readonly EventUpcasterService _upcasterService; - private readonly Migrator _migrator; - private readonly ILogger _logger; - - public Upcaster(EventUpcasterService upcasterService, Migrator migrator, ILogger logger) - { - _upcasterService = upcasterService; - _migrator = migrator; - _logger = logger; - } - protected override Task ExecuteAsync(CancellationToken stoppingToken) { return Task.Run(async () => { - _logger.LogInformation("Waiting for migration to finish"); - await _migrator.WaitForFinish(); - _logger.LogInformation("Migration finished, starting event upcasting"); + logger.LogInformation("Waiting for migration to finish"); + await migrator.WaitForFinish(); + logger.LogInformation("Migration finished, starting event upcasting"); try { - await _upcasterService.Run(); - _logger.LogInformation("Event upcasting finished"); + await upcasterService.Run(); + logger.LogInformation("Event upcasting finished"); } catch (Exception e) { - _logger.LogError(e, "Error while upcasting"); + logger.LogError(e, "Error while upcasting"); throw; } }, stoppingToken); } } -public class PostgreSQLConfig +public class PostgreSQLConfig(string connectionString) { - public PostgreSQLConfig(string connectionString) - { - ConnectionString = connectionString; - } - - public string ConnectionString { get; } + public string ConnectionString { get; } = connectionString; } diff --git a/src/Fluss.Regen/Attributes/SelectorAttribute.cs b/src/Fluss.Regen/Attributes/SelectorAttribute.cs index a69d153..b5c6e5d 100644 --- a/src/Fluss.Regen/Attributes/SelectorAttribute.cs +++ b/src/Fluss.Regen/Attributes/SelectorAttribute.cs @@ -2,18 +2,20 @@ public abstract class SelectorAttribute { - public static string FullName => $"{Namespace}.{AttributeName}"; - private const string Namespace = "Fluss.Regen"; private const string AttributeName = "SelectorAttribute"; - public const string AttributeSourceCode = $@"// + public const string AttributeSourceCode = $$""" + // -namespace {Namespace} -{{ - [System.AttributeUsage(System.AttributeTargets.Method)] - public class {AttributeName} : System.Attribute - {{ - }} -}}"; + namespace {{Namespace}} + { + [System.AttributeUsage(System.AttributeTargets.Method)] + public class {{AttributeName}} : System.Attribute + { + } + } + """; + + public static string FullName => $"{Namespace}.{AttributeName}"; } \ No newline at end of file diff --git a/src/Fluss.Regen/Fluss.Regen.csproj b/src/Fluss.Regen/Fluss.Regen.csproj index 585dd15..33350fe 100644 --- a/src/Fluss.Regen/Fluss.Regen.csproj +++ b/src/Fluss.Regen/Fluss.Regen.csproj @@ -11,6 +11,7 @@ Fluss.Regen Fluss.Regen + true @@ -20,7 +21,7 @@ - + diff --git a/src/Fluss.Regen/Inspectors/ISyntaxInfo.cs b/src/Fluss.Regen/Inspectors/ISyntaxInfo.cs index 4030a73..7db35ca 100644 --- a/src/Fluss.Regen/Inspectors/ISyntaxInfo.cs +++ b/src/Fluss.Regen/Inspectors/ISyntaxInfo.cs @@ -2,6 +2,4 @@ namespace Fluss.Regen.Inspectors; -public interface ISyntaxInfo : IEquatable -{ -} +public interface ISyntaxInfo : IEquatable; diff --git a/src/Fluss.Regen/Inspectors/SelectorInfo.cs b/src/Fluss.Regen/Inspectors/SelectorInfo.cs index 359ebcb..2fd5938 100644 --- a/src/Fluss.Regen/Inspectors/SelectorInfo.cs +++ b/src/Fluss.Regen/Inspectors/SelectorInfo.cs @@ -3,30 +3,18 @@ namespace Fluss.Regen.Inspectors; -public sealed class SelectorInfo : ISyntaxInfo +public sealed class SelectorInfo( + AttributeSyntax attributeSyntax, + IMethodSymbol methodSymbol, + MethodDeclarationSyntax methodSyntax) + : ISyntaxInfo { - private AttributeSyntax AttributeSyntax { get; } - public IMethodSymbol MethodSymbol { get; } - private MethodDeclarationSyntax MethodSyntax { get; } - public string Name { get; } - public string Namespace { get; } - public string ContainingType { get; } - - public SelectorInfo( - AttributeSyntax attributeSyntax, - IMethodSymbol attributeSymbol, - IMethodSymbol methodSymbol, - MethodDeclarationSyntax methodSyntax - ) - { - AttributeSyntax = attributeSyntax; - MethodSymbol = methodSymbol; - MethodSyntax = methodSyntax; - - Name = methodSymbol.Name; - Namespace = methodSymbol.ContainingNamespace.ToDisplayString(); - ContainingType = methodSymbol.ContainingType.ToDisplayString(); - } + private AttributeSyntax AttributeSyntax { get; } = attributeSyntax; + public IMethodSymbol MethodSymbol { get; } = methodSymbol; + private MethodDeclarationSyntax MethodSyntax { get; } = methodSyntax; + public string Name { get; } = methodSymbol.Name; + public string Namespace { get; } = methodSymbol.ContainingNamespace.ToDisplayString(); + public string ContainingType { get; } = methodSymbol.ContainingType.ToDisplayString(); public bool Equals(SelectorInfo? other) { diff --git a/src/Fluss.Regen/Inspectors/SelectorInspector.cs b/src/Fluss.Regen/Inspectors/SelectorInspector.cs index 0aa64ae..97ea73d 100644 --- a/src/Fluss.Regen/Inspectors/SelectorInspector.cs +++ b/src/Fluss.Regen/Inspectors/SelectorInspector.cs @@ -33,7 +33,6 @@ public bool TryHandle( { syntaxInfo = new SelectorInfo( attributeSyntax, - attributeSymbol, methodSymbol, methodSyntax); return true; diff --git a/src/Fluss.Regen/Readme.md b/src/Fluss.Regen/Readme.md index 1a1cf44..b4f36c0 100644 --- a/src/Fluss.Regen/Readme.md +++ b/src/Fluss.Regen/Readme.md @@ -1,29 +1,45 @@ # Roslyn Source Generators Sample -A set of three projects that illustrates Roslyn source generators. Enjoy this template to learn from and modify source generators for your own needs. +A set of three projects that illustrates Roslyn source generators. Enjoy this template to learn from and modify source +generators for your own needs. ## Content + ### Fluss.Regen + A .NET Standard project with implementations of sample source generators. **You must build this project to see the result (generated code) in the IDE.** -- [SampleSourceGenerator.cs](SampleSourceGenerator.cs): A source generator that creates C# classes based on a text file (in this case, Domain Driven Design ubiquitous language registry). -- [SampleIncrementalSourceGenerator.cs](SampleIncrementalSourceGenerator.cs): A source generator that creates a custom report based on class properties. The target class should be annotated with the `Generators.ReportAttribute` attribute. +- [SampleSourceGenerator.cs](SampleSourceGenerator.cs): A source generator that creates C# classes based on a text + file (in this case, Domain Driven Design ubiquitous language registry). +- [SampleIncrementalSourceGenerator.cs](SampleIncrementalSourceGenerator.cs): A source generator that creates a custom + report based on class properties. The target class should be annotated with the `Generators.ReportAttribute` + attribute. ### Fluss.Regen.Sample -A project that references source generators. Note the parameters of `ProjectReference` in [Fluss.Regen.Sample.csproj](../Fluss.Regen.Sample/Fluss.Regen.Sample.csproj), they make sure that the project is referenced as a set of source generators. + +A project that references source generators. Note the parameters of `ProjectReference` +in [Fluss.Regen.Sample.csproj](../Fluss.Regen.Sample/Fluss.Regen.Sample.csproj), they make sure that the project is +referenced as a set of source generators. ### Fluss.Regen.Tests + Unit tests for source generators. The easiest way to develop language-related features is to start with unit tests. ## How To? + ### How to debug? + - Use the [launchSettings.json](Properties/launchSettings.json) profile. - Debug tests. ### How can I determine which syntax nodes I should expect? + Consider installing the Roslyn syntax tree viewer plugin [Rossynt](https://plugins.jetbrains.com/plugin/16902-rossynt/). ### How to learn more about wiring source generators? -Watch the walkthrough video: [Let’s Build an Incremental Source Generator With Roslyn, by Stefan Pölz](https://youtu.be/azJm_Y2nbAI) -The complete set of information is available in [Source Generators Cookbook](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md). \ No newline at end of file + +Watch the walkthrough +video: [Let’s Build an Incremental Source Generator With Roslyn, by Stefan Pölz](https://youtu.be/azJm_Y2nbAI) +The complete set of information is available +in [Source Generators Cookbook](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md). \ No newline at end of file diff --git a/src/Fluss.Testing/AggregateTestBed.cs b/src/Fluss.Testing/AggregateTestBed.cs index 286fac3..19a340f 100644 --- a/src/Fluss.Testing/AggregateTestBed.cs +++ b/src/Fluss.Testing/AggregateTestBed.cs @@ -2,7 +2,6 @@ using Fluss.Aggregates; using Fluss.Authentication; using Fluss.Events; -using Fluss.Extensions; using Fluss.Validation; using Moq; using Xunit; @@ -12,7 +11,7 @@ namespace Fluss.Testing; public class AggregateTestBed : EventTestBed where TAggregate : AggregateRoot, new() { private readonly UnitOfWork _unitOfWork; - private readonly IList _ignoredTypes = new List(); + private readonly List _ignoredTypes = []; public AggregateTestBed() { @@ -22,7 +21,7 @@ public AggregateTestBed() validator.Setup(v => v.ValidateAggregate(It.IsAny(), It.IsAny())) .Returns((_, _) => Task.CompletedTask); - _unitOfWork = new UnitOfWork(EventRepository, EventListenerFactory, new[] { new AllowAllPolicy() }, + _unitOfWork = new UnitOfWork(EventRepository, EventListenerFactory, [new AllowAllPolicy()], new UserIdProvider(_ => Guid.Empty, null!), validator.Object); } @@ -34,7 +33,7 @@ public AggregateTestBed Calling(Func action) public AggregateTestBed Calling(TKey key, Func action) { - var aggregate = _unitOfWork.GetAggregate(key).GetResult(); + var aggregate = _unitOfWork.GetAggregate(key).AsTask().Result; action(aggregate).GetAwaiter().GetResult(); return this; } @@ -52,7 +51,7 @@ public void ResultsIn(params Event[] expectedEvents) if (expectedEvents.Length == publishedEvents.Length) { - for (int i = 0; i < expectedEvents.Length; i++) + for (var i = 0; i < expectedEvents.Length; i++) { expectedEvents[i] = GetEventRespectingIgnoredTypes(expectedEvents[i], publishedEvents[i]); } @@ -69,7 +68,7 @@ private Event GetEventRespectingIgnoredTypes(Event expected, Event published) } var cloneMethod = expected.GetType().GetMethod("$"); - var exp = (Event)cloneMethod!.Invoke(expected, Array.Empty())!; + var exp = (Event)cloneMethod!.Invoke(expected, [])!; foreach (var fieldInfo in expected.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { if (_ignoredTypes.Contains(fieldInfo.FieldType)) diff --git a/src/Fluss.Testing/CanaryEvent.cs b/src/Fluss.Testing/CanaryEvent.cs index bcdf8db..786a336 100644 --- a/src/Fluss.Testing/CanaryEvent.cs +++ b/src/Fluss.Testing/CanaryEvent.cs @@ -3,7 +3,4 @@ namespace Fluss.Testing; // This event should not be allowed by anything -public class CanaryEvent : Event -{ - -} +public class CanaryEvent : Event; diff --git a/src/Fluss.Testing/EventRepositoryTestBase.cs b/src/Fluss.Testing/EventRepositoryTestBase.cs index bcd5089..af42303 100644 --- a/src/Fluss.Testing/EventRepositoryTestBase.cs +++ b/src/Fluss.Testing/EventRepositoryTestBase.cs @@ -13,7 +13,7 @@ private static DateTimeOffset RemoveTicks(DateTimeOffset d) return d.AddTicks(-(d.Ticks % TimeSpan.TicksPerSecond)); } - protected abstract T Repository { get; set; } + protected abstract T Repository { get; } [Fact] public async Task ReturnRightLatestVersion() @@ -81,7 +81,7 @@ public async Task ReturnsRawEvents() var rawEnvelopes = (await Repository.GetRawEvents()).ToList(); Assert.Equal(envelopes.Count, rawEnvelopes.Count); - for (int i = 0; i < envelopes.Count; i++) + for (var i = 0; i < envelopes.Count; i++) { var envelope = envelopes[i]; var rawEnvelope = rawEnvelopes[i]; @@ -126,7 +126,7 @@ public async Task DeletesEvents() var envelopes = GetMockEnvelopes(0, 2).ToList(); await Repository.Publish(envelopes); - await Repository.ReplaceEvent(1, Enumerable.Empty()); + await Repository.ReplaceEvent(1, []); var latestVersion = await Repository.GetLatestVersion(); Assert.Equal(1, latestVersion); @@ -150,7 +150,7 @@ await Assert.ThrowsAsync(async () => await Repository.Publish(envelopes2)); } - private IEnumerable GetMockEnvelopes(int from, int to) + private static List GetMockEnvelopes(int from, int to) { return Enumerable.Range(from, to - from + 1).Select(version => new EventEnvelope { At = DateTimeOffset.UtcNow, By = null, Version = version, Event = new MockEvent() }) @@ -159,7 +159,7 @@ private IEnumerable GetMockEnvelopes(int from, int to) private record MockEvent : Event; - public class AtTickIgnoringEnvelopeCompare : EqualityComparer + private class AtTickIgnoringEnvelopeCompare : EqualityComparer { public override bool Equals(EventEnvelope? a, EventEnvelope? b) { diff --git a/src/Fluss.Testing/EventTestBed.cs b/src/Fluss.Testing/EventTestBed.cs index 9fc335b..890467c 100644 --- a/src/Fluss.Testing/EventTestBed.cs +++ b/src/Fluss.Testing/EventTestBed.cs @@ -1,5 +1,4 @@ using Fluss.Events; -using Fluss.Extensions; namespace Fluss.Testing; @@ -16,21 +15,21 @@ protected EventTestBed() public virtual EventTestBed WithEvents(params Event[] events) { - var startingVersion = EventRepository.GetLatestVersion().GetAwaiter().GetResult(); + var startingVersion = EventRepository.GetLatestVersion().AsTask().Result; EventRepository.Publish(events.Select(@event => new EventEnvelope { Version = ++startingVersion, At = DateTimeOffset.Now, By = null, Event = @event - })).GetAwaiter().GetResult(); + })).AsTask().Wait(); return this; } public virtual EventTestBed WithEventEnvelopes(params EventEnvelope[] eventEnvelopes) { - EventRepository.Publish(eventEnvelopes).GetResult(); + EventRepository.Publish(eventEnvelopes).AsTask().Wait(); return this; } } diff --git a/src/Fluss.Testing/Fluss.Testing.csproj b/src/Fluss.Testing/Fluss.Testing.csproj index d3eab38..6a7f0d1 100644 --- a/src/Fluss.Testing/Fluss.Testing.csproj +++ b/src/Fluss.Testing/Fluss.Testing.csproj @@ -1,18 +1,19 @@  - net8.0; net7.0 + net8.0 enable enable + true - + - - + + diff --git a/src/Fluss.Testing/PolicyTestBed.cs b/src/Fluss.Testing/PolicyTestBed.cs index c30bb2c..f21437b 100644 --- a/src/Fluss.Testing/PolicyTestBed.cs +++ b/src/Fluss.Testing/PolicyTestBed.cs @@ -1,6 +1,5 @@ using Fluss.Authentication; using Fluss.Events; -using Fluss.Extensions; using Fluss.ReadModel; using Xunit; @@ -28,7 +27,7 @@ public PolicyTestBed Allows(params Event[] events) { foreach (var @event in events) { - Assert.True(_policy.AuthenticateEvent(GetEnvelope(@event), _authContext).GetResult(), $"Event should be allowed {@event}"); + Assert.True(_policy.AuthenticateEvent(GetEnvelope(@event), _authContext).AsTask().Result, $"Event should be allowed {@event}"); } AssertPolicyDoesNoAllowCanary(); @@ -40,7 +39,7 @@ public PolicyTestBed Refuses(params Event[] events) { foreach (var @event in events) { - Assert.False(_policy.AuthenticateEvent(GetEnvelope(@event), _authContext).GetResult(), $"Event should not be allowed {@event}"); + Assert.False(_policy.AuthenticateEvent(GetEnvelope(@event), _authContext).AsTask().Result, $"Event should not be allowed {@event}"); } AssertPolicyDoesNoAllowCanary(); @@ -54,8 +53,8 @@ private void AssertPolicyDoesNoAllowCanary() { At = DateTimeOffset.Now, Event = new CanaryEvent(), - Version = EventRepository.GetLatestVersion().GetResult() + 1 - }, _authContext).GetResult(), "Policy should not allow any event"); + Version = EventRepository.GetLatestVersion().AsTask().Result + 1, + }, _authContext).AsTask().Result, "Policy should not allow any event"); } public override PolicyTestBed WithEvents(params Event[] events) @@ -77,19 +76,12 @@ private EventEnvelope GetEnvelope(Event @event) At = DateTimeOffset.Now, By = _userId, Event = @event, - Version = EventRepository.GetLatestVersion().GetResult() + Version = EventRepository.GetLatestVersion().AsTask().Result, }; } - private class AuthContextMock : IAuthContext + private class AuthContextMock(PolicyTestBed policyTestBed) : IAuthContext { - private readonly PolicyTestBed _policyTestBed; - - public AuthContextMock(PolicyTestBed policyTestBed) - { - _policyTestBed = policyTestBed; - } - public async ValueTask CacheAndGet(string key, Func> func) { return await func(); @@ -97,12 +89,12 @@ public async ValueTask CacheAndGet(string key, Func> func) public async ValueTask GetReadModel() where TReadModel : EventListener, IRootEventListener, IReadModel, new() { - return await _policyTestBed.EventListenerFactory.UpdateTo(new TReadModel(), await _policyTestBed.EventRepository.GetLatestVersion()); + return await policyTestBed.EventListenerFactory.UpdateTo(new TReadModel(), await policyTestBed.EventRepository.GetLatestVersion()); } public async ValueTask GetReadModel(TKey key) where TReadModel : EventListener, IEventListenerWithKey, IReadModel, new() { - return await _policyTestBed.EventListenerFactory.UpdateTo(new TReadModel { Id = key }, await _policyTestBed.EventRepository.GetLatestVersion()); + return await policyTestBed.EventListenerFactory.UpdateTo(new TReadModel { Id = key }, await policyTestBed.EventRepository.GetLatestVersion()); } public async ValueTask> GetMultipleReadModels(IEnumerable keys) where TReadModel : EventListener, IReadModel, IEventListenerWithKey, new() where TKey : notnull @@ -110,6 +102,6 @@ public async ValueTask CacheAndGet(string key, Func> func) return await Task.WhenAll(keys.Select(async k => await GetReadModel(k))); } - public Guid UserId => _policyTestBed._userId; + public Guid UserId => policyTestBed._userId; } } diff --git a/src/Fluss.Testing/ReadModelTestBed.cs b/src/Fluss.Testing/ReadModelTestBed.cs index 4d4aa60..4b2904f 100644 --- a/src/Fluss.Testing/ReadModelTestBed.cs +++ b/src/Fluss.Testing/ReadModelTestBed.cs @@ -1,5 +1,4 @@ using Fluss.Events; -using Fluss.Extensions; using Fluss.ReadModel; using Xunit; @@ -10,7 +9,7 @@ public class ReadModelTestBed : EventTestBed public ReadModelTestBed ResultsIn(TReadModel readModel) where TReadModel : RootReadModel, new() { var eventSourced = EventListenerFactory - .UpdateTo(new TReadModel(), EventRepository.GetLatestVersion().GetResult()).GetResult(); + .UpdateTo(new TReadModel(), EventRepository.GetLatestVersion().AsTask().Result).AsTask().Result; Assert.Equal(readModel with { Tag = eventSourced.Tag }, eventSourced); @@ -23,7 +22,7 @@ public ReadModelTestBed ResultsIn(TReadModel readModel) where TReadModel : ReadModelWithKey, new() { var eventSourced = EventListenerFactory - .UpdateTo(new TReadModel { Id = readModel.Id }, EventRepository.GetLatestVersion().GetResult()).GetResult(); + .UpdateTo(new TReadModel { Id = readModel.Id }, EventRepository.GetLatestVersion().AsTask().Result).AsTask().Result; Assert.Equal(readModel with { Tag = eventSourced.Tag }, eventSourced); @@ -38,7 +37,7 @@ private void AssertReadModelDoesNotReactToCanary(EventListener readModel) { At = DateTimeOffset.Now, Event = new CanaryEvent(), - Version = EventRepository.GetLatestVersion().GetResult() + 1 + Version = EventRepository.GetLatestVersion().AsTask().Result + 1, }), "Read model should not react to arbitrary events"); } diff --git a/src/Fluss.UnitTest/Core/Authentication/ArbitraryUserUnitOfWorkExtensionTest.cs b/src/Fluss.UnitTest/Core/Authentication/ArbitraryUserUnitOfWorkExtensionTest.cs index 19b53ed..c7d35e6 100644 --- a/src/Fluss.UnitTest/Core/Authentication/ArbitraryUserUnitOfWorkExtensionTest.cs +++ b/src/Fluss.UnitTest/Core/Authentication/ArbitraryUserUnitOfWorkExtensionTest.cs @@ -55,7 +55,7 @@ await unitOfWorkFactory.Commit(async work => Assert.Equal(guid, events[0].ToArray()[0].By); } - private class TestEvent : Event { } + private class TestEvent : Event; private class AllowAllPolicy : Policy { diff --git a/src/Fluss.UnitTest/Core/Authentication/AuthContextTest.cs b/src/Fluss.UnitTest/Core/Authentication/AuthContextTest.cs index 830c624..fcb7e5d 100644 --- a/src/Fluss.UnitTest/Core/Authentication/AuthContextTest.cs +++ b/src/Fluss.UnitTest/Core/Authentication/AuthContextTest.cs @@ -8,14 +8,12 @@ namespace Fluss.UnitTest.Core.Authentication; public class AuthContextTest { private readonly Mock _unitOfWork; - private readonly Guid _userId; private readonly AuthContext _authContext; public AuthContextTest() { _unitOfWork = new Mock(); - _userId = Guid.NewGuid(); - _authContext = new AuthContext(_unitOfWork.Object, _userId); + _authContext = new AuthContext(_unitOfWork.Object, Guid.NewGuid()); } [Fact] @@ -56,12 +54,12 @@ public async Task ForwardsGetMultipleReadModels() var testReadModel = new TestReadModelWithKey { Id = 0 }; _unitOfWork .Setup(uow => uow.UnsafeGetMultipleReadModelsWithoutAuthorization(new[] { 0, 1 }, null)) - .Returns(ValueTask.FromResult((IReadOnlyList)new List { testReadModel }.AsReadOnly())); + .Returns(ValueTask.FromResult>(new List { testReadModel }.AsReadOnly())); - Assert.Equal(new List { testReadModel }, await _authContext.GetMultipleReadModels(new[] { 0, 1 })); + Assert.Equal(new List { testReadModel }, await _authContext.GetMultipleReadModels([0, 1])); } - public record TestReadModel : RootReadModel + private record TestReadModel : RootReadModel { protected override EventListener When(EventEnvelope envelope) { @@ -69,7 +67,7 @@ protected override EventListener When(EventEnvelope envelope) } } - public record TestReadModelWithKey : ReadModelWithKey + private record TestReadModelWithKey : ReadModelWithKey { protected override EventListener When(EventEnvelope envelope) { diff --git a/src/Fluss.UnitTest/Core/Events/EventListenerFactoryTest.cs b/src/Fluss.UnitTest/Core/Events/EventListenerFactoryTest.cs index 31b2d71..d917698 100644 --- a/src/Fluss.UnitTest/Core/Events/EventListenerFactoryTest.cs +++ b/src/Fluss.UnitTest/Core/Events/EventListenerFactoryTest.cs @@ -6,18 +6,17 @@ namespace Fluss.UnitTest.Core.Events; public class EventListenerFactoryTest { private readonly EventListenerFactory _eventListenerFactory; - private readonly InMemoryEventRepository _eventRepository; public EventListenerFactoryTest() { - _eventRepository = new InMemoryEventRepository(); - _eventListenerFactory = new EventListenerFactory(_eventRepository); + var repository = new InMemoryEventRepository(); + _eventListenerFactory = new EventListenerFactory(repository); - _eventRepository.Publish(new[] { + repository.Publish([ new EventEnvelope { Event = new TestEvent(1), Version = 0 }, new EventEnvelope { Event = new TestEvent(2), Version = 1 }, new EventEnvelope { Event = new TestEvent(1), Version = 2 }, - }); + ]).AsTask().Wait(); } [Fact] @@ -36,29 +35,31 @@ public async Task CanGetReadModel() private record TestRootReadModel : RootReadModel { - public int GotEvents { get; private set; } + public int GotEvents { get; private init; } + protected override TestRootReadModel When(EventEnvelope envelope) { return envelope.Event switch { TestEvent => this with { GotEvents = GotEvents + 1 }, - _ => this + _ => this, }; } } private record TestReadModel : ReadModelWithKey { - public int GotEvents { get; private set; } + public int GotEvents { get; private init; } + protected override TestReadModel When(EventEnvelope envelope) { return envelope.Event switch { TestEvent testEvent when testEvent.Id == Id => this with { GotEvents = GotEvents + 1 }, - _ => this + _ => this, }; } } private record TestEvent(int Id) : Event; -} +} \ No newline at end of file diff --git a/src/Fluss.UnitTest/Core/Events/InMemoryCacheTest.cs b/src/Fluss.UnitTest/Core/Events/InMemoryCacheTest.cs index a5772fd..65ff772 100644 --- a/src/Fluss.UnitTest/Core/Events/InMemoryCacheTest.cs +++ b/src/Fluss.UnitTest/Core/Events/InMemoryCacheTest.cs @@ -98,12 +98,12 @@ public async Task CanRequestAcrossMultipleCacheItems() } } - private ReadOnlyCollection> GetMockEnvelopes(int from, int to) + private static ReadOnlyCollection> GetMockEnvelopes(int from, int to) { return Enumerable.Range(from, to - from + 1).Select(version => new EventEnvelope { At = DateTimeOffset.Now, By = null, Version = version, Event = new MockEvent() }) .ToList().ToPagedMemory(); } - private class MockEvent : Event { } + private class MockEvent : Event; } diff --git a/src/Fluss.UnitTest/Core/Events/InMemoryEventRepositoryTest.cs b/src/Fluss.UnitTest/Core/Events/InMemoryEventRepositoryTest.cs index 7d7e91b..66049ba 100644 --- a/src/Fluss.UnitTest/Core/Events/InMemoryEventRepositoryTest.cs +++ b/src/Fluss.UnitTest/Core/Events/InMemoryEventRepositoryTest.cs @@ -5,7 +5,7 @@ namespace Fluss.UnitTest.Core.Events; public class InMemoryEventRepositoryTest : EventRepositoryTestBase { - protected sealed override InMemoryEventRepository Repository { get; set; } = new(); + protected sealed override InMemoryEventRepository Repository { get; } = new(); [Fact] public async Task ThrowsOnWrongGetEventsUsage() diff --git a/src/Fluss.UnitTest/Core/Events/InMemoryListenerCacheTest.cs b/src/Fluss.UnitTest/Core/Events/InMemoryListenerCacheTest.cs index 5a0637f..14d171d 100644 --- a/src/Fluss.UnitTest/Core/Events/InMemoryListenerCacheTest.cs +++ b/src/Fluss.UnitTest/Core/Events/InMemoryListenerCacheTest.cs @@ -13,14 +13,15 @@ public InMemoryListenerCacheTest() _baseEventListenerFactory = new Mock(); _listenerCache = new InMemoryEventListenerCache { - Next = _baseEventListenerFactory.Object + Next = _baseEventListenerFactory.Object, }; } + // ReSharper disable ReturnValueOfPureMethodIsNotUsed + [Fact] public async Task PassesUpdatesToNext() { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed _baseEventListenerFactory.Setup(f => f.UpdateTo(It.IsAny(), 100)) .Returns(ValueTask.FromResult(new TestEventListener())); @@ -38,7 +39,6 @@ public async Task PassesUpdatesToNext() [Fact] public async Task ReturnsCachedEventListener() { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed _baseEventListenerFactory.Setup(f => f.UpdateTo(It.IsAny(), 100)) .Returns(ValueTask.FromResult(new TestEventListener { Tag = new EventListenerVersionTag(100) })); @@ -57,7 +57,6 @@ public async Task ReturnsCachedEventListener() [Fact] public async Task ReturnsCachedKeyedEventListener() { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed _baseEventListenerFactory.Setup(f => f.UpdateTo(It.IsAny(), 100)) .Returns(ValueTask.FromResult(new KeyedTestEventListener { Id = 1, Tag = new EventListenerVersionTag(100) })); @@ -76,7 +75,6 @@ public async Task ReturnsCachedKeyedEventListener() [Fact] public async Task ForwardsIfCacheContainsNewer() { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed _baseEventListenerFactory.Setup(f => f.UpdateTo(It.IsAny(), 100)) .Returns(ValueTask.FromResult(new TestEventListener { Tag = new EventListenerVersionTag(100) })); _baseEventListenerFactory.Setup(f => f.UpdateTo(It.IsAny(), 90)) @@ -104,7 +102,6 @@ public async Task ForwardsIfCacheContainsNewer() [Fact] public async Task UpdatesStoreWithNewerVersion() { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed var testEventListener = new TestEventListener(); var otherTestEventList = new TestEventListener(); @@ -130,8 +127,6 @@ public async Task UpdatesStoreWithNewerVersion() [Fact] public async Task ForwardsAgainIfCleaned() { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - _baseEventListenerFactory.Setup(f => f.UpdateTo(It.IsAny(), 100)) .Returns(ValueTask.FromResult(new TestEventListener { Tag = new EventListenerVersionTag(100) })); diff --git a/src/Fluss.UnitTest/Core/Extensions/ValueTaskTest.cs b/src/Fluss.UnitTest/Core/Extensions/ValueTaskTest.cs deleted file mode 100644 index 6c819a9..0000000 --- a/src/Fluss.UnitTest/Core/Extensions/ValueTaskTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Fluss.Extensions; - -namespace Fluss.UnitTest.Core.Extensions; - -public class ValueTaskTest -{ - [Fact] - public async Task AnyReturnsFalse() - { - Assert.False(await new[] { False() }.AnyAsync()); - } - - [Fact] - public async Task AnyReturnsTrue() - { - Assert.True(await new[] { False(), True() }.AnyAsync()); - } - - [Fact] - public async Task AllReturnsFalse() - { - Assert.False(await new[] { False(), True() }.AllAsync()); - } - - [Fact] - public async Task AllReturnsTrue() - { - Assert.True(await new[] { True(), True() }.AllAsync()); - } - - [Fact] - public void GetResultWorksForBoolean() - { - Assert.True(True().GetResult()); - } - - [Fact] - public void GetResultWorksForEmpty() - { - Empty().GetResult(); - } - - private ValueTask True() - { - return ValueTask.FromResult(true); - } - - private ValueTask False() - { - return ValueTask.FromResult(false); - } - - private ValueTask Empty() - { - return ValueTask.CompletedTask; - } -} diff --git a/src/Fluss.UnitTest/Core/SideEffects/DispatcherTest.cs b/src/Fluss.UnitTest/Core/SideEffects/DispatcherTest.cs index a1cfd02..c4b635a 100644 --- a/src/Fluss.UnitTest/Core/SideEffects/DispatcherTest.cs +++ b/src/Fluss.UnitTest/Core/SideEffects/DispatcherTest.cs @@ -46,9 +46,7 @@ await serviceProvider.GetRequiredService().Commit(async unitO await dispatcher.StopAsync(CancellationToken.None); } - private class TestEvent : Event - { - } + private class TestEvent : Event; private class AllowAllPolicy : Policy { @@ -60,7 +58,7 @@ public ValueTask AuthenticateEvent(EventEnvelope envelope, IAuthContext au private class TestSideEffect : SideEffect { - public bool DidTrigger { get; set; } = false; + public bool DidTrigger { get; private set; } public Task> HandleAsync(TestEvent @event, Fluss.UnitOfWork unitOfWork) { @@ -105,11 +103,11 @@ await serviceProvider.GetRequiredService().Commit(async unitO await dispatcher.StopAsync(CancellationToken.None); } - private class TestTransientEvent : TransientEvent { } + private class TestTransientEvent : TransientEvent; private class TestTransientSideEffect : SideEffect { - public bool DidTrigger { get; set; } = false; + public bool DidTrigger { get; set; } public Task> HandleAsync(TestTransientEvent @event, Fluss.UnitOfWork unitOfWork) @@ -157,18 +155,18 @@ await serviceProvider.GetRequiredService().Commit(async unitO await dispatcher.StopAsync(CancellationToken.None); } - private class TestTriggerEvent : Event { } - private class TestReturnedEvent : Event { } + private class TestTriggerEvent : Event; + private class TestReturnedEvent : Event; private class TestReturningSideEffect : SideEffect { public Task> HandleAsync(TestTriggerEvent @event, Fluss.UnitOfWork unitOfWork) { - return Task.FromResult>(new[] { new TestReturnedEvent() }); + return Task.FromResult>([new TestReturnedEvent()]); } } - private async Task WaitUntilTrue(Func> f, TimeSpan timeSpan) + private static async Task WaitUntilTrue(Func> f, TimeSpan timeSpan) { var cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.CancelAfter(timeSpan); diff --git a/src/Fluss.UnitTest/Core/TransientEvents/TransientEventAwareEventListenerFactoryTest.cs b/src/Fluss.UnitTest/Core/TransientEvents/TransientEventAwareEventListenerFactoryTest.cs index 838af48..fa40e32 100644 --- a/src/Fluss.UnitTest/Core/TransientEvents/TransientEventAwareEventListenerFactoryTest.cs +++ b/src/Fluss.UnitTest/Core/TransientEvents/TransientEventAwareEventListenerFactoryTest.cs @@ -35,7 +35,7 @@ public async Task AppliesTransientEvents() }; var readModel = new ExampleReadModel(); - await _transientRepository.Publish(new[] { transientEventEnvelope }); + await _transientRepository.Publish([transientEventEnvelope]); var updatedReadModel = await _transientFactory.UpdateTo(readModel, -1); @@ -43,11 +43,9 @@ public async Task AppliesTransientEvents() } } -record ExampleReadModel : RootReadModel +internal record ExampleReadModel : RootReadModel { protected override EventListener When(EventEnvelope envelope) => this; } -class ExampleTransientEvent : TransientEvent -{ -} +internal class ExampleTransientEvent : TransientEvent; diff --git a/src/Fluss.UnitTest/Core/TransientEvents/TransientEventAwareEventRepositoryTest.cs b/src/Fluss.UnitTest/Core/TransientEvents/TransientEventAwareEventRepositoryTest.cs index 9becac0..ec628c8 100644 --- a/src/Fluss.UnitTest/Core/TransientEvents/TransientEventAwareEventRepositoryTest.cs +++ b/src/Fluss.UnitTest/Core/TransientEvents/TransientEventAwareEventRepositoryTest.cs @@ -56,45 +56,17 @@ public async Task DoesntCleanEventsBeforeExpiry() Assert.Empty(thirdResult); } - private List Wrap(params Event[] events) + private static List Wrap(params Event[] events) { return events.Select((e, i) => new EventEnvelope { At = DateTimeOffset.Now, By = null, Version = i, Event = e }) .ToList(); } - private class MockEvent : Event { } + private class MockEvent : Event; - private class TransientMockEvent : TransientEvent { } + private class TransientMockEvent : TransientEvent; [ExpiresAfter(200)] - private class ExpiringTransientMockEvent : TransientEvent { } - - private class EventEnvelopeEqualityComparer : IEqualityComparer - { - public bool Equals(EventEnvelope? x, EventEnvelope? y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - - if (ReferenceEquals(x, null)) - { - return false; - } - - if (ReferenceEquals(y, null)) - { - return false; - } - - return x.At == y.At && x.Event == y.Event && x.Version == y.Version; - } - - public int GetHashCode(EventEnvelope obj) - { - return obj.Event.GetHashCode(); - } - } + private class ExpiringTransientMockEvent : TransientEvent; } diff --git a/src/Fluss.UnitTest/Core/UnitOfWork/UnitOfWorkAndAuthorizationTest.cs b/src/Fluss.UnitTest/Core/UnitOfWork/UnitOfWorkAndAuthorizationTest.cs index 7632458..bd33ca8 100644 --- a/src/Fluss.UnitTest/Core/UnitOfWork/UnitOfWorkAndAuthorizationTest.cs +++ b/src/Fluss.UnitTest/Core/UnitOfWork/UnitOfWorkAndAuthorizationTest.cs @@ -13,18 +13,18 @@ public async Task DoesNotReuseCacheWhenNewEventIsAdded() await Assert.ThrowsAsync(async () => { - await _unitOfWork.GetReadModel(1); + await _unitOfWork.GetReadModel(1); }); await _unitOfWork.Publish(new AllowEvent()); - await _unitOfWork.GetReadModel(1); + await _unitOfWork.GetReadModel(1); } private record AllowEvent : Event; private record HasAllowEventReadModel : RootReadModel { - public bool HasAllowEvent { get; init; } = false; + public bool HasAllowEvent { get; private init; } protected override EventListener When(EventEnvelope envelope) { @@ -34,7 +34,7 @@ protected override EventListener When(EventEnvelope envelope) _ => this }; } - }; + } private class AllowReadAfterEventPolicy : Policy { @@ -58,7 +58,5 @@ public async Task AnEmptyPolicyDoesNotAllowAnything() Assert.False(await emptyPolicy.AuthenticateReadModel(null!, null!)); } - private class EmptyPolicy : Policy - { - } + private class EmptyPolicy : Policy; } diff --git a/src/Fluss.UnitTest/Core/UnitOfWork/UnitOfWorkTest.cs b/src/Fluss.UnitTest/Core/UnitOfWork/UnitOfWorkTest.cs index e9c7dff..e27c188 100644 --- a/src/Fluss.UnitTest/Core/UnitOfWork/UnitOfWorkTest.cs +++ b/src/Fluss.UnitTest/Core/UnitOfWork/UnitOfWorkTest.cs @@ -11,40 +11,34 @@ namespace Fluss.UnitTest.Core.UnitOfWork; public partial class UnitOfWorkTest { private readonly InMemoryEventRepository _eventRepository; - private readonly EventListenerFactory _eventListenerFactory; - private readonly Guid _userId; private readonly List _policies; private readonly Fluss.UnitOfWork _unitOfWork; private readonly UnitOfWorkFactory _unitOfWorkFactory; - private readonly Mock _validator; - public UnitOfWorkTest() { _eventRepository = new InMemoryEventRepository(); - _eventListenerFactory = new EventListenerFactory(_eventRepository); - _userId = Guid.NewGuid(); - _policies = new List(); + _policies = []; - _validator = new Mock(MockBehavior.Strict); - _validator.Setup(v => v.ValidateEvent(It.IsAny(), It.IsAny?>())) + Mock validator = new(MockBehavior.Strict); + validator.Setup(v => v.ValidateEvent(It.IsAny(), It.IsAny?>())) .Returns?>((_, _) => Task.CompletedTask); - _validator.Setup(v => v.ValidateAggregate(It.IsAny(), It.IsAny())) + validator.Setup(v => v.ValidateAggregate(It.IsAny(), It.IsAny())) .Returns((_, _) => Task.CompletedTask); _unitOfWork = new Fluss.UnitOfWork( _eventRepository, - _eventListenerFactory, + new EventListenerFactory(_eventRepository), _policies, - new UserIdProvider(_ => _userId, null!), - _validator.Object + new UserIdProvider(_ => Guid.NewGuid(), null!), + validator.Object ); - _eventRepository.Publish(new[] { + _eventRepository.Publish([ new EventEnvelope { Event = new TestEvent(1), Version = 0 }, new EventEnvelope { Event = new TestEvent(2), Version = 1 }, new EventEnvelope { Event = new TestEvent(1), Version = 2 }, - }); + ]).AsTask().Wait(); _unitOfWorkFactory = new UnitOfWorkFactory( new ServiceCollection() @@ -157,7 +151,7 @@ public async Task CanGetMultipleReadModels() { _policies.Add(new AllowAllPolicy()); - var readModels = await _unitOfWork.GetMultipleReadModels(new[] { 1, 2 }); + var readModels = await _unitOfWork.GetMultipleReadModels([1, 2]); Assert.Equal(2, readModels[0].GotEvents); Assert.Equal(1, readModels[1].GotEvents); Assert.Equal(2, readModels.Count); @@ -171,14 +165,14 @@ public async Task CanGetMultipleReadModels() [Fact] public async Task ReturnsNothingWhenMultipleReadModelNotAuthorized() { - var readModels = await _unitOfWork.GetMultipleReadModels(new[] { 1, 2 }); + var readModels = await _unitOfWork.GetMultipleReadModels([1, 2]); Assert.Equal(0, readModels.Count(rm => rm != null)); } [Fact] public async Task CanGetMultipleReadModelsUnsafe() { - var readModels = await _unitOfWork.UnsafeGetMultipleReadModelsWithoutAuthorization(new[] { 1, 2 }); + var readModels = await _unitOfWork.UnsafeGetMultipleReadModelsWithoutAuthorization([1, 2]); Assert.Equal(2, readModels[0].GotEvents); Assert.Equal(1, readModels[1].GotEvents); Assert.Equal(2, readModels.Count); @@ -223,7 +217,7 @@ protected override TestRootReadModel When(EventEnvelope envelope) return envelope.Event switch { TestEvent => this with { GotEvents = GotEvents + 1 }, - _ => this + _ => this, }; } } @@ -236,7 +230,7 @@ protected override TestReadModel When(EventEnvelope envelope) return envelope.Event switch { TestEvent testEvent when testEvent.Id == Id => this with { GotEvents = GotEvents + 1 }, - _ => this + _ => this, }; } } @@ -253,7 +247,7 @@ protected override TestAggregate When(EventEnvelope envelope) return envelope.Event switch { TestEvent testEvent when testEvent.Id == Id => this with { Exists = true }, - _ => this + _ => this, }; } } diff --git a/src/Fluss.UnitTest/Core/Upcasting/EventUpcasterServiceTest.cs b/src/Fluss.UnitTest/Core/Upcasting/EventUpcasterServiceTest.cs index 9f695f4..22d898c 100644 --- a/src/Fluss.UnitTest/Core/Upcasting/EventUpcasterServiceTest.cs +++ b/src/Fluss.UnitTest/Core/Upcasting/EventUpcasterServiceTest.cs @@ -8,14 +8,14 @@ namespace Fluss.UnitTest.Core.Upcasting; public class EventUpcasterServiceTest { - private RawEventEnvelope GetRawTestEvent1Envelope(int version) + private static RawEventEnvelope GetRawTestEvent1Envelope(int version) { var jObject = new TestEvent1("Value").ToJObject(); return new RawEventEnvelope { Version = version, RawEvent = jObject }; } - private RawEventEnvelope GetRawTestEvent2Envelope(int version) + private static RawEventEnvelope GetRawTestEvent2Envelope(int version) { var jObject = new TestEvent2("Value2").ToJObject(); @@ -39,7 +39,7 @@ private RawEventEnvelope GetRawTestEvent2Envelope(int version) [Fact] public async Task UpcasterReturningNullDoesntReplaceEvents() { - var (upcasterService, eventRepositoryMock) = GetServices(new[] { new NoopUpcast() }); + var (upcasterService, eventRepositoryMock) = GetServices([new NoopUpcast()]); await upcasterService.Run(); eventRepositoryMock.Verify( @@ -54,7 +54,7 @@ public async Task UpcasterReturningNullDoesntReplaceEvents() [Fact] public async Task SingleEventsAreUpcast() { - var (upcasterService, eventRepositoryMock) = GetServices(new[] { new SingleEventUpcast() }); + var (upcasterService, eventRepositoryMock) = GetServices([new SingleEventUpcast()]); await upcasterService.Run(); @@ -72,7 +72,7 @@ public async Task SingleEventsAreUpcast() [Fact] public async Task MultipleEventsAreUpcast() { - var (upcasterService, eventRepositoryMock) = GetServices(new[] { new MultiEventUpcast() }); + var (upcasterService, eventRepositoryMock) = GetServices([new MultiEventUpcast()]); await upcasterService.Run(); @@ -93,7 +93,7 @@ public async Task UpcastsAreChainable() .Append(GetRawTestEvent2Envelope(4)); var (upcasterService, eventRepositoryMock) = - GetServices(new IUpcaster[] { new ChainedEventUpcast2(), new ChainedEventUpcast() }, events); + GetServices([new ChainedEventUpcast2(), new ChainedEventUpcast()], events); await upcasterService.Run(); @@ -130,7 +130,8 @@ public async Task UpcastsAreChainable() [Fact] public async Task VersionIsSetCorrectly() { - var (upcasterService, eventRepositoryMock) = GetServices(new[] { new MultiEventUpcast() }, new[] { GetRawTestEvent1Envelope(1) }); + var (upcasterService, eventRepositoryMock) = GetServices([new MultiEventUpcast()], [GetRawTestEvent1Envelope(1) + ]); await upcasterService.Run(); @@ -146,16 +147,18 @@ public async Task VersionIsSetCorrectly() } } +// ReSharper disable once NotAccessedPositionalProperty.Global record TestEvent1(string Property1) : Event; +// ReSharper disable once NotAccessedPositionalProperty.Global record TestEvent2(string Property2) : Event; -class NoopUpcast : IUpcaster +internal class NoopUpcast : IUpcaster { public IEnumerable? Upcast(JObject eventJson) => null; } -class SingleEventUpcast : IUpcaster +internal class SingleEventUpcast : IUpcaster { public IEnumerable? Upcast(JObject eventJson) { @@ -166,11 +169,11 @@ class SingleEventUpcast : IUpcaster var clone = (JObject)eventJson.DeepClone(); clone["Property1"] = "Upcast"; - return new[] { clone }; + return [clone]; } } -class MultiEventUpcast : IUpcaster +internal class MultiEventUpcast : IUpcaster { public IEnumerable? Upcast(JObject eventJson) { @@ -178,11 +181,11 @@ class MultiEventUpcast : IUpcaster if (type != typeof(TestEvent1).AssemblyQualifiedName) return null; - return new[] { eventJson, eventJson, eventJson }; + return [eventJson, eventJson, eventJson]; } } -class ChainedEventUpcast : IUpcaster +internal class ChainedEventUpcast : IUpcaster { public IEnumerable? Upcast(JObject eventJson) { @@ -195,12 +198,12 @@ class ChainedEventUpcast : IUpcaster clone.Remove("Property1"); clone["$type"] = typeof(TestEvent2).AssemblyQualifiedName; - return new[] { clone }; + return [clone]; } } [DependsOn(typeof(ChainedEventUpcast))] -class ChainedEventUpcast2 : IUpcaster +internal class ChainedEventUpcast2 : IUpcaster { public IEnumerable? Upcast(JObject eventJson) { @@ -211,6 +214,6 @@ class ChainedEventUpcast2 : IUpcaster var clone = (JObject)eventJson.DeepClone(); clone["Property2"] = "Upcast-" + clone["Property2"]!.ToObject(); - return new[] { clone }; + return [clone]; } } diff --git a/src/Fluss.UnitTest/Core/Upcasting/UpcasterSorterTest.cs b/src/Fluss.UnitTest/Core/Upcasting/UpcasterSorterTest.cs index 797305d..f2a8f73 100644 --- a/src/Fluss.UnitTest/Core/Upcasting/UpcasterSorterTest.cs +++ b/src/Fluss.UnitTest/Core/Upcasting/UpcasterSorterTest.cs @@ -48,36 +48,36 @@ public void ThrowsWhenMissingDependencies() } } -class ExampleUpcasterNoDeps : IUpcaster +internal class ExampleUpcasterNoDeps : IUpcaster { public IEnumerable Upcast(JObject eventJson) => throw new NotImplementedException(); } -class ExampleUpcasterDeps1 : IUpcaster +internal class ExampleUpcasterDeps1 : IUpcaster { public IEnumerable Upcast(JObject eventJson) => throw new NotImplementedException(); } [DependsOn(typeof(ExampleUpcasterDeps1), typeof(ExampleUpcasterNoDeps))] -class ExampleUpcasterDeps2 : IUpcaster +internal class ExampleUpcasterDeps2 : IUpcaster { public IEnumerable Upcast(JObject eventJson) => throw new NotImplementedException(); } [DependsOn(typeof(ExampleUpcasterDeps1), typeof(ExampleUpcasterDeps2))] -class ExampleUpcasterDeps3 : IUpcaster +internal class ExampleUpcasterDeps3 : IUpcaster { public IEnumerable Upcast(JObject eventJson) => throw new NotImplementedException(); } [DependsOn(typeof(ExampleUpcasterCyclic2))] -class ExampleUpcasterCyclic1 : IUpcaster +internal class ExampleUpcasterCyclic1 : IUpcaster { public IEnumerable Upcast(JObject eventJson) => throw new NotImplementedException(); } [DependsOn(typeof(ExampleUpcasterCyclic1))] -class ExampleUpcasterCyclic2 : IUpcaster +internal class ExampleUpcasterCyclic2 : IUpcaster { public IEnumerable Upcast(JObject eventJson) => throw new NotImplementedException(); } diff --git a/src/Fluss.UnitTest/Core/Validation/RootValidatorTests.cs b/src/Fluss.UnitTest/Core/Validation/RootValidatorTests.cs index 48f155f..783982b 100644 --- a/src/Fluss.UnitTest/Core/Validation/RootValidatorTests.cs +++ b/src/Fluss.UnitTest/Core/Validation/RootValidatorTests.cs @@ -25,8 +25,8 @@ public async Task ValidatesValidEvent() { var validator = new RootValidator( _arbitraryUserUnitOfWorkCacheMock.Object, - new[] { new AggregateValidatorAlwaysValid() }, - new[] { new EventValidatorAlwaysValid() } + [new AggregateValidatorAlwaysValid()], + [new EventValidatorAlwaysValid()] ); await validator.ValidateEvent(new EventEnvelope { Event = new TestEvent() }); @@ -37,8 +37,8 @@ public async Task ValidatesInvalidEvent() { var validator = new RootValidator( _arbitraryUserUnitOfWorkCacheMock.Object, - new[] { new AggregateValidatorAlwaysValid() }, - new[] { new EventValidatorAlwaysInvalid() } + [new AggregateValidatorAlwaysValid()], + [new EventValidatorAlwaysInvalid()] ); await Assert.ThrowsAsync(async () => @@ -52,8 +52,8 @@ public async Task ValidatesValidAggregate() { var validator = new RootValidator( _arbitraryUserUnitOfWorkCacheMock.Object, - new[] { new AggregateValidatorAlwaysValid() }, - new[] { new EventValidatorAlwaysValid() } + [new AggregateValidatorAlwaysValid()], + [new EventValidatorAlwaysValid()] ); await validator.ValidateAggregate(new TestAggregate(), new Fluss.UnitOfWork(null!, null!, null!, null!, null!)); @@ -64,8 +64,8 @@ public async Task ValidatesInvalidAggregate() { var validator = new RootValidator( _arbitraryUserUnitOfWorkCacheMock.Object, - new[] { new AggregateValidatorAlwaysInvalid() }, - new[] { new EventValidatorAlwaysValid() } + [new AggregateValidatorAlwaysInvalid()], + [new EventValidatorAlwaysValid()] ); await Assert.ThrowsAsync(async () => @@ -74,7 +74,7 @@ await Assert.ThrowsAsync(async () => }); } - private class TestEvent : Event { } + private class TestEvent : Event; private class EventValidatorAlwaysValid : EventValidator { diff --git a/src/Fluss.UnitTest/Fluss.UnitTest.csproj b/src/Fluss.UnitTest/Fluss.UnitTest.csproj index 4e07e21..c3d6873 100644 --- a/src/Fluss.UnitTest/Fluss.UnitTest.csproj +++ b/src/Fluss.UnitTest/Fluss.UnitTest.csproj @@ -1,20 +1,22 @@ - net8.0; net7.0 + net8.0 enable enable false true + + true - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -26,13 +28,13 @@ - - - + + + - + diff --git a/src/Fluss.UnitTest/Regen/SelectorGeneratorTests.cs b/src/Fluss.UnitTest/Regen/SelectorGeneratorTests.cs index 4971445..83834ac 100644 --- a/src/Fluss.UnitTest/Regen/SelectorGeneratorTests.cs +++ b/src/Fluss.UnitTest/Regen/SelectorGeneratorTests.cs @@ -14,32 +14,32 @@ public Task GeneratesForAsyncSelector() var driver = CSharpGeneratorDriver.Create(generator); var compilation = CSharpCompilation.Create(nameof(SelectorGeneratorTests), - new[] - { + [ CSharpSyntaxTree.ParseText( - @" -using Fluss.Regen; -using System.Threading.Tasks; - -namespace TestNamespace; - -public class Test -{ - [Selector] - public static async ValueTask Add(int a, int b) { - return a + b; - } - - [Selector] - public static async ValueTask Add2(int a, int b) { - return a + b; - } -}") - }, - new[] - { + """ + + using Fluss.Regen; + using System.Threading.Tasks; + + namespace TestNamespace; + + public class Test + { + [Selector] + public static async ValueTask Add(int a, int b) { + return a + b; + } + + [Selector] + public static async ValueTask Add2(int a, int b) { + return a + b; + } + } + """) + ], + [ MetadataReference.CreateFromFile(typeof(object).Assembly.Location) - }); + ]); var runResult = driver.RunGenerators(compilation).GetRunResult(); @@ -54,26 +54,26 @@ public Task GeneratesForNonAsyncSelector() var driver = CSharpGeneratorDriver.Create(generator); var compilation = CSharpCompilation.Create(nameof(SelectorGeneratorTests), - new[] - { + [ CSharpSyntaxTree.ParseText( - @" -using Fluss.Regen; - -namespace TestNamespace; - -public class Test -{ - [Selector] - public static int Add(int a, int b) { - return a + b; - } -}") - }, - new[] - { + """ + + using Fluss.Regen; + + namespace TestNamespace; + + public class Test + { + [Selector] + public static int Add(int a, int b) { + return a + b; + } + } + """) + ], + [ MetadataReference.CreateFromFile(typeof(object).Assembly.Location) - }); + ]); var runResult = driver.RunGenerators(compilation).GetRunResult(); @@ -88,28 +88,28 @@ public Task GeneratesForUnitOfWorkSelector() var driver = CSharpGeneratorDriver.Create(generator); var compilation = CSharpCompilation.Create(nameof(SelectorGeneratorTests), - new[] - { + [ CSharpSyntaxTree.ParseText( - @" -using Fluss; -using Fluss.Regen; - -namespace TestNamespace; - -public class Test -{ - [Selector] - public static int Add(IUnitOfWork unitOfWork, int a, int b) { - return a + b; - } -}") - }, - new[] - { + """ + + using Fluss; + using Fluss.Regen; + + namespace TestNamespace; + + public class Test + { + [Selector] + public static int Add(IUnitOfWork unitOfWork, int a, int b) { + return a + b; + } + } + """) + ], + [ MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(UnitOfWork).Assembly.Location) - }); + ]); var runResult = driver.RunGenerators(compilation).GetRunResult(); diff --git a/src/Fluss.UnitTest/Setup.cs b/src/Fluss.UnitTest/Setup.cs index f72ce85..dfcaebf 100644 --- a/src/Fluss.UnitTest/Setup.cs +++ b/src/Fluss.UnitTest/Setup.cs @@ -8,6 +8,6 @@ public static class Setup public static void Init() { VerifySourceGenerators.Initialize(); - Verifier.UseSourceFileRelativeDirectory("Snapshots"); + UseSourceFileRelativeDirectory("Snapshots"); } } \ No newline at end of file diff --git a/src/Fluss.sln.DotSettings.user b/src/Fluss.sln.DotSettings.user index 7c371f3..448c27b 100644 --- a/src/Fluss.sln.DotSettings.user +++ b/src/Fluss.sln.DotSettings.user @@ -1,4 +1,6 @@  + ForceIncluded + ForceIncluded C:\Users\Enterprize1\AppData\Local\JetBrains\Rider2024.2\resharper-host\temp\Rider\vAny\CoverageData\_Fluss.-842573491\Snapshot\snapshot.utdcvr <SessionState ContinuousTestingMode="0" IsActive="True" Name="Session" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> diff --git a/src/Fluss/Authentication/ArbitraryUserUnitOfWorkExtension.cs b/src/Fluss/Authentication/ArbitraryUserUnitOfWorkExtension.cs index bbc73bf..a7f5a06 100644 --- a/src/Fluss/Authentication/ArbitraryUserUnitOfWorkExtension.cs +++ b/src/Fluss/Authentication/ArbitraryUserUnitOfWorkExtension.cs @@ -9,16 +9,10 @@ public interface IArbitraryUserUnitOfWorkCache IUnitOfWork GetUserUnitOfWork(Guid userId); } -public class ArbitraryUserUnitOfWorkCache : IArbitraryUserUnitOfWorkCache +public class ArbitraryUserUnitOfWorkCache(IServiceProvider serviceProvider) : IArbitraryUserUnitOfWorkCache { - private readonly IServiceProvider _serviceProvider; private readonly ConcurrentDictionary _cache = new(); - public ArbitraryUserUnitOfWorkCache(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - public UnitOfWorkFactory GetUserUnitOfWorkFactory(Guid userId) { var sp = GetCachedServiceProvider(userId); @@ -45,7 +39,7 @@ private IServiceProvider CreateUserServiceProvider(Guid providedId) foreach (var type in constructorArgumentTypes) { if (type == typeof(UserIdProvider)) continue; - collection.AddSingleton(type, _serviceProvider.GetRequiredService(type)); + collection.AddSingleton(type, serviceProvider.GetRequiredService(type)); } collection.ProvideUserIdFrom(_ => providedId); diff --git a/src/Fluss/Authentication/AuthContext.cs b/src/Fluss/Authentication/AuthContext.cs index 255a395..a1d01e1 100644 --- a/src/Fluss/Authentication/AuthContext.cs +++ b/src/Fluss/Authentication/AuthContext.cs @@ -20,21 +20,14 @@ public ValueTask> public Guid UserId { get; } } -internal class AuthContext : IAuthContext +internal class AuthContext(IUnitOfWork unitOfWork, Guid userId) : IAuthContext { - private readonly IUnitOfWork _unitOfWork; - public readonly Dictionary Data = new(); - public Guid UserId { get; private set; } - - public AuthContext(IUnitOfWork unitOfWork, Guid userId) - { - _unitOfWork = unitOfWork; - UserId = userId; - } + private readonly Dictionary Data = new(); + public Guid UserId { get; private set; } = userId; public async ValueTask CacheAndGet(string key, Func> func) { - var o = Data.ContainsKey(key) ? Data[key] : null; + var o = Data.GetValueOrDefault(key); switch (o) { @@ -55,19 +48,19 @@ public async ValueTask CacheAndGet(string key, Func> func) public ValueTask GetReadModel() where TReadModel : EventListener, IRootEventListener, IReadModel, new() { - return _unitOfWork.UnsafeGetReadModelWithoutAuthorization(); + return unitOfWork.UnsafeGetReadModelWithoutAuthorization(); } public ValueTask GetReadModel(TKey key) where TReadModel : EventListener, IEventListenerWithKey, IReadModel, new() { - return _unitOfWork.UnsafeGetReadModelWithoutAuthorization(key); + return unitOfWork.UnsafeGetReadModelWithoutAuthorization(key); } public ValueTask> GetMultipleReadModels(IEnumerable keys) where TKey : notnull where TReadModel : EventListener, IReadModel, IEventListenerWithKey, new() { - return _unitOfWork.UnsafeGetMultipleReadModelsWithoutAuthorization(keys); + return unitOfWork.UnsafeGetMultipleReadModelsWithoutAuthorization(keys); } } diff --git a/src/Fluss/Authentication/UserIdProvider.cs b/src/Fluss/Authentication/UserIdProvider.cs index 7d226ea..5097f35 100644 --- a/src/Fluss/Authentication/UserIdProvider.cs +++ b/src/Fluss/Authentication/UserIdProvider.cs @@ -1,18 +1,9 @@ namespace Fluss.Authentication; -public class UserIdProvider +public class UserIdProvider(Func func, IServiceProvider serviceProvider) { - private readonly Func _func; - private readonly IServiceProvider _serviceProvider; - - public UserIdProvider(Func func, IServiceProvider serviceProvider) - { - _func = func; - _serviceProvider = serviceProvider; - } - public Guid Get() { - return _func(_serviceProvider); + return func(serviceProvider); } } diff --git a/src/Fluss/Events/EventListener.cs b/src/Fluss/Events/EventListener.cs index ee47052..ad289dc 100644 --- a/src/Fluss/Events/EventListener.cs +++ b/src/Fluss/Events/EventListener.cs @@ -53,7 +53,7 @@ public record EventListenerVersionTag /// Last Event that was consumed by this EventListener public long LastSeen = -1; /// Last Event that mutated this EventListener - public long LastAccepted = -1; + public readonly long LastAccepted = -1; /// Last TransientEvent that was consumed by this EventListener public long LastSeenTransient = -1; @@ -70,22 +70,16 @@ public bool HasTaint() } } -public interface IRootEventListener -{ -} +public interface IRootEventListener; public interface IEventListenerWithKey { - public object Id { get; init; } + public object Id { get; } } public interface IEventListenerWithKey : IEventListenerWithKey { public new TKey Id { get; init; } - object IEventListenerWithKey.Id - { - get => Id!; - init => Id = (TKey)value; - } + object IEventListenerWithKey.Id => Id!; } \ No newline at end of file diff --git a/src/Fluss/Events/EventListenerFactory.cs b/src/Fluss/Events/EventListenerFactory.cs index 77caedf..f35350e 100644 --- a/src/Fluss/Events/EventListenerFactory.cs +++ b/src/Fluss/Events/EventListenerFactory.cs @@ -3,18 +3,11 @@ namespace Fluss.Events; -public sealed class EventListenerFactory : IEventListenerFactory +public sealed class EventListenerFactory(IEventRepository eventRepository) : IEventListenerFactory { - private readonly IEventRepository _eventRepository; - - public EventListenerFactory(IEventRepository eventRepository) - { - _eventRepository = eventRepository; - } - public async ValueTask UpdateTo(TEventListener eventListener, long to) where TEventListener : EventListener { - var events = await _eventRepository.GetEvents(eventListener.Tag.LastSeen, to); + var events = await eventRepository.GetEvents(eventListener.Tag.LastSeen, to); return UpdateWithEvents(eventListener, events); } diff --git a/src/Fluss/Events/EventMemoryArrayExtensions.cs b/src/Fluss/Events/EventMemoryArrayExtensions.cs index 165d0b9..39d47b6 100644 --- a/src/Fluss/Events/EventMemoryArrayExtensions.cs +++ b/src/Fluss/Events/EventMemoryArrayExtensions.cs @@ -12,12 +12,10 @@ public static ReadOnlyCollection> ToPagedMemory casted.ToArray().AsMemory().AsReadOnly() }.AsReadOnly(); } - else - { - return new[] { - envelopes.Cast().ToArray().AsMemory().AsReadOnly() - }.AsReadOnly(); - } + + return new[] { + envelopes.Cast().ToArray().AsMemory().AsReadOnly() + }.AsReadOnly(); } public static IReadOnlyList ToFlatEventList(this ReadOnlyCollection> pagedMemory) diff --git a/src/Fluss/Events/EventRepository.cs b/src/Fluss/Events/EventRepository.cs index e2e899d..9b7283f 100644 --- a/src/Fluss/Events/EventRepository.cs +++ b/src/Fluss/Events/EventRepository.cs @@ -12,9 +12,7 @@ public interface IEventRepository ValueTask GetLatestVersion(); } -public interface IBaseEventRepository : IEventRepository -{ -} +public interface IBaseEventRepository : IEventRepository; public abstract class EventRepositoryPipeline : IEventRepository { diff --git a/src/Fluss/Events/InMemoryEventCache.cs b/src/Fluss/Events/InMemoryEventCache.cs index 5b7dd35..513fcc0 100644 --- a/src/Fluss/Events/InMemoryEventCache.cs +++ b/src/Fluss/Events/InMemoryEventCache.cs @@ -2,18 +2,12 @@ namespace Fluss.Events; -public class InMemoryEventCache : EventRepositoryPipeline +public class InMemoryEventCache(long cacheSizePerItem = 10_000) : EventRepositoryPipeline { - private readonly long _cacheSizePerItem; - private readonly List _cache = new(); + private readonly List _cache = []; private long _lastKnownVersion = -1; private readonly SemaphoreSlim _loadLock = new(1, 1); - public InMemoryEventCache(long cacheSizePerItem = 10_000) - { - _cacheSizePerItem = cacheSizePerItem; - } - public override async ValueTask>> GetEvents(long fromExclusive, long toInclusive) { @@ -35,19 +29,19 @@ public override async ValueTask { return new[] { _cache[fromItemId] - .AsMemory((int)((fromExclusive + 1) % _cacheSizePerItem), (int)(toInclusive - fromExclusive)).AsReadOnly() + .AsMemory((int)((fromExclusive + 1) % cacheSizePerItem), (int)(toInclusive - fromExclusive)).AsReadOnly() }.AsReadOnly(); } var result = new ReadOnlyMemory[toItemId - fromItemId + 1]; - result[0] = _cache[fromItemId].AsMemory((int)((fromExclusive + 1) % _cacheSizePerItem)); + result[0] = _cache[fromItemId].AsMemory((int)((fromExclusive + 1) % cacheSizePerItem)); for (var i = fromItemId + 1; i < toItemId; i++) { result[i - fromItemId] = _cache[i].AsMemory(); } - result[^1] = _cache[toItemId].AsMemory(0, (int)(toInclusive % _cacheSizePerItem) + 1); + result[^1] = _cache[toItemId].AsMemory(0, (int)(toInclusive % cacheSizePerItem) + 1); return result.AsReadOnly(); } @@ -97,12 +91,12 @@ public override async ValueTask Publish(IEnumerable events) private int GetCacheKey(long i) { - return (int)(i / _cacheSizePerItem); + return (int)(i / cacheSizePerItem); } private long MinItemForCache(int itemId) { - return _cacheSizePerItem * itemId; + return cacheSizePerItem * itemId; } private void AddEvents(ReadOnlyCollection> eventEnvelopes) @@ -120,7 +114,7 @@ private void AddEvents(ReadOnlyCollection> eventEn var cacheKey = GetCacheKey(eventEnvelope.Version); while (_cache.Count <= cacheKey) { - _cache.Add(new EventEnvelope[_cacheSizePerItem]); + _cache.Add(new EventEnvelope[cacheSizePerItem]); } _cache[cacheKey][eventEnvelope.Version - MinItemForCache(cacheKey)] = eventEnvelope; diff --git a/src/Fluss/Events/InMemoryEventRepository.cs b/src/Fluss/Events/InMemoryEventRepository.cs index a85519e..be6db6e 100644 --- a/src/Fluss/Events/InMemoryEventRepository.cs +++ b/src/Fluss/Events/InMemoryEventRepository.cs @@ -6,7 +6,7 @@ namespace Fluss.Events; public class InMemoryEventRepository : IBaseEventRepository { - private readonly List _events = new(); + private readonly List _events = []; public event EventHandler? NewEvents; public ValueTask Publish(IEnumerable eventEnvelopes) diff --git a/src/Fluss/Events/TransientEvents/TransientEvent.cs b/src/Fluss/Events/TransientEvents/TransientEvent.cs index 62a2433..0c97c82 100644 --- a/src/Fluss/Events/TransientEvents/TransientEvent.cs +++ b/src/Fluss/Events/TransientEvents/TransientEvent.cs @@ -2,15 +2,15 @@ namespace Fluss.Events.TransientEvents; /// An Event containing temporary information. /// A TransientEvent will not be persisted. -public interface TransientEvent : Event { } +public interface TransientEvent : Event; public record TransientEventEnvelope : EventEnvelope { public DateTimeOffset ExpiresAt { get; init; } } -public class ExpiresAfterAttribute : Attribute +[AttributeUsage(AttributeTargets.Class)] +public class ExpiresAfterAttribute(double ms) : Attribute { - public double Ms { get; } - public ExpiresAfterAttribute(double ms) => this.Ms = ms; + public double Ms { get; } = ms; } diff --git a/src/Fluss/Events/TransientEvents/TransientEventAwareEventListenerFactory.cs b/src/Fluss/Events/TransientEvents/TransientEventAwareEventListenerFactory.cs index 6d47d73..03eb44d 100644 --- a/src/Fluss/Events/TransientEvents/TransientEventAwareEventListenerFactory.cs +++ b/src/Fluss/Events/TransientEvents/TransientEventAwareEventListenerFactory.cs @@ -1,18 +1,12 @@ namespace Fluss.Events.TransientEvents; -public class TransientEventAwareEventListenerFactory : EventListenerFactoryPipeline +public class TransientEventAwareEventListenerFactory(TransientEventAwareEventRepository transientEventRepository) + : EventListenerFactoryPipeline { - private readonly TransientEventAwareEventRepository _transientEventRepository; - - public TransientEventAwareEventListenerFactory(TransientEventAwareEventRepository transientEventRepository) - { - _transientEventRepository = transientEventRepository; - } - public override async ValueTask UpdateTo(TEventListener eventListener, long to) { var next = await Next.UpdateTo(eventListener, to); - var transientEvents = _transientEventRepository.GetCurrentTransientEvents(); + var transientEvents = transientEventRepository.GetCurrentTransientEvents(); return UpdateWithEvents(next, transientEvents); } diff --git a/src/Fluss/Events/TransientEvents/TransientEventAwareEventRepository.cs b/src/Fluss/Events/TransientEvents/TransientEventAwareEventRepository.cs index 81f721c..dcac24f 100644 --- a/src/Fluss/Events/TransientEvents/TransientEventAwareEventRepository.cs +++ b/src/Fluss/Events/TransientEvents/TransientEventAwareEventRepository.cs @@ -5,10 +5,10 @@ namespace Fluss.Events.TransientEvents; public sealed class TransientEventAwareEventRepository : EventRepositoryPipeline { - private readonly List _transientEvents = new(); + private readonly List _transientEvents = []; private long _transientEventVersion; - private bool _cleanTaskIsRunning = false; - private bool _anotherCleanTaskRequired = false; + private bool _cleanTaskIsRunning; + private bool _anotherCleanTaskRequired; public event EventHandler? NewTransientEvents; @@ -25,7 +25,7 @@ public ReadOnlyCollection> GetCurrentTransientEven public override async ValueTask Publish(IEnumerable events) { var eventEnvelopes = events.ToList(); - if (!eventEnvelopes.Any()) return; + if (eventEnvelopes.Count == 0) return; var transientEventEnvelopes = eventEnvelopes.Where(e => e.Event is TransientEvent); diff --git a/src/Fluss/Exceptions/RetryException.cs b/src/Fluss/Exceptions/RetryException.cs index 917a260..6455620 100644 --- a/src/Fluss/Exceptions/RetryException.cs +++ b/src/Fluss/Exceptions/RetryException.cs @@ -1,6 +1,3 @@ namespace Fluss.Exceptions; -public class RetryException : Exception -{ - public RetryException() : base("This operation needs to be retried") { } -} +public class RetryException() : Exception("This operation needs to be retried"); diff --git a/src/Fluss/Extensions/ValueTaskExtensions.cs b/src/Fluss/Extensions/ValueTaskExtensions.cs deleted file mode 100644 index 53dda75..0000000 --- a/src/Fluss/Extensions/ValueTaskExtensions.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Fluss.Extensions; - -public static class ValueTaskExtensions -{ - public static async ValueTask AnyAsync(this IEnumerable> valueTasks) - { - foreach (var valueTask in valueTasks) - { - if (await valueTask) - { - return true; - } - } - - return false; - } - - public static async ValueTask AllAsync(this IEnumerable> valueTasks) - { - foreach (var valueTask in valueTasks) - { - if (!await valueTask) - { - return false; - } - } - - return true; - } - - public static T GetResult(this ValueTask valueTask) - { - var task = Task.Run(async () => await valueTask); - return task.GetAwaiter().GetResult(); - } - - public static void GetResult(this ValueTask valueTask) - { - var task = Task.Run(async () => await valueTask); - task.GetAwaiter().GetResult(); - } -} diff --git a/src/Fluss/Fluss.csproj b/src/Fluss/Fluss.csproj index 2dae398..d58ef06 100644 --- a/src/Fluss/Fluss.csproj +++ b/src/Fluss/Fluss.csproj @@ -1,22 +1,23 @@  - net8.0; net7.0 + net8.0 enable enable Fluss ATMINA Solutions GmbH + true - - - - - - - - + + + + + + + + diff --git a/src/Fluss/Fluss.csproj.DotSettings b/src/Fluss/Fluss.csproj.DotSettings index 3755a63..322bd61 100644 --- a/src/Fluss/Fluss.csproj.DotSettings +++ b/src/Fluss/Fluss.csproj.DotSettings @@ -1,2 +1,4 @@ - + True \ No newline at end of file diff --git a/src/Fluss/ReadModel/ReadModel.cs b/src/Fluss/ReadModel/ReadModel.cs index 3b66e7a..adb99c2 100644 --- a/src/Fluss/ReadModel/ReadModel.cs +++ b/src/Fluss/ReadModel/ReadModel.cs @@ -2,13 +2,9 @@ namespace Fluss.ReadModel; -public interface IReadModel -{ -} +public interface IReadModel; -public abstract record RootReadModel : EventListener, IRootEventListener, IReadModel -{ -} +public abstract record RootReadModel : EventListener, IRootEventListener, IReadModel; public abstract record ReadModelWithKey : EventListener, IEventListenerWithKey, IReadModel { diff --git a/src/Fluss/ServiceCollectionExtensions.cs b/src/Fluss/ServiceCollectionExtensions.cs index 4f8d189..dbeb120 100644 --- a/src/Fluss/ServiceCollectionExtensions.cs +++ b/src/Fluss/ServiceCollectionExtensions.cs @@ -69,10 +69,7 @@ public static IServiceCollection AddEventRepositoryPipeline(th public static IServiceCollection AddBaseEventRepository(this IServiceCollection services) where TBaseEventRepository : class, IBaseEventRepository { - if (services is null) - { - throw new ArgumentNullException(nameof(services)); - } + ArgumentNullException.ThrowIfNull(services); return services .AddSingleton() diff --git a/src/Fluss/SideEffects/SideEffect.cs b/src/Fluss/SideEffects/SideEffect.cs index ffaa40d..57f749a 100644 --- a/src/Fluss/SideEffects/SideEffect.cs +++ b/src/Fluss/SideEffects/SideEffect.cs @@ -2,9 +2,7 @@ namespace Fluss.SideEffects; -public interface SideEffect -{ -} +public interface SideEffect; public interface SideEffect : SideEffect where T : Event { diff --git a/src/Fluss/SideEffects/SideEffectDispatcher.cs b/src/Fluss/SideEffects/SideEffectDispatcher.cs index 56a2a63..c353517 100644 --- a/src/Fluss/SideEffects/SideEffectDispatcher.cs +++ b/src/Fluss/SideEffects/SideEffectDispatcher.cs @@ -87,13 +87,14 @@ private void CacheSideEffects(IEnumerable sideEffects) foreach (var sideEffect in sideEffects) { var eventType = sideEffect.GetType().GetInterface(typeof(SideEffect<>).Name)!.GetGenericArguments()[0]; - if (!_sideEffectCache.ContainsKey(eventType)) + if (!_sideEffectCache.TryGetValue(eventType, out var value)) { - _sideEffectCache[eventType] = new List<(SideEffect, MethodInfo)>(); + value = []; + _sideEffectCache[eventType] = value; } var method = sideEffect.GetType().GetMethod(nameof(SideEffect.HandleAsync))!; - _sideEffectCache[eventType].Add((sideEffect, method)); + value.Add((sideEffect, method)); } } @@ -122,8 +123,7 @@ await unitOfWorkFactory.Commit(async unitOfWork => long? version = envelope.Event is not TransientEvent ? envelope.Version : null; var versionedUnitOfWork = unitOfWork.WithPrefilledVersion(version); - var invocationResult = handleAsync.Invoke(sideEffect, - new object?[] { envelope.Event, versionedUnitOfWork }); + var invocationResult = handleAsync.Invoke(sideEffect, [envelope.Event, versionedUnitOfWork]); if (invocationResult is not Task> resultTask) { throw new Exception( diff --git a/src/Fluss/UnitOfWork/IWriteUnitOfWork.cs b/src/Fluss/UnitOfWork/IWriteUnitOfWork.cs index 6977f4d..8b069eb 100644 --- a/src/Fluss/UnitOfWork/IWriteUnitOfWork.cs +++ b/src/Fluss/UnitOfWork/IWriteUnitOfWork.cs @@ -1,7 +1,5 @@ -using System.Collections.Concurrent; using Fluss.Aggregates; using Fluss.Events; -using Fluss.ReadModel; namespace Fluss; diff --git a/src/Fluss/UnitOfWork/UnitOfWork.Aggregates.cs b/src/Fluss/UnitOfWork/UnitOfWork.Aggregates.cs index 05388bc..6ea222a 100644 --- a/src/Fluss/UnitOfWork/UnitOfWork.Aggregates.cs +++ b/src/Fluss/UnitOfWork/UnitOfWork.Aggregates.cs @@ -1,12 +1,12 @@ using System.Collections.Concurrent; using Fluss.Aggregates; using Fluss.Events; +// ReSharper disable LoopCanBeConvertedToQuery namespace Fluss; public partial class UnitOfWork { - private readonly List _aggregateRoots = new(); public ConcurrentQueue PublishedEventEnvelopes { get; } = new(); public async ValueTask GetAggregate() where TAggregate : AggregateRoot, new() @@ -25,8 +25,6 @@ public partial class UnitOfWork aggregate = (TAggregate)aggregate.WhenInt(publishedEventEnvelope); } - _aggregateRoots.Add(aggregate); - return aggregate; } @@ -47,8 +45,6 @@ public async ValueTask GetAggregate(TKey key) aggregate = (TAggregate)aggregate.WhenInt(publishedEventEnvelope); } - _aggregateRoots.Add(aggregate); - return aggregate; } @@ -82,9 +78,7 @@ private async ValueTask ValidateEventResult(EventEnvelope envelope, T? aggreg // It's possible that the given aggregate does not have all necessary events applied yet. aggregate = await UpdateAndApplyPublished(aggregate, null); - var result = aggregate.WhenInt(envelope) as T; - - if (result == null || result == aggregate) return; + if (aggregate.WhenInt(envelope) is not T result || result == aggregate) return; await _validator.ValidateAggregate(result, this); } diff --git a/src/Fluss/UnitOfWork/UnitOfWork.ReadModels.cs b/src/Fluss/UnitOfWork/UnitOfWork.ReadModels.cs index f2261e4..492c3c1 100644 --- a/src/Fluss/UnitOfWork/UnitOfWork.ReadModels.cs +++ b/src/Fluss/UnitOfWork/UnitOfWork.ReadModels.cs @@ -6,7 +6,7 @@ namespace Fluss; public partial class UnitOfWork { - private readonly ConcurrentBag _readModels = new(); + private readonly ConcurrentBag _readModels = []; public IReadOnlyCollection ReadModels => _readModels; public async ValueTask GetReadModel(Type tReadModel, object? key, long? at = null) diff --git a/src/Fluss/UnitOfWork/UnitOfWorkFactory.cs b/src/Fluss/UnitOfWork/UnitOfWorkFactory.cs index bcbca28..21869ce 100644 --- a/src/Fluss/UnitOfWork/UnitOfWorkFactory.cs +++ b/src/Fluss/UnitOfWork/UnitOfWorkFactory.cs @@ -6,15 +6,8 @@ namespace Fluss; -public class UnitOfWorkFactory +public class UnitOfWorkFactory(IServiceProvider serviceProvider) { - private readonly IServiceProvider _serviceProvider; - - public UnitOfWorkFactory(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } - private static readonly IList Delay = Backoff .DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromMilliseconds(1), retryCount: 100) .Select(s => TimeSpan.FromTicks(Math.Min(s.Ticks, TimeSpan.FromSeconds(1).Ticks))).ToList(); @@ -30,7 +23,7 @@ public async ValueTask Commit(Func action) await RetryPolicy .ExecuteAsync(async () => { - var unitOfWork = _serviceProvider.GetRequiredService(); + var unitOfWork = serviceProvider.GetRequiredService(); await action(unitOfWork); await unitOfWork.CommitInternal(); }); @@ -43,7 +36,7 @@ public async ValueTask Commit(Func> action) return await RetryPolicy .ExecuteAsync(async () => { - var unitOfWork = _serviceProvider.GetRequiredService(); + var unitOfWork = serviceProvider.GetRequiredService(); var result = await action(unitOfWork); await unitOfWork.CommitInternal(); return result; diff --git a/src/Fluss/UnitOfWork/UnitOfWorkRecordingProxy.cs b/src/Fluss/UnitOfWork/UnitOfWorkRecordingProxy.cs index 12678f1..b5a9001 100644 --- a/src/Fluss/UnitOfWork/UnitOfWorkRecordingProxy.cs +++ b/src/Fluss/UnitOfWork/UnitOfWorkRecordingProxy.cs @@ -4,63 +4,56 @@ namespace Fluss; -public class UnitOfWorkRecordingProxy : IUnitOfWork +public class UnitOfWorkRecordingProxy(IUnitOfWork impl) : IUnitOfWork { - private readonly IUnitOfWork _impl; - - public UnitOfWorkRecordingProxy(IUnitOfWork impl) - { - _impl = impl; - } - public ValueTask ConsistentVersion() { - return _impl.ConsistentVersion(); + return impl.ConsistentVersion(); } - public IReadOnlyCollection ReadModels => _impl.ReadModels; - public ConcurrentQueue PublishedEventEnvelopes => _impl.PublishedEventEnvelopes; + public IReadOnlyCollection ReadModels => impl.ReadModels; + public ConcurrentQueue PublishedEventEnvelopes => impl.PublishedEventEnvelopes; - public List RecordedListeners { get; } = new List(); + public List RecordedListeners { get; } = []; - public ValueTask GetReadModel(Type tReadModel, object key, long? at = null) + public ValueTask GetReadModel(Type tReadModel, object? key, long? at = null) { - return _impl.GetReadModel(tReadModel, key, at); + return impl.GetReadModel(tReadModel, key, at); } public ValueTask GetReadModel(long? at = null) where TReadModel : EventListener, IRootEventListener, IReadModel, new() { - return Record(_impl.GetReadModel(at)); + return Record(impl.GetReadModel(at)); } public ValueTask GetReadModel(TKey key, long? at = null) where TReadModel : EventListener, IEventListenerWithKey, IReadModel, new() { - return Record(_impl.GetReadModel(key, at)); + return Record(impl.GetReadModel(key, at)); } public ValueTask UnsafeGetReadModelWithoutAuthorization(long? at = null) where TReadModel : EventListener, IRootEventListener, IReadModel, new() { - return Record(_impl.UnsafeGetReadModelWithoutAuthorization(at)); + return Record(impl.UnsafeGetReadModelWithoutAuthorization(at)); } public ValueTask UnsafeGetReadModelWithoutAuthorization(TKey key, long? at = null) where TReadModel : EventListener, IEventListenerWithKey, IReadModel, new() { - return Record(_impl.UnsafeGetReadModelWithoutAuthorization(key, at)); + return Record(impl.UnsafeGetReadModelWithoutAuthorization(key, at)); } public ValueTask> GetMultipleReadModels(IEnumerable keys, long? at = null) where TReadModel : EventListener, IReadModel, IEventListenerWithKey, new() where TKey : notnull { - return Record(_impl.GetMultipleReadModels(keys, at)); + return Record(impl.GetMultipleReadModels(keys, at)); } public ValueTask> UnsafeGetMultipleReadModelsWithoutAuthorization(IEnumerable keys, long? at = null) where TReadModel : EventListener, IReadModel, IEventListenerWithKey, new() where TKey : notnull { - return Record(_impl.UnsafeGetMultipleReadModelsWithoutAuthorization(keys, at)); + return Record(impl.UnsafeGetMultipleReadModelsWithoutAuthorization(keys, at)); } public IUnitOfWork WithPrefilledVersion(long? version) { - return _impl.WithPrefilledVersion(version); + return impl.WithPrefilledVersion(version); } private async ValueTask Record(ValueTask readModel) where TReadModel : EventListener diff --git a/src/Fluss/Upcasting/EventUpcasterService.cs b/src/Fluss/Upcasting/EventUpcasterService.cs index bad600b..484c598 100644 --- a/src/Fluss/Upcasting/EventUpcasterService.cs +++ b/src/Fluss/Upcasting/EventUpcasterService.cs @@ -8,30 +8,24 @@ public interface AwaitableService public Task WaitForCompletionAsync(); } -public class EventUpcasterService : AwaitableService +public class EventUpcasterService( + IEnumerable upcasters, + UpcasterSorter sorter, + IEventRepository eventRepository, + ILogger logger) + : AwaitableService { - private List _sortedUpcasters; - private IEventRepository _eventRepository; - private ILogger _logger; + private readonly List _sortedUpcasters = sorter.SortByDependencies(upcasters); - private CancellationTokenSource _onCompletedSource; - - public EventUpcasterService(IEnumerable upcasters, UpcasterSorter sorter, IEventRepository eventRepository, ILogger logger) - { - _sortedUpcasters = sorter.SortByDependencies(upcasters); - _eventRepository = eventRepository; - _logger = logger; - - _onCompletedSource = new CancellationTokenSource(); - } + private readonly CancellationTokenSource _onCompletedSource = new(); public async ValueTask Run() { - var events = await _eventRepository.GetRawEvents(); + var events = await eventRepository.GetRawEvents(); foreach (var upcaster in _sortedUpcasters) { - _logger.LogInformation("Running Upcaster {UpcasterName}", upcaster.GetType().Name); + logger.LogInformation("Running Upcaster {UpcasterName}", upcaster.GetType().Name); var upcastedEvents = new List(); @@ -45,7 +39,7 @@ public async ValueTask Run() } var envelopes = upcastResult.Select((json, i) => new RawEventEnvelope { RawEvent = json, At = @event.At, By = @event.By, Version = upcastedEvents.Count + i }).ToList(); - await _eventRepository.ReplaceEvent(upcastedEvents.Count, envelopes); + await eventRepository.ReplaceEvent(upcastedEvents.Count, envelopes); upcastedEvents.AddRange(envelopes); } diff --git a/src/Fluss/Upcasting/IUpcaster.cs b/src/Fluss/Upcasting/IUpcaster.cs index 0f0b447..9b06c9e 100644 --- a/src/Fluss/Upcasting/IUpcaster.cs +++ b/src/Fluss/Upcasting/IUpcaster.cs @@ -8,10 +8,8 @@ public interface IUpcaster public IEnumerable? Upcast(JObject eventJson); } -public class DependsOnAttribute : Attribute +[AttributeUsage(AttributeTargets.Class)] +public class DependsOnAttribute(params Type[] upcasters) : Attribute { - public ImmutableHashSet Dependencies { get; private set; } - - public DependsOnAttribute(params Type[] upcasters) => - Dependencies = ImmutableHashSet.Empty.Union(upcasters); + public ImmutableHashSet Dependencies { get; private set; } = ImmutableHashSet.Empty.Union(upcasters); } diff --git a/src/Fluss/Upcasting/UpcasterSorter.cs b/src/Fluss/Upcasting/UpcasterSorter.cs index a95eb3b..f6a49a8 100644 --- a/src/Fluss/Upcasting/UpcasterSorter.cs +++ b/src/Fluss/Upcasting/UpcasterSorter.cs @@ -2,15 +2,11 @@ namespace Fluss.Upcasting; -public class UpcasterSortException : Exception -{ - public UpcasterSortException() : base( - $"Failed to sort Upcasters in {UpcasterSorter.MaxIterations} iterations. Ensure the following:\n" + - " - There are no cyclic dependencies\n" + - $" - All dependencies implement {nameof(IUpcaster)}\n" + - " - All upcasters are placed in the same Assembly") - { } -} +public class UpcasterSortException() : Exception( + $"Failed to sort Upcasters in {UpcasterSorter.MaxIterations} iterations. Ensure the following:\n" + + " - There are no cyclic dependencies\n" + + $" - All dependencies implement {nameof(IUpcaster)}\n" + + " - All upcasters are placed in the same Assembly"); public class UpcasterSorter { @@ -20,13 +16,13 @@ public List SortByDependencies(IEnumerable upcasters) { var remaining = upcasters.ToHashSet(); - var result = remaining.Where(t => GetDependencies(t).Count() == 0).ToList(); + var result = remaining.Where(t => !GetDependencies(t).Any()).ToList(); var includedTypes = result.Select(u => u.GetType()).ToHashSet(); remaining.ExceptWith(result); var remainingIterations = MaxIterations; - while (remaining.Any()) + while (remaining.Count != 0) { // This approach is not the most performant admittedly but it works :) var next = remaining.Where(t => GetDependencies(t).All(d => includedTypes.Contains(d))).ToList(); diff --git a/src/Fluss/Validation/AggregateValidator.cs b/src/Fluss/Validation/AggregateValidator.cs index a4b3c1a..0274166 100644 --- a/src/Fluss/Validation/AggregateValidator.cs +++ b/src/Fluss/Validation/AggregateValidator.cs @@ -2,7 +2,7 @@ namespace Fluss.Validation; -public interface AggregateValidator { } +public interface AggregateValidator; public interface AggregateValidator : AggregateValidator where T : AggregateRoot { diff --git a/src/Fluss/Validation/EventValidator.cs b/src/Fluss/Validation/EventValidator.cs index 156adee..469a6f5 100644 --- a/src/Fluss/Validation/EventValidator.cs +++ b/src/Fluss/Validation/EventValidator.cs @@ -2,7 +2,7 @@ namespace Fluss.Validation; -public interface EventValidator { } +public interface EventValidator; public interface EventValidator : EventValidator where T : Event { diff --git a/src/Fluss/Validation/RootValidator.cs b/src/Fluss/Validation/RootValidator.cs index 24572ca..7fe8757 100644 --- a/src/Fluss/Validation/RootValidator.cs +++ b/src/Fluss/Validation/RootValidator.cs @@ -29,13 +29,14 @@ private void CacheAggregateValidators(IEnumerable validators foreach (var validator in validators) { var aggregateType = validator.GetType().GetInterface(typeof(AggregateValidator<>).Name)!.GetGenericArguments()[0]; - if (!_aggregateValidators.ContainsKey(aggregateType)) + if (!_aggregateValidators.TryGetValue(aggregateType, out List<(AggregateValidator validator, MethodInfo handler)>? value)) { - _aggregateValidators[aggregateType] = new List<(AggregateValidator, MethodInfo)>(); + value = new List<(AggregateValidator, MethodInfo)>(); + _aggregateValidators[aggregateType] = value; } var method = validator.GetType().GetMethod(nameof(AggregateValidator.ValidateAsync))!; - _aggregateValidators[aggregateType].Add((validator, method)); + value.Add((validator, method)); } } @@ -74,7 +75,7 @@ public async Task ValidateEvent(EventEnvelope envelope, IReadOnlyList - v.handler.Invoke(v.validator, new object?[] { envelope.Event, versionedUnitOfWork })); + v.handler.Invoke(v.validator, [envelope.Event, versionedUnitOfWork])); await Task.WhenAll(invocations.Cast().Select(async x => await x)); } @@ -97,7 +98,7 @@ public async Task ValidateAggregate(AggregateRoot aggregate, UnitOfWork unitOfWo try { - var invocations = validator.Select(v => v.handler.Invoke(v.validator, new object?[] { aggregate, unitOfWork })); + var invocations = validator.Select(v => v.handler.Invoke(v.validator, [aggregate, unitOfWork])); await Task.WhenAll(invocations.Cast().Select(async x => await x)); } catch (TargetInvocationException targetInvocationException)