Browse Source

DataLoader implemented.

pull/378/head
Sebastian 7 years ago
parent
commit
c1bc9e7eda
  1. 46
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs
  2. 78
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs
  3. 18
      src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs
  4. 3
      src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs
  5. 14
      src/Squidex/Config/Domain/EntitiesServices.cs
  6. 41
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

46
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs

@ -8,43 +8,25 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using GraphQL;
using Microsoft.Extensions.Caching.Memory;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService
{
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10);
private readonly IContentQueryService contentQuery;
private readonly IGraphQLUrlGenerator urlGenerator;
private readonly ISemanticLog log;
private readonly IAssetQueryService assetQuery;
private readonly IAppProvider appProvider;
public CachingGraphQLService(
IMemoryCache cache,
IAppProvider appProvider,
IAssetQueryService assetQuery,
IContentQueryService contentQuery,
IGraphQLUrlGenerator urlGenerator,
ISemanticLog log)
private readonly IDependencyResolver resolver;
public CachingGraphQLService(IMemoryCache cache, IDependencyResolver resolver)
: base(cache)
{
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(assetQuery, nameof(assetQuery));
Guard.NotNull(contentQuery, nameof(contentQuery));
Guard.NotNull(urlGenerator, nameof(urlGenerator));
Guard.NotNull(log, nameof(log));
this.appProvider = appProvider;
this.assetQuery = assetQuery;
this.contentQuery = contentQuery;
this.urlGenerator = urlGenerator;
this.log = log;
Guard.NotNull(resolver, nameof(resolver));
this.resolver = resolver;
}
public async Task<(bool HasError, object Response)> QueryAsync(QueryContext context, params GraphQLQuery[] queries)
@ -54,7 +36,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var model = await GetModelAsync(context.App);
var ctx = new GraphQLExecutionContext(context, assetQuery, contentQuery, urlGenerator);
var ctx = new GraphQLExecutionContext(context, resolver);
var result = await Task.WhenAll(queries.Select(q => QueryInternalAsync(model, ctx, q)));
@ -68,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
var model = await GetModelAsync(context.App);
var ctx = new GraphQLExecutionContext(context, assetQuery, contentQuery, urlGenerator);
var ctx = new GraphQLExecutionContext(context, resolver);
var result = await QueryInternalAsync(model, ctx, query);
@ -82,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return (false, new { data = new object() });
}
var result = await model.ExecuteAsync(ctx, query, log);
var result = await model.ExecuteAsync(ctx, query);
if (result.Errors?.Any() == true)
{
@ -102,9 +84,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
entry.AbsoluteExpirationRelativeToNow = CacheDuration;
var allSchemas = await appProvider.GetSchemasAsync(app.Id);
var allSchemas = await resolver.Resolve<IAppProvider>().GetSchemasAsync(app.Id);
return new GraphQLModel(app, allSchemas, contentQuery.DefaultPageSizeGraphQl, assetQuery.DefaultPageSizeGraphQl, urlGenerator);
return new GraphQLModel(app,
allSchemas,
resolver.Resolve<IContentQueryService>().DefaultPageSizeGraphQl,
resolver.Resolve<IAssetQueryService>().DefaultPageSizeGraphQl,
resolver.Resolve<IGraphQLUrlGenerator>());
});
}

78
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLExecutionContext.cs

