Browse Source

Event consumers simplified.

pull/199/head
Sebastian Stehle 8 years ago
parent
commit
797f6a164b
  1. 98
      src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrain.cs
  2. 30
      src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState.cs
  3. 2
      src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Apps.cs
  4. 9
      src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Rules.cs
  5. 19
      src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Schemas.cs
  6. 22
      src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrain.cs
  7. 17
      src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrainState.cs
  8. 16
      src/Squidex.Infrastructure/CollectionExtensions.cs
  9. 1
      src/Squidex.Infrastructure/Squidex.Infrastructure.csproj
  10. 1
      src/Squidex/Config/Web/WebServices.cs
  11. 31
      tests/Squidex.Infrastructure.Tests/CollectionExtensionsTests.cs

98
src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrain.cs

@ -18,14 +18,12 @@ using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.State.Grains
{
public class AppStateGrain : StatefulObject<AppStateGrainState>
{
private readonly FieldRegistry fieldRegistry;
private readonly TaskFactory taskFactory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(1));
private Exception exception;
public AppStateGrain(FieldRegistry fieldRegistry)
@ -53,101 +51,77 @@ namespace Squidex.Domain.Apps.Read.State.Grains
public virtual Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid id)
{
return taskFactory.StartNew(() =>
{
var schema = State.FindSchema(x => x.Id == id && !x.IsDeleted);
var schema = State.FindSchema(x => x.Id == id && !x.IsDeleted);
return (State.GetApp(), schema);
});
return Task.FromResult((State.GetApp(), schema));
}
public virtual Task<IAppEntity> GetAppAsync()
{
return taskFactory.StartNew(() =>
{
var value = State.GetApp();
var result = State.GetApp();
return value;
});
return Task.FromResult(result);
}
public virtual Task<List<IRuleEntity>> GetRulesAsync()
{
return taskFactory.StartNew(() =>
{
var value = State.FindRules();
var result = State.FindRules();
return value;
});
return Task.FromResult(result);
}
public virtual Task<List<ISchemaEntity>> GetSchemasAsync()
{
return taskFactory.StartNew(() =>
{
var value = State.FindSchemas(x => !x.IsDeleted);
var result = State.FindSchemas(x => !x.IsDeleted);
return value;
});
return Task.FromResult(result);
}
public virtual Task<ISchemaEntity> GetSchemaAsync(Guid id, bool provideDeleted = false)
{
return taskFactory.StartNew(() =>
{
var value = State.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted));
var result = State.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted));
return value;
});
return Task.FromResult(result);
}
public virtual Task<ISchemaEntity> GetSchemaAsync(string name, bool provideDeleted = false)
{
return taskFactory.StartNew(() =>
{
var value = State.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted));
var result = State.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted));
return value;
});
return Task.FromResult(result);
}
public virtual Task HandleAsync(Envelope<IEvent> message)
public async virtual Task HandleAsync(Envelope<IEvent> message)
{
return taskFactory.StartNew(async () =>
if (exception != null)
{
if (exception != null)
if (message.Payload is AppCreated)
{
exception = null;
}
else
{
if (message.Payload is AppCreated)
{
exception = null;
}
else
{
throw exception;
}
throw exception;
}
}
if (message.Payload is AppEvent appEvent && (State.App == null || State.App.Id == appEvent.AppId.Id))
{
try
{
State = State.Apply(message);
if (message.Payload is AppEvent appEvent)
await WriteStateAsync();
}
catch (InconsistentStateException)
{
if (State.App == null || State.App.Id == appEvent.AppId.Id)
{
try
{
State.Apply(message);
await WriteStateAsync();
}
catch (InconsistentStateException)
{
await ReadStateAsync();
State.Apply(message);
await WriteStateAsync();
}
}
await ReadStateAsync();
State = State.Apply(message);
await WriteStateAsync();
}
}).Unwrap();
}
}
}
}

30
src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState.cs

