Skip to content
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
119 changes: 119 additions & 0 deletions src/Marten.Testing/Events/CustomAggregatorLookupTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Baseline;
using Marten.Events;
using Marten.Events.Projections;
using Marten.Util;
using Shouldly;
using Xunit;

namespace Marten.Testing.Events
{
public class CustomAggregatorLookupTests
{
private readonly EventGraph theGraph = new EventGraph(new StoreOptions());

public CustomAggregatorLookupTests()
{
theGraph.UseAggregatorLookup(new AggregatorLookup(type => typeof(AggregatorUsePrivateApply<>).CloseAndBuildAs<IAggregator>(type)));
}

[Fact]
public void can_lookup_private_apply_methods()
{
var aggregator = theGraph.AggregateFor<AggregateWithPrivateEventApply>();

var stream = new EventStream(Guid.NewGuid(), false)
.Add(new QuestStarted {Name = "Destroy the Ring"});

var party = aggregator.Build(stream.Events, null);

party.Name.ShouldBe("Destroy the Ring");
}
}

public class AggregatorUsePrivateApply<T> : IAggregator<T> where T : class, new()
{
public static readonly string ApplyMethod = "Apply";

private readonly IDictionary<Type, object> _aggregations = new Dictionary<Type, object>();


public AggregatorUsePrivateApply()
{
typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(x => x.Name == ApplyMethod && x.GetParameters().Length == 1)
.Each(method =>
{
var eventType = method.GetParameters().Single<ParameterInfo>().ParameterType;
var step = typeof(AggregationStep<,>)
.CloseAndBuildAs<object>(method, typeof(T), eventType);

_aggregations.Add(eventType, step);
});

Alias = typeof(T).Name.ToTableAlias();
}

public Type AggregateType => typeof(T);

public string Alias { get; }

public T Build(IEnumerable<IEvent> events, IDocumentSession session)
{
var state = new T();

events.Each(x => x.Apply(state, this));

return state;
}

public Type[] EventTypes => _aggregations.Keys.ToArray();

public AggregatorUsePrivateApply<T> Add<TEvent>(IAggregation<T, TEvent> aggregation)
{
if (_aggregations.ContainsKey(typeof(TEvent)))
{
_aggregations[typeof(TEvent)] = aggregation;
}
else
{
_aggregations.Add(typeof(TEvent), aggregation);
}

return this;
}

public AggregatorUsePrivateApply<T> Add<TEvent>(Action<T, TEvent> application)
{
return Add(new AggregationStep<T, TEvent>(application));
}

public IAggregation<T, TEvent> AggregatorFor<TEvent>()
{
return _aggregations.ContainsKey(typeof(TEvent))
? _aggregations[typeof(TEvent)].As<IAggregation<T, TEvent>>()
: null;
}


public bool AppliesTo(EventStream stream)
{
return stream.Events.Any(x => _aggregations.ContainsKey(x.Data.GetType()));
}
}

public class AggregateWithPrivateEventApply
{
public Guid Id { get; set; }

private void Apply(QuestStarted started)
{
Name = started.Name;
}

public string Name { get; private set; }
}
}
18 changes: 14 additions & 4 deletions src/Marten/Events/EventGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ public class EventGraph
private readonly ConcurrentCache<string, EventMapping> _byEventName = new ConcurrentCache<string, EventMapping>();
private readonly ConcurrentCache<Type, EventMapping> _events = new ConcurrentCache<Type, EventMapping>();


private IAggregatorLookup _aggregatorLookup;
private string _databaseSchemaName;

public EventGraph(StoreOptions options)
{
Options = options;
_aggregatorLookup = new AggregatorLookup();
_events.OnMissing = eventType =>
{
var mapping = typeof(EventMapping<>).CloseAndBuildAs<EventMapping>(this, eventType);
Expand All @@ -38,7 +39,7 @@ public EventGraph(StoreOptions options)
SchemaObjects = new EventStoreDatabaseObjects(this);

InlineProjections = new ProjectionCollection(options);
AsyncProjections = new ProjectionCollection(options);
AsyncProjections = new ProjectionCollection(options);
}

internal StoreOptions Options { get; }
Expand Down Expand Up @@ -104,7 +105,7 @@ public string DatabaseSchemaName
.GetOrAdd(typeof(T), type =>
{
Options.MappingFor(typeof(T));
return new Aggregator<T>();
return _aggregatorLookup.Lookup<T>();
})
.As<IAggregator<T>>();
}
Expand All @@ -124,12 +125,21 @@ public Type AggregateTypeFor(string aggregateTypeName)
public string AggregateAliasFor(Type aggregateType)
{
return _aggregates
.GetOrAdd(aggregateType, type => typeof(Aggregator<>).CloseAndBuildAs<IAggregator>(aggregateType)).Alias;
.GetOrAdd(aggregateType, type => _aggregatorLookup.Lookup(type)).Alias;
}

public IProjection ProjectionFor(Type viewType)
{
return AsyncProjections.ForView(viewType) ?? InlineProjections.ForView(viewType);
}

/// <summary>
/// Set default strategy to lookup IAggregator when no explicit IAggregator registration exists.
/// </summary>
/// <remarks>Unless called, <see cref="AggregatorLookup"/> is used</remarks>
public void UseAggregatorLookup(IAggregatorLookup aggregatorLookup)
{
_aggregatorLookup = aggregatorLookup;
}
}
}
30 changes: 30 additions & 0 deletions src/Marten/Events/Projections/AggregatorLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using Baseline;

namespace Marten.Events.Projections
{
/// <summary>
/// Default IAggregator lookup strategy. Defaults to <see cref="Aggregator{T}"/>
/// </summary>
public sealed class AggregatorLookup : IAggregatorLookup
{
private readonly Func<Type, IAggregator> factory;

/// <param name="factory">Factory for resolving IAggregator for the supplied type</param>
public AggregatorLookup(Func<Type, IAggregator> factory = null)
{
this.factory = factory;
}

public IAggregator<T> Lookup<T>() where T : class, new()
{
// trade null check for the cost of using default factory with Activator.CreateInstance
return (IAggregator<T>)factory?.Invoke(typeof(T)) ?? new Aggregator<T>();
}

public IAggregator Lookup(Type aggregateType)
{
return factory?.Invoke(aggregateType) ?? typeof(Aggregator<>).CloseAndBuildAs<IAggregator>(aggregateType);
}
}
}
19 changes: 19 additions & 0 deletions src/Marten/Events/Projections/IAggregatorLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace Marten.Events.Projections
{
/// <summary>
/// Used by <see cref="EventGraph"/> to resolve IAggregator when no explicit IAggregator registration exists
/// </summary>
public interface IAggregatorLookup
{
/// <summary>
/// Resolve aggregator for T
/// </summary>
IAggregator<T> Lookup<T>() where T : class, new();
/// <summary>
/// Resolve aggregator for aggregateType
/// </summary>
IAggregator Lookup(Type aggregateType);
}
}