Browse Source

Factory refactored.

pull/95/head
Sebastian Stehle 9 years ago
parent
commit
8448b26cb5
  1. 1
      Squidex.sln.DotSettings
  2. 81
      src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs
  3. 21
      src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs
  4. 2
      src/Squidex.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs
  5. 11
      src/Squidex.Infrastructure/Dispatching/ActionContextDispatcher.cs
  6. 11
      src/Squidex.Infrastructure/Dispatching/ActionDispatcher.cs
  7. 11
      src/Squidex.Infrastructure/Dispatching/FuncContextDispatcher.cs
  8. 11
      src/Squidex.Infrastructure/Dispatching/FuncDispatcher.cs
  9. 22
      src/Squidex.Infrastructure/Dispatching/Helper.cs
  10. 2
      src/Squidex/Config/Domain/WriteModule.cs
  11. 2
      src/Squidex/Pipeline/CommandHandlers/ETagCommandMiddleware.cs
  12. 2
      src/Squidex/Pipeline/CommandHandlers/EnrichWithActorCommandMiddleware.cs
  13. 2
      src/Squidex/Pipeline/CommandHandlers/EnrichWithAppIdCommandMiddleware.cs
  14. 2
      src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdCommandMiddleware.cs
  15. 26
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/AggregateHandlerTests.cs
  16. 10
      tests/Squidex.Infrastructure.Tests/CQRS/Commands/DefaultDomainObjectRepositoryTests.cs

1
Squidex.sln.DotSettings

@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=CommandHandlerMiddleware_002Ecs/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002Aapp_002A_002A_002Ejs/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002Aapp_002A_002A_002Ejs/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002Aapp_002F_002A_002A_002F_002A_002Ejs/@EntryIndexedValue">False</s:Boolean>

81
src/Squidex.Infrastructure/CQRS/Commands/AggregateHandler.cs

@ -10,6 +10,9 @@ using System;
using System.Threading.Tasks;
using Squidex.Infrastructure.CQRS.Events;
// ReSharper disable ConvertIfStatementToConditionalTernaryExpression
// ReSharper disable InvertIf
namespace Squidex.Infrastructure.CQRS.Commands
{
public sealed class AggregateHandler : IAggregateHandler
@ -17,16 +20,6 @@ namespace Squidex.Infrastructure.CQRS.Commands
private readonly IDomainObjectRepository domainObjectRepository;
private readonly IDomainObjectFactory domainObjectFactory;
public IDomainObjectRepository Repository
{
get { return domainObjectRepository; }
}
public IDomainObjectFactory Factory
{
get { return domainObjectFactory; }
}
public AggregateHandler(
IDomainObjectFactory domainObjectFactory,
IDomainObjectRepository domainObjectRepository)
@ -38,44 +31,58 @@ namespace Squidex.Infrastructure.CQRS.Commands
this.domainObjectRepository = domainObjectRepository;
}
public async Task<T> CreateAsync<T>(CommandContext context, Func<T, Task> creator) where T : class, IAggregate
public Task<T> CreateAsync<T>(CommandContext context, Func<T, Task> creator) where T : class, IAggregate
{
Guard.NotNull(creator, nameof(creator));
Guard.NotNull(context, nameof(context));
var aggregateCommand = GetCommand(context);
var aggregate = domainObjectFactory.CreateNew<T>(aggregateCommand.AggregateId);
await creator(aggregate);
await SaveAsync(aggregate);
return InvokeAsync(context, creator, false);
}
if (!context.IsCompleted)
{
context.Complete(new EntityCreatedResult<Guid>(aggregate.Id, aggregate.Version));
}
public Task<T> UpdateAsync<T>(CommandContext context, Func<T, Task> updater) where T : class, IAggregate
{
Guard.NotNull(updater, nameof(updater));
return aggregate;
return InvokeAsync(context, updater, true);
}
public async Task<T> UpdateAsync<T>(CommandContext context, Func<T, Task> updater) where T : class, IAggregate
private async Task<T> InvokeAsync<T>(CommandContext context, Func<T, Task> handler, bool isUpdate) where T : class, IAggregate
{
Guard.NotNull(updater, nameof(updater));
Guard.NotNull(context, nameof(context));
var aggregateCommand = GetCommand(context);
var aggregate = await domainObjectRepository.GetByIdAsync<T>(aggregateCommand.AggregateId, aggregateCommand.ExpectedVersion);
var aggregateObject = domainObjectFactory.CreateNew<T>(aggregateCommand.AggregateId);
await updater(aggregate);
if (isUpdate)
{
await domainObjectRepository.LoadAsync(aggregateObject, aggregateCommand.ExpectedVersion);
}
await handler(aggregateObject);
var events = aggregateObject.GetUncomittedEvents();
foreach (var @event in events)
{
@event.SetAggregateId(aggregateObject.Id);
}
await domainObjectRepository.SaveAsync(aggregateObject, events, Guid.NewGuid());
await SaveAsync(aggregate);
aggregateObject.ClearUncommittedEvents();
if (!context.IsCompleted)
{
context.Complete(new EntitySavedResult(aggregate.Version));
if (isUpdate)
{
context.Complete(new EntitySavedResult(aggregateObject.Version));
}
else
{
context.Complete(EntityCreatedResult.Create(aggregateObject.Id, aggregateObject.Version));
}
}
return aggregate;
return aggregateObject;
}
private static IAggregateCommand GetCommand(CommandContext context)
@ -91,19 +98,5 @@ namespace Squidex.Infrastructure.CQRS.Commands
return command;
}
private async Task SaveAsync(IAggregate aggregate)
{
var events = aggregate.GetUncomittedEvents();
foreach (var @event in events)
{
@event.SetAggregateId(aggregate.Id);
}
await domainObjectRepository.SaveAsync(aggregate, events, Guid.NewGuid());
aggregate.ClearUncommittedEvents();
}
}
}