@ -8,18 +8,20 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Read.Apps;
using Squidex.Domain.Apps.Read.Rules;
using Squidex.Domain.Apps.Read.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
namespace Squidex.Domain.Apps.Read.State.Grains
{
public sealed partial class AppStateGrainState
public sealed partial class AppStateGrainState : Cloneable<AppStateGrainState>
{
private FieldRegistry registry;
@ -27,10 +29,10 @@ namespace Squidex.Domain.Apps.Read.State.Grains
public JsonAppEntity App { get; set; }
[JsonProperty]
public Dictionary<Guid, JsonRuleEntity> Rules { get; set; }
public ImmutableDictionary<Guid, JsonRuleEntity> Rules { get; set; } = ImmutableDictionary<Guid, JsonRuleEntity>.Empty;
[JsonProperty]
public Dictionary<Guid, JsonSchemaEntity> Schemas { get; set; }
public ImmutableDictionary<Guid, JsonSchemaEntity> Schemas { get; set; } = ImmutableDictionary<Guid, JsonSchemaEntity>.Empty;
public void SetRegistry(FieldRegistry registry)
{
@ -57,21 +59,17 @@ namespace Squidex.Domain.Apps.Read.State.Grains
return Rules?.Values.OfType<IRuleEntity>().ToList() ?? new List<IRuleEntity>();
}
public void Reset()
public AppStateGrainState Apply(Envelope<IEvent> envelope)
{
Rules = new Dictionary<Guid, JsonRuleEntity>();
Schemas = new Dictionary<Guid, JsonSchemaEntity>();
}
public void Apply(Envelope<IEvent> envelope)
{
this.DispatchAction(envelope.Payload, envelope.Headers);
if (App != null)
return Clone(c =>
{
App.Etag = Guid.NewGuid().ToString();
}
c.DispatchAction(envelope.Payload, envelope.Headers);
if (c.App != null)
{
c.App.Etag = Guid.NewGuid().ToString();
}
});
}
}
}

2
src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Apps.cs

@ -21,8 +21,6 @@ namespace Squidex.Domain.Apps.Read.State.Grains
{
public void On(AppCreated @event, EnvelopeHeaders headers)
{
Reset();
App = EntityMapper.Create<JsonAppEntity>(@event, headers, a =>
{
SimpleMapper.Map(@event, a);

9
src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Rules.cs

@ -9,6 +9,7 @@
using System;
using Squidex.Domain.Apps.Events.Rules;
using Squidex.Domain.Apps.Events.Rules.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
namespace Squidex.Domain.Apps.Read.State.Grains
@ -17,10 +18,12 @@ namespace Squidex.Domain.Apps.Read.State.Grains
{
public void On(RuleCreated @event, EnvelopeHeaders headers)
{
Rules[@event.RuleId] = EntityMapper.Create<JsonRuleEntity>(@event, headers, r =>
var id = @event.RuleId;
Rules.SetItem(id, EntityMapper.Create<JsonRuleEntity>(@event, headers, r =>
{
r.RuleDef = RuleEventDispatcher.Create(@event);
});
}));
}
public void On(RuleUpdated @event, EnvelopeHeaders headers)
@ -56,7 +59,7 @@ namespace Squidex.Domain.Apps.Read.State.Grains
{
var id = @event.RuleId;
Rules[id] = Rules[id].Clone().Update(@event, headers, updater);
Rules = Rules.SetItem(id, x => x.Clone().Update(@event, headers, updater));
}
}
}

19
src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrainState_Schemas.cs

@ -11,6 +11,7 @@ using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Schemas;
using Squidex.Domain.Apps.Events.Schemas.Old;
using Squidex.Domain.Apps.Events.Schemas.Utils;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Reflection;
@ -22,12 +23,14 @@ namespace Squidex.Domain.Apps.Read.State.Grains
{
public void On(SchemaCreated @event, EnvelopeHeaders headers)
{
Schemas[@event.SchemaId.Id] = EntityMapper.Create<JsonSchemaEntity>(@event, headers, s =>
var id = @event.SchemaId.Id;
Schemas = Schemas.SetItem(id, EntityMapper.Create<JsonSchemaEntity>(@event, headers, s =>
{
s.SchemaDef = SchemaEventDispatcher.Create(@event, registry);
SimpleMapper.Map(@event, s);
});
}));
}
public void On(SchemaPublished @event, EnvelopeHeaders headers)
@ -70,11 +73,6 @@ namespace Squidex.Domain.Apps.Read.State.Grains
});
}
public void On(SchemaDeleted @event)
{
Schemas.Remove(@event.SchemaId.Id);
}
public void On(FieldAdded @event, EnvelopeHeaders headers)
{
UpdateSchema(@event, headers, s =>
@ -149,11 +147,16 @@ namespace Squidex.Domain.Apps.Read.State.Grains
UpdateSchema(@event, headers);
}
public void On(SchemaDeleted @event, EnvelopeHeaders headers)
{
Schemas = Schemas.Remove(@event.SchemaId.Id);
}
private void UpdateSchema(SchemaEvent @event, EnvelopeHeaders headers, Action<JsonSchemaEntity> updater = null)
{
var id = @event.SchemaId.Id;
Schemas[id] = Schemas[id].Clone().Update(@event, headers, updater);
Schemas = Schemas.SetItem(id, x => x.Clone().Update(@event, headers, updater));
}
}
}

22
src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrain.cs