@ -7,37 +7,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GraphQL;
using GraphQL.DataLoader;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Log;
namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public sealed class GraphQLExecutionContext : QueryExecutionContext
{
private static readonly List<IAssetEntity> EmptyAssets = new List<IAssetEntity>();
private static readonly List<IContentEntity> EmptyContents = new List<IContentEntity>();
private readonly IDataLoaderContextAccessor dataLoaderContextAccessor;
private readonly IDependencyResolver resolver;
public IGraphQLUrlGenerator UrlGenerator { get; }
public GraphQLExecutionContext(QueryContext context,
IAssetQueryService assetQuery,
IContentQueryService contentQuery,
IGraphQLUrlGenerator urlGenerator)
: base(context, assetQuery, contentQuery)
public ISemanticLog Log { get; }
public GraphQLExecutionContext(QueryContext context, IDependencyResolver resolver)
: base(context,
resolver.Resolve<IAssetQueryService>(),
resolver.Resolve<IContentQueryService>())
{
UrlGenerator = resolver.Resolve<IGraphQLUrlGenerator>();
dataLoaderContextAccessor = resolver.Resolve<IDataLoaderContextAccessor>();
this.resolver = resolver;
}
public void Setup(ExecutionOptions execution)
{
UrlGenerator = urlGenerator;
var loader = resolver.Resolve<DataLoaderDocumentListener>();
execution.Listeners.Add(loader);
execution.FieldMiddleware.Use(LoggingMiddleware.Create(resolver.Resolve<ISemanticLog>()));
execution.UserContext = this;
}
public Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(IJsonValue value)
public async Task<IReadOnlyList<IAssetEntity>> GetReferencedAssetsAsync(IJsonValue value)
{
var ids = ParseIds(value);
return GetReferencedAssetsAsync(ids);
if (ids == null)
{
return EmptyAssets;
}
var dataLoader =
dataLoaderContextAccessor.Context.GetOrAddBatchLoader<Guid, IAssetEntity>("Assets",
async batch =>
{
var result = await GetReferencedAssetsAsync(new List<Guid>(batch));
return result.ToDictionary(x => x.Id);
});
var assets = await Task.WhenAll(ids.Select(x => dataLoader.LoadAsync(x)));
return assets.Where(x => x != null).ToList();
}
public Task<IReadOnlyList<IContentEntity>> GetReferencedContentsAsync(Guid schemaId, IJsonValue value)
public async Task<IReadOnlyList<IContentEntity>> GetReferencedContentsAsync(Guid schemaId, IJsonValue value)
{
var ids = ParseIds(value);
return GetReferencedContentsAsync(schemaId, ids);
if (ids == null)
{
return EmptyContents;
}
var dataLoader =
dataLoaderContextAccessor.Context.GetOrAddBatchLoader<Guid, IContentEntity>($"Schema_{schemaId}",
async batch =>
{
var result = await GetReferencedContentsAsync(schemaId, new List<Guid>(batch));
return result.ToDictionary(x => x.Id);
});
var contents = await Task.WhenAll(ids.Select(x => dataLoader.LoadAsync(x)));
return contents.Where(x => x != null).ToList();
}
private static ICollection<Guid> ParseIds(IJsonValue value)
@ -58,7 +114,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
}
catch
{
return new List<Guid>();
return null;
}
}
}

18
src/Squidex.Domain.Apps.Entities/Contents/GraphQL/GraphQLModel.cs

@ -20,7 +20,6 @@ using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types;
using Squidex.Domain.Apps.Entities.Contents.GraphQL.Types.Utils;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Log;
using GraphQLSchema = GraphQL.Types.Schema;
#pragma warning disable IDE0003
@ -172,20 +171,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
return contentTypes.GetOrAdd(schema, s => new ContentGraphType());
}
public async Task<(object Data, object[] Errors)> ExecuteAsync(GraphQLExecutionContext context, GraphQLQuery query, ISemanticLog log)
public async Task<(object Data, object[] Errors)> ExecuteAsync(GraphQLExecutionContext context, GraphQLQuery query)
{
Guard.NotNull(context, nameof(context));
var inputs = query.Variables?.ToInputs();
var result = await new DocumentExecuter().ExecuteAsync(options =>
var result = await new DocumentExecuter().ExecuteAsync(execution =>
{
options.FieldMiddleware.Use(LoggingMiddleware.Create(log));
options.OperationName = query.OperationName;
options.UserContext = context;
options.Schema = graphQLSchema;
options.Inputs = inputs;
options.Query = query.Query;
context.Setup(execution);
execution.Schema = graphQLSchema;
execution.Inputs = query.Variables?.ToInputs();
execution.Query = query.Query;
}).ConfigureAwait(false);
return (result.Data, result.Errors?.Select(x => (object)new { x.Message, x.Locations }).ToArray());

3
src/Squidex.Domain.Apps.Entities/Contents/ScheduleJob.cs

@ -1,5 +1,4 @@

// ==========================================================================
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)

14
src/Squidex/Config/Domain/EntitiesServices.cs

@ -6,6 +6,8 @@
// ==========================================================================
using System;
using GraphQL;
using GraphQL.DataLoader;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -74,6 +76,18 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<AssetUsageTracker>()
.As<IEventConsumer>().As<IAssetUsageTracker>();
services.AddSingletonAs(x => new FuncDependencyResolver(t => x.GetRequiredService(t)))
.As<IDependencyResolver>();
services.AddSingletonAs<DataLoaderContextAccessor>()
.As<IDataLoaderContextAccessor>();
services.AddSingletonAs<DataLoaderDocumentListener>()
.AsSelf();
services.AddSingletonAs<CachingGraphQLService>()
.As<IGraphQLService>();
services.AddSingletonAs<CachingGraphQLService>()
.As<IGraphQLService>();

41
tests/Squidex.Domain.Apps.Entities.Tests/Contents/GraphQL/GraphQLTestBase.cs

@ -9,6 +9,8 @@ using System;
using System.Collections.Generic;
using System.Security.Claims;
using FakeItEasy;
using GraphQL;
using GraphQL.DataLoader;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
@ -34,7 +36,6 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
public class GraphQLTestBase
{
protected readonly Schema schemaDef;
protected readonly Guid schemaId = Guid.NewGuid();
protected readonly Guid appId = Guid.NewGuid();
protected readonly string appName = "my-app";
@ -42,8 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
protected readonly IAssetQueryService assetQuery = A.Fake<IAssetQueryService>();
protected readonly ISchemaEntity schema = A.Fake<ISchemaEntity>();
protected readonly IJsonSerializer serializer = TestUtils.CreateSerializer(TypeNameHandling.None);
protected readonly IMemoryCache cache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
protected readonly IAppProvider appProvider = A.Fake<IAppProvider>();
protected readonly IDependencyResolver dependencyResolver;
protected readonly IAppEntity app = A.Dummy<IAppEntity>();
protected readonly QueryContext context;
protected readonly ClaimsPrincipal user = new ClaimsPrincipal();
@ -51,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
public GraphQLTestBase()
{
schemaDef =
var schemaDef =
new Schema("my-schema")
.AddJson(1, "my-json", Partitioning.Invariant,
new JsonFieldProperties())
@ -93,11 +93,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
A.CallTo(() => schema.Id).Returns(schemaId);
A.CallTo(() => schema.SchemaDef).Returns(schemaDef);
var allSchemas = new List<ISchemaEntity> { schema };
A.CallTo(() => appProvider.GetSchemasAsync(appId)).Returns(allSchemas);
sut = new CachingGraphQLService(cache, appProvider, assetQuery, contentQuery, new FakeUrlGenerator(), A.Fake<ISemanticLog>());
sut = CreateSut();
}
protected static IContentEntity CreateContent(Guid id, Guid refId, Guid assetId, NamedContentData data = null, NamedContentData dataDraft = null)
@ -210,5 +206,32 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL
{
return serializer.Serialize(result);
}
private CachingGraphQLService CreateSut()
{
var appProvider = A.Fake<IAppProvider>();
A.CallTo(() => appProvider.GetSchemasAsync(appId))
.Returns(new List<ISchemaEntity> { schema });
var dataLoaderContext = new DataLoaderContextAccessor();
var services = new Dictionary<Type, object>
{
[typeof(IAppProvider)] = appProvider,
[typeof(IAssetQueryService)] = assetQuery,
[typeof(IContentQueryService)] = contentQuery,
[typeof(IDataLoaderContextAccessor)] = dataLoaderContext,
[typeof(IGraphQLUrlGenerator)] = new FakeUrlGenerator(),
[typeof(ISemanticLog)] = A.Fake<ISemanticLog>(),
[typeof(DataLoaderDocumentListener)] = new DataLoaderDocumentListener(dataLoaderContext)
};
var resolver = new FuncDependencyResolver(t => services[t]);
var cache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
return new CachingGraphQLService(cache, resolver);
}
}
}

Loading…
Cancel
Save