21
src/Squidex.Infrastructure/CQRS/Commands/DefaultDomainObjectRepository.cs

@ -17,40 +17,31 @@ namespace Squidex.Infrastructure.CQRS.Commands
public sealed class DefaultDomainObjectRepository : IDomainObjectRepository
{
private readonly IStreamNameResolver nameResolver;
private readonly IDomainObjectFactory factory;
private readonly IEventStore eventStore;
private readonly EventDataFormatter formatter;
public DefaultDomainObjectRepository(
IDomainObjectFactory factory,
IEventStore eventStore,
IStreamNameResolver nameResolver,
EventDataFormatter formatter)
public DefaultDomainObjectRepository(IEventStore eventStore, IStreamNameResolver nameResolver, EventDataFormatter formatter)
{
Guard.NotNull(factory, nameof(factory));
Guard.NotNull(formatter, nameof(formatter));
Guard.NotNull(eventStore, nameof(eventStore));
Guard.NotNull(nameResolver, nameof(nameResolver));
this.factory = factory;
this.formatter = formatter;
this.eventStore = eventStore;
this.nameResolver = nameResolver;
}
public async Task<T> GetByIdAsync<T>(Guid id, long? expectedVersion = null) where T : class, IAggregate
public async Task LoadAsync(IAggregate domainObject, long? expectedVersion = null)
{
var streamName = nameResolver.GetStreamName(typeof(T), id);
var streamName = nameResolver.GetStreamName(domainObject.GetType(), domainObject.Id);
var events = await eventStore.GetEventsAsync(streamName);
if (events.Count == 0)
{
throw new DomainObjectNotFoundException(id.ToString(), typeof(T));
throw new DomainObjectNotFoundException(domainObject.Id.ToString(), domainObject.GetType());
}
var domainObject = factory.CreateNew<T>(id);
foreach (var storedEvent in events)
{
var envelope = ParseKnownCommand(storedEvent);
@ -63,10 +54,8 @@ namespace Squidex.Infrastructure.CQRS.Commands
if (expectedVersion != null && domainObject.Version != expectedVersion.Value)
{
throw new DomainObjectVersionException(id.ToString(), typeof(T), domainObject.Version, expectedVersion.Value);
throw new DomainObjectVersionException(domainObject.Id.ToString(), domainObject.GetType(), domainObject.Version, expectedVersion.Value);
}
return domainObject;
}
public async Task SaveAsync(IAggregate domainObject, ICollection<Envelope<IEvent>> events, Guid commitId)

2
src/Squidex.Infrastructure/CQRS/Commands/IDomainObjectRepository.cs

@ -15,7 +15,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
{
public interface IDomainObjectRepository
{
Task<T> GetByIdAsync<T>(Guid id, long? expectedVersion = null) where T : class, IAggregate;
Task LoadAsync(IAggregate domainObject, long? expectedVersion = null);
Task SaveAsync(IAggregate domainObject, ICollection<Envelope<IEvent>> events, Guid commitId);
}

11
src/Squidex.Infrastructure/Dispatching/ActionContextDispatcher.cs

@ -28,14 +28,15 @@ namespace Squidex.Infrastructure.Dispatching
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
.Where(m => Helper.HasRightName(m, methodName))
.Where(m => Helper.HasRightParameters<TIn, TContext>(m))
.Where(m => Helper.HasRightVoidReturn(m))
.Where(m =>
m.HasMatchingName(methodName) &&
m.HasMatchingParameters<TIn, TContext>() &&
m.HasMatchingReturnType(typeof(void)))
.Select(m =>
{
var inputType = m.GetParameters()[0].ParameterType;
var factoryMethod =
var handler =
typeof(ActionContextDispatcher<TTarget, TIn, TContext>)
.GetMethod(nameof(Factory),
BindingFlags.Static |
@ -43,7 +44,7 @@ namespace Squidex.Infrastructure.Dispatching
.MakeGenericMethod(inputType)
.Invoke(null, new object[] { m });
return (inputType, factoryMethod);
return (inputType, handler);
})
.ToDictionary(m => m.Item1, h => (ActionContextDelegate<TIn>)h.Item2);

11
src/Squidex.Infrastructure/Dispatching/ActionDispatcher.cs

@ -28,14 +28,15 @@ namespace Squidex.Infrastructure.Dispatching
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
.Where(m => Helper.HasRightName(m, methodName))
.Where(m => Helper.HasRightParameters<TIn>(m))
.Where(m => Helper.HasRightVoidReturn(m))
.Where(m =>
m.HasMatchingName(methodName) &&
m.HasMatchingParameters<TIn>() &&
m.HasMatchingReturnType(typeof(void)))
.Select(m =>
{
var inputType = m.GetParameters()[0].ParameterType;
var factoryMethod =
var handler =
typeof(ActionDispatcher<TTarget, TIn>)
.GetMethod(nameof(Factory),
BindingFlags.Static |
@ -43,7 +44,7 @@ namespace Squidex.Infrastructure.Dispatching
.MakeGenericMethod(inputType)
.Invoke(null, new object[] { m });
return (inputType, factoryMethod);
return (inputType, handler);
})
.ToDictionary(m => m.Item1, h => (ActionDelegate<TIn>)h.Item2);

11
src/Squidex.Infrastructure/Dispatching/FuncContextDispatcher.cs

@ -27,14 +27,15 @@ namespace Squidex.Infrastructure.Dispatching
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
.Where(m => Helper.HasRightName(m, methodName))
.Where(m => Helper.HasRightParameters<TIn, TContext>(m))
.Where(m => Helper.HasRightReturnType<TOut>(m))
.Where(m =>
m.HasMatchingName(methodName) &&
m.HasMatchingParameters<TIn, TContext>() &&
m.HasMatchingReturnType(typeof(TOut)))
.Select(m =>
{
var inputType = m.GetParameters()[0].ParameterType;
var factoryMethod =
var handler =
typeof(FuncContextDispatcher<TTarget, TIn, TContext, TOut>)
.GetMethod(nameof(Factory),
BindingFlags.Static |
@ -42,7 +43,7 @@ namespace Squidex.Infrastructure.Dispatching
.MakeGenericMethod(inputType)
.Invoke(null, new object[] { m });
return (inputType, factoryMethod);
return (inputType, handler);
})
.ToDictionary(m => m.Item1, h => (FuncContextDelegate<TIn>)h.Item2);

11
src/Squidex.Infrastructure/Dispatching/FuncDispatcher.cs

@ -27,14 +27,15 @@ namespace Squidex.Infrastructure.Dispatching
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
.Where(m => Helper.HasRightName(m, methodName))
.Where(m => Helper.HasRightParameters<TIn>(m))
.Where(m => Helper.HasRightReturnType<TOut>(m))
.Where(m =>
m.HasMatchingName(methodName) &&
m.HasMatchingParameters<TIn>() &&
m.HasMatchingReturnType(typeof(TOut)))
.Select(m =>
{
var inputType = m.GetParameters()[0].ParameterType;
var factoryMethod =
var handler =
typeof(FuncDispatcher<TTarget, TIn, TOut>)
.GetMethod(nameof(Factory),
BindingFlags.Static |
@ -42,7 +43,7 @@ namespace Squidex.Infrastructure.Dispatching
.MakeGenericMethod(inputType)
.Invoke(null, new object[] { m });
return (inputType, factoryMethod);
return (inputType, handler);
})
.ToDictionary(m => m.Item1, h => (FuncDelegate<TIn>)h.Item2);

22
src/Squidex.Infrastructure/Dispatching/Helper.cs

@ -13,34 +13,24 @@ namespace Squidex.Infrastructure.Dispatching
{
internal static class Helper
{
public static bool HasRightName(MethodInfo method, string name)
public static bool HasMatchingName(this MethodInfo method, string name)
{
return string.Equals(method.Name, name, StringComparison.OrdinalIgnoreCase);
}
public static bool HasRightName(MethodInfo method)
{
return string.Equals(method.Name, "On", StringComparison.OrdinalIgnoreCase);
}
public static bool HasRightReturnType<TOut>(MethodInfo method)
{
return method.ReturnType == typeof(TOut);
}
public static bool HasRightVoidReturn(MethodInfo method)
public static bool HasMatchingReturnType(this MethodInfo method, Type type)
{
return method.ReturnType == typeof(void);
return method.ReturnType == type;
}
public static bool HasRightParameters<TIn>(MethodInfo method)
public static bool HasMatchingParameters<TIn>(this MethodInfo method)
{
var parameters = method.GetParameters();
return parameters.Length == 1 && typeof(TIn).IsAssignableFrom(parameters[0].ParameterType);
}
public static bool HasRightParameters<TIn, TContext>(MethodInfo method)
public static bool HasMatchingParameters<TIn, TContext>(this MethodInfo method)
{
var parameters = method.GetParameters();

2
src/Squidex/Config/Domain/WriteModule.cs

@ -14,7 +14,7 @@ using Squidex.Domain.Apps.Write.Assets;
using Squidex.Domain.Apps.Write.Contents;
using Squidex.Domain.Apps.Write.Schemas;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Pipeline.CommandMiddlewares;
using Squidex.Pipeline.CommandHandlers;
// ReSharper disable UnusedAutoPropertyAccessor.Local

2
src/Squidex/Pipeline/CommandHandlers/ETagCommandMiddleware.cs

@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Squidex.Infrastructure.CQRS.Commands;
namespace Squidex.Pipeline.CommandMiddlewares
namespace Squidex.Pipeline.CommandHandlers
{
public class ETagCommandMiddleware : ICommandMiddleware
{

2
src/Squidex/Pipeline/CommandHandlers/EnrichWithActorCommandMiddleware.cs

@ -17,7 +17,7 @@ using Squidex.Infrastructure.Security;
// ReSharper disable InvertIf
namespace Squidex.Pipeline.CommandMiddlewares
namespace Squidex.Pipeline.CommandHandlers
{
public class EnrichWithActorCommandMiddleware : ICommandMiddleware
{

2
src/Squidex/Pipeline/CommandHandlers/EnrichWithAppIdCommandMiddleware.cs

@ -15,7 +15,7 @@ using Squidex.Infrastructure.CQRS.Commands;
// ReSharper disable InvertIf
namespace Squidex.Pipeline.CommandMiddlewares
namespace Squidex.Pipeline.CommandHandlers
{
public sealed class EnrichWithAppIdCommandMiddleware : ICommandMiddleware
{

2
src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdCommandMiddleware.cs

@ -18,7 +18,7 @@ using Squidex.Infrastructure.CQRS.Commands;
// ReSharper disable InvertIf
namespace Squidex.Pipeline.CommandMiddlewares
namespace Squidex.Pipeline.CommandHandlers
{
public sealed class EnrichWithSchemaIdCommandMiddleware : ICommandMiddleware
{

26
tests/Squidex.Infrastructure.Tests/CQRS/Commands/AggregateHandlerTests.cs

@ -53,7 +53,6 @@ namespace Squidex.Infrastructure.CQRS.Commands
private readonly Envelope<IEvent> event1 = new Envelope<IEvent>(new MyEvent());
private readonly Envelope<IEvent> event2 = new Envelope<IEvent>(new MyEvent());
private readonly CommandContext context;
private readonly MyCommand command;
private readonly AggregateHandler sut;
private readonly MyDomainObject domainObject;
@ -66,20 +65,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
.RaiseNewEvent(event1)
.RaiseNewEvent(event2);
command = new MyCommand { AggregateId = domainObject.Id };
context = new CommandContext(command);
}
[Fact]
public void Should_provide_access_to_factory()
{
Assert.Equal(factory, sut.Factory);
}
[Fact]
public void Should_provide_access_to_repository()
{
Assert.Equal(repository, sut.Repository);
context = new CommandContext(new MyCommand { AggregateId = domainObject.Id });
}
[Fact]
@ -143,8 +129,8 @@ namespace Squidex.Infrastructure.CQRS.Commands
[Fact]
public async Task Update_async_should_create_domain_object_and_save()
{
A.CallTo(() => repository.GetByIdAsync<MyDomainObject>(command.AggregateId, null))
.Returns(Task.FromResult(domainObject));
A.CallTo(() => factory.CreateNew<MyDomainObject>(domainObject.Id))
.Returns(domainObject);
A.CallTo(() => repository.SaveAsync(domainObject, A<ICollection<Envelope<IEvent>>>.Ignored, A<Guid>.Ignored))
.Returns(TaskHelper.Done);
@ -161,14 +147,15 @@ namespace Squidex.Infrastructure.CQRS.Commands
Assert.Equal(domainObject, passedDomainObject);
Assert.NotNull(context.Result<EntitySavedResult>());
A.CallTo(() => repository.LoadAsync(domainObject, null)).MustHaveHappened();
A.CallTo(() => repository.SaveAsync(domainObject, A<ICollection<Envelope<IEvent>>>.Ignored, A<Guid>.Ignored)).MustHaveHappened();
}
[Fact]
public async Task Update_sync_should_create_domain_object_and_save()
{
A.CallTo(() => repository.GetByIdAsync<MyDomainObject>(command.AggregateId, null))
.Returns(Task.FromResult(domainObject));
A.CallTo(() => factory.CreateNew<MyDomainObject>(domainObject.Id))
.Returns(domainObject);
A.CallTo(() => repository.SaveAsync(domainObject, A<ICollection<Envelope<IEvent>>>.Ignored, A<Guid>.Ignored))
.Returns(TaskHelper.Done);
@ -183,6 +170,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
Assert.Equal(domainObject, passedDomainObject);
Assert.NotNull(context.Result<EntitySavedResult>());
A.CallTo(() => repository.LoadAsync(domainObject, null)).MustHaveHappened();
A.CallTo(() => repository.SaveAsync(domainObject, A<ICollection<Envelope<IEvent>>>.Ignored, A<Guid>.Ignored)).MustHaveHappened();
}
}

10
tests/Squidex.Infrastructure.Tests/CQRS/Commands/DefaultDomainObjectRepositoryTests.cs

@ -40,7 +40,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
A.CallTo(() => factory.CreateNew<MyDomainObject>(aggregateId))
.Returns(domainObject);
sut = new DefaultDomainObjectRepository(factory, eventStore, streamNameResolver, eventDataFormatter);
sut = new DefaultDomainObjectRepository(eventStore, streamNameResolver, eventDataFormatter);
}
public sealed class MyEvent : IEvent
@ -77,7 +77,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
A.CallTo(() => eventStore.GetEventsAsync(streamName))
.Returns(Task.FromResult<IReadOnlyList<StoredEvent>>(new List<StoredEvent>()));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.GetByIdAsync<MyDomainObject>(aggregateId));
await Assert.ThrowsAsync<DomainObjectNotFoundException>(() => sut.LoadAsync(domainObject, -1));
}
[Fact]
@ -103,9 +103,9 @@ namespace Squidex.Infrastructure.CQRS.Commands
A.CallTo(() => eventDataFormatter.Parse(eventData2))
.Returns(new Envelope<IEvent>(event2));
var result = await sut.GetByIdAsync<MyDomainObject>(aggregateId);
await sut.LoadAsync(domainObject);
Assert.Equal(result.AppliedEvents, new[] { event1, event2 });
Assert.Equal(domainObject.AppliedEvents, new[] { event1, event2 });
}
[Fact]
@ -131,7 +131,7 @@ namespace Squidex.Infrastructure.CQRS.Commands
A.CallTo(() => eventDataFormatter.Parse(eventData2))
.Returns(new Envelope<IEvent>(event2));
await Assert.ThrowsAsync<DomainObjectVersionException>(() => sut.GetByIdAsync<MyDomainObject>(aggregateId, 200));
await Assert.ThrowsAsync<DomainObjectVersionException>(() => sut.LoadAsync(domainObject, 200));
}
[Fact]

Loading…
Cancel
Save