@ -10,40 +10,28 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure.States;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Read.State.Grains
{
public sealed class AppUserGrain : StatefulObject<AppUserGrainState>
{
private readonly TaskFactory taskFactory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(1));
public Task AddAppAsync(string appName)
{
return taskFactory.StartNew(() =>
{
State.AppNames.Add(appName);
State = State.AddApp(appName);
return WriteStateAsync();
}).Unwrap();
return WriteStateAsync();
}
public Task RemoveAppAsync(string appName)
{
return taskFactory.StartNew(() =>
{
State.AppNames.Remove(appName);
State = State.RemoveApp(appName);
return WriteStateAsync();
}).Unwrap();
return WriteStateAsync();
}
public Task<List<string>> GetAppNamesAsync()
{
return taskFactory.StartNew(() =>
{
return State.AppNames.ToList();
});
return Task.FromResult(State.AppNames.ToList());
}
}
}

17
src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrainState.cs

@ -6,14 +6,25 @@
// All rights reserved.
// ==========================================================================
using System.Collections.Generic;
using System.Collections.Immutable;
using Newtonsoft.Json;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.State.Grains
{
public sealed class AppUserGrainState
public sealed class AppUserGrainState : Cloneable<AppUserGrainState>
{
[JsonProperty]
public HashSet<string> AppNames { get; set; } = new HashSet<string>();
public ImmutableHashSet<string> AppNames { get; set; } = ImmutableHashSet<string>.Empty;
public AppUserGrainState AddApp(string appName)
{
return Clone(c => c.AppNames = c.AppNames.Add(appName));
}
public AppUserGrainState RemoveApp(string appName)
{
return Clone(c => c.AppNames = c.AppNames.Remove(appName));
}
}
}

16
src/Squidex.Infrastructure/CollectionExtensions.cs

@ -8,12 +8,28 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Squidex.Infrastructure
{
public static class CollectionExtensions
{
public static ImmutableDictionary<TKey, TValue> SetItem<TKey, TValue>(this ImmutableDictionary<TKey, TValue> dictionary, TKey key, Func<TValue, TValue> updater)
{
if (dictionary.TryGetValue(key, out var value))
{
var newValue = updater(value);
if (!Equals(newValue, value))
{
return dictionary.SetItem(key, newValue);
}
}
return dictionary;
}
public static bool TryGetValue<TKey, TValue, TBase>(this IReadOnlyDictionary<TKey, TValue> values, TKey key, out TBase item) where TValue : TBase
{
if (values.TryGetValue(key, out var value))

1
src/Squidex.Infrastructure/Squidex.Infrastructure.csproj

@ -15,6 +15,7 @@
<PackageReference Include="RefactoringEssentials" Version="5.4.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0001" />
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="1.4.0" />
<PackageReference Include="System.Linq" Version="4.3.0" />
<PackageReference Include="System.Reactive" Version="3.1.1" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.4.0" />

1
src/Squidex/Config/Web/WebServices.cs

@ -17,6 +17,7 @@ namespace Squidex.Config.Web
public static void AddMyMvc(this IServiceCollection services)
{
services.AddSingletonAs<FileCallbackResultExecutor>();
services.AddSingletonAs<AppApiFilter>();
services.AddSingletonAs<ApiCostsFilter>();

31
tests/Squidex.Infrastructure.Tests/CollectionExtensionsTests.cs

@ -7,6 +7,7 @@
// ==========================================================================
using System.Collections.Generic;
using System.Collections.Immutable;
using Xunit;
namespace Squidex.Infrastructure
@ -268,5 +269,35 @@ namespace Squidex.Infrastructure
Assert.Equal(source, target);
}
[Fact]
public void Should_return_same_dictionary_if_item_to_replace_not_found()
{
var dict_0 = ImmutableDictionary<int, int>.Empty;
var dict_1 = dict_0.SetItem(1, x => x);
Assert.Same(dict_0, dict_1);
}
[Fact]
public void Should_return_same_dictionary_if_replaced_item_is_same()
{
var dict_0 = ImmutableDictionary<int, int>.Empty;
var dict_1 = dict_0.SetItem(1, 1);
var dict_2 = dict_1.SetItem(1, x => x);
Assert.Same(dict_1, dict_2);
}
[Fact]
public void Should_return_new_dictionary_if_updated_item_is_different()
{
var dict_0 = ImmutableDictionary<int, int>.Empty;
var dict_1 = dict_0.SetItem(2, 2);
var dict_2 = dict_1.SetItem(2, x => 2 * x);
Assert.NotSame(dict_1, dict_2);
Assert.Equal(4, dict_2[2]);
}
}
}
Loading…
Cancel
Save