Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Auto-discovery of event types, documents, and compiled queries #3247

Merged
merged 2 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/CoreTests/auto_register_specs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Marten.Events;
using Marten.Linq;
using Marten.Schema;
using Marten.Testing.Documents;
using Marten.Testing.Harness;
using Shouldly;
using Xunit;

namespace CoreTests;

public class auto_register_specs : OneOffConfigurationsContext
{
private readonly EventGraph theEvents;

public auto_register_specs()
{
StoreOptions(opts =>
{
opts.ApplicationAssembly = GetType().Assembly;
opts.AutoRegister(x =>
{
x.EventsImplement<IDomainEvent>();
x.EventsImplement<IIntegrationEvent>();
});
});

theEvents = (EventGraph)theStore.Options.Events;
}

[Fact]
public void discovered_events_based_on_filters()
{
theEvents.AllKnownEventTypes().ShouldContain(x => x.EventType == typeof(DomainEvent1));
theEvents.AllKnownEventTypes().ShouldContain(x => x.EventType == typeof(DomainEvent2));
theEvents.AllKnownEventTypes().ShouldContain(x => x.EventType == typeof(DomainEvent3));
theEvents.AllKnownEventTypes().ShouldContain(x => x.EventType == typeof(IntegrationEvent1));
theEvents.AllKnownEventTypes().ShouldContain(x => x.EventType == typeof(IntegrationEvent2));
}

[Fact]
public void find_events_by_attribute()
{
theEvents.AllKnownEventTypes().ShouldContain(x => x.EventType == typeof(IntegrationEvent3));

theEvents.EventMappingFor<IntegrationEvent4>()
.Alias.ShouldBe("four");
}

[Fact]
public void discover_documents_by_attribute()
{
theStore.Options.Storage.AllDocumentMappings.ShouldContain(x => x.DocumentType == typeof(DiscoveredDocument1));
theStore.Options.Storage.AllDocumentMappings.ShouldContain(x => x.DocumentType == typeof(DiscoveredDocument2));
}

[Fact]
public void finds_compiled_query_types()
{
theStore.Options.CompiledQueryTypes.ShouldContain(x => x == typeof(UsersByFirstName));
}
}

[MartenDocument]
public class DiscoveredDocument1
{
public Guid Id { get; set; }
}

[MartenDocument]
public class DiscoveredDocument2
{
public Guid Id { get; set; }
}

public interface IDomainEvent{}
public interface IIntegrationEvent{}

public class DomainEvent1: IDomainEvent;
public class DomainEvent2: IDomainEvent;
public class DomainEvent3: IDomainEvent;

public class IntegrationEvent1: IIntegrationEvent;
public class IntegrationEvent2: IIntegrationEvent;

[MartenEvent]
public class IntegrationEvent3{}

[MartenEvent(Alias = "four")]
public class IntegrationEvent4{}

public class UsersByFirstName: ICompiledListQuery<User>
{
public static int Count;
public string FirstName { get; set; }

public Expression<Func<IMartenQueryable<User>, IEnumerable<User>>> QueryIs()
{
return query => query.Where(x => x.FirstName == FirstName);
}
}


5 changes: 4 additions & 1 deletion src/Marten/Events/Aggregation/IAggregateProjection.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using Marten.Events.Daemon;
using Marten.Events.Projections;

namespace Marten.Events.Aggregation;
Expand All @@ -16,12 +17,14 @@ public interface IAggregateProjection // THIS NEEDS TO REMAIN PUBLIC

Type[] AllEventTypes { get; }

ProjectionLifecycle Lifecycle { get; }
ProjectionLifecycle Lifecycle { get; set; }

bool MatchesAnyDeleteType(StreamAction action);
bool MatchesAnyDeleteType(IEventSlice slice);
bool AppliesTo(IEnumerable<Type> eventTypes);

AsyncOptions Options { get; }


/// <summary>
/// Specify that this projection is a non 1 version of the original projection definition to opt
Expand Down
2 changes: 1 addition & 1 deletion src/Marten/Events/Projections/ProjectionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public abstract class ProjectionBase : EventFilterable
/// <summary>
/// The projection lifecycle that governs when this projection is executed
/// </summary>
public ProjectionLifecycle Lifecycle { get; internal set; } = ProjectionLifecycle.Async;
public ProjectionLifecycle Lifecycle { get; set; } = ProjectionLifecycle.Async;


/// <summary>
Expand Down
39 changes: 37 additions & 2 deletions src/Marten/Events/Projections/ProjectionOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ internal IProjection[] BuildInlineProjections(DocumentStore store)
/// <param name="lifecycle"></param>
/// <param name="projectionName">
/// Overwrite the named identity of this projection. This is valuable if using the projection
/// asynchonously
/// asynchronously
/// </param>
/// <param name="asyncConfiguration">
/// Optional configuration including teardown instructions for the usage of this
Expand Down Expand Up @@ -177,6 +177,41 @@ public void Add(
}
}

/// <summary>
/// Register a projection to the Marten configuration
/// </summary>
/// <param name="projection">Value values are Inline/Async, The default is Inline</param>
/// <param name="lifecycle"></param>
/// <param name="projectionName">
/// Overwrite the named identity of this projection. This is valuable if using the projection
/// asynchronously
/// </param>
/// <param name="asyncConfiguration">
/// Optional configuration including teardown instructions for the usage of this
/// projection within the async projection daempon
/// </param>
public void Register(
IProjectionSource source,
ProjectionLifecycle lifecycle,
Action<AsyncOptions> asyncConfiguration = null
)
{
if (source is ProjectionBase p)
{
p.AssembleAndAssertValidity();
p.Lifecycle = lifecycle;
}

if (lifecycle == ProjectionLifecycle.Live && source is IAggregateProjection aggregateProjection)
{
// Hack to address https://github.com/JasperFx/marten/issues/2610
_options.Storage.MappingFor(aggregateProjection.AggregateType).SkipSchemaGeneration = true;
}

asyncConfiguration?.Invoke(source.Options);
All.Add(source);
}

/// <summary>
/// Add a projection that will be executed inline
/// </summary>
Expand Down Expand Up @@ -296,7 +331,7 @@ public void Add<TProjection>(
}

/// <summary>
/// Register an aggregate projection that should be evaluated inline
/// Register an aggregate projection
/// </summary>
/// <param name="projection"></param>
/// <typeparam name="T"></typeparam>
Expand Down
8 changes: 7 additions & 1 deletion src/Marten/Linq/ICompiledQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
void SetUniqueValuesForQueryPlanning();
}

/// <summary>
/// Strictly a marker class to help Marten discover compiled query types
/// in assemblies
/// </summary>
public interface ICompiledQueryMarker{}

/// <summary>
/// Used to express a query expression that when used will be cached by class type implementing this interface
/// </summary>
Expand All @@ -23,7 +29,7 @@

#region sample_ICompiledQuery

public interface ICompiledQuery<TDoc, TOut> where TDoc: notnull
public interface ICompiledQuery<TDoc, TOut> : ICompiledQueryMarker where TDoc: notnull
{
Expression<Func<IMartenQueryable<TDoc>, TOut>> QueryIs();
}
Expand All @@ -38,7 +44,7 @@

#region sample_ICompiledListQuery-with-no-select

public interface ICompiledListQuery<TDoc>: ICompiledListQuery<TDoc, TDoc>

Check warning on line 47 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.

Check warning on line 47 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.

Check warning on line 47 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.

Check warning on line 47 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.
{
}

Expand All @@ -52,7 +58,7 @@

#region sample_ICompiledListQuery-with-select

public interface ICompiledListQuery<TDoc, TOut>: ICompiledQuery<TDoc, IEnumerable<TOut>>

Check warning on line 61 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.

Check warning on line 61 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.

Check warning on line 61 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.

Check warning on line 61 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.
{
}

Expand All @@ -65,7 +71,7 @@

#region sample_ICompiledQuery-for-single-doc

public interface ICompiledQuery<TDoc>: ICompiledQuery<TDoc, TDoc>

Check warning on line 74 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.

Check warning on line 74 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.

Check warning on line 74 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.

Check warning on line 74 in src/Marten/Linq/ICompiledQuery.cs

View workflow job for this annotation

GitHub Actions / build

The type 'TDoc' cannot be used as type parameter 'TDoc' in the generic type or method 'ICompiledQuery<TDoc, TOut>'. Nullability of type argument 'TDoc' doesn't match 'notnull' constraint.
{
}

Expand Down
4 changes: 2 additions & 2 deletions src/Marten/MartenServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,7 @@ public MartenConfigurationExpression AddProjectionWithServices<T>(ProjectionLife
ProjectionType = typeof(T)
};

opts.Projections.Add(projection, lifecycle);
opts.Projections.Register(projection, lifecycle);
});
break;
}
Expand Down Expand Up @@ -827,7 +827,7 @@ public MartenConfigurationExpression AddProjectionWithServices<T>(ProjectionLife
ProjectionName = projectionName
};

opts.Projections.Add(projection, lifecycle, projectionName);
opts.Projections.Register(projection, lifecycle);
});
break;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Marten/Schema/DatabaseSchemaNameAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Marten.Schema;
/// Overrides the database schema name for the document type
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class DatabaseSchemaNameAttribute: MartenAttribute
public class DatabaseSchemaNameAttribute: MartenDocumentAttribute
{
private readonly string _name;

Expand Down
2 changes: 1 addition & 1 deletion src/Marten/Schema/DocumentAliasAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Marten.Schema;
/// types
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class DocumentAliasAttribute: MartenAttribute
public class DocumentAliasAttribute : MartenDocumentAttribute
{
public DocumentAliasAttribute(string alias)
{
Expand Down
9 changes: 9 additions & 0 deletions src/Marten/Schema/MartenAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ public virtual void Modify(DocumentMapping mapping) { }
/// <param name="mapping"></param>
/// <param name="member"></param>
public virtual void Modify(DocumentMapping mapping, MemberInfo member) { }

/// <summary>
/// When used with the automatic type discovery (assembly scanning), this will be called
/// to make registrations to the Marten configuration with the type that this attribute
/// decorates
/// </summary>
/// <param name="discoveredType"></param>
/// <param name="options"></param>
public virtual void Register(Type discoveredType, StoreOptions options){}
}

#endregion
17 changes: 17 additions & 0 deletions src/Marten/Schema/MartenDocumentAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#nullable enable
using System;

namespace Marten.Schema;

/// <summary>
/// Just marks a type as being a persisted Marten document for the AutoRegister()
/// feature
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class MartenDocumentAttribute: MartenAttribute
{
public override void Register(Type discoveredType, StoreOptions options)
{
options.RegisterDocumentType(discoveredType);
}
}
28 changes: 28 additions & 0 deletions src/Marten/Schema/MartenEventAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#nullable enable
using System;
using JasperFx.Core;

namespace Marten.Schema;

/// <summary>
/// Just marks a type as being a persisted Marten event for the AutoRegister()
/// feature
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class MartenEventAttribute: MartenAttribute
{
/// <summary>
/// Use to override the Marten event type alias
/// </summary>
public string? Alias { get; set; }

public override void Register(Type discoveredType, StoreOptions options)
{
options.Events.AddEventType(discoveredType);
if (Alias.IsNotEmpty())
{

options.Events.MapEventType(discoveredType, Alias);
}
}
}
2 changes: 1 addition & 1 deletion src/Marten/Schema/SoftDeletedAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Marten.Schema;
/// Marks a document type as "soft deleted"
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class SoftDeletedAttribute: MartenAttribute
public class SoftDeletedAttribute: MartenDocumentAttribute
{
/// <summary>
/// Creates an index on deleted documents
Expand Down
2 changes: 1 addition & 1 deletion src/Marten/Schema/UseOptimisticConcurrencyAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Marten.Schema;
/// Directs Marten to use optimistic versioning checks when updating this document type
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class UseOptimisticConcurrencyAttribute: MartenAttribute
public class UseOptimisticConcurrencyAttribute: MartenDocumentAttribute
{
public override void Modify(DocumentMapping mapping)
{
Expand Down
Loading
Loading