Question

I am investigating a good maintainable architecture for GraphQL. In particular we want to migrate a REST app to GraphQL. Specifically I am using .NET.

I am following the tutorial here: https://fullstackmark.com/post/17/building-a-graphql-api-with-aspnet-core-2-and-entity-framework-core
which is very similar to most tutorials

It has the following Mutator file:

public class NHLStatsMutation : ObjectGraphType
{
   public NHLStatsMutation(IPlayerRepository playerRepository)
   {
     Name = "Mutation";

     Field<PlayerType>(
           "createPlayer",
           arguments: new QueryArguments(
           new QueryArgument<NonNullGraphType<PlayerInputType>> { Name = "player" }
     ),
     resolve: context =>
     {
        var player = context.GetArgument<Player>("player");
        return playerRepository.Add(player);
     });
   }
}

This gets assigned in the schema:

public class NHLStatsSchema : Schema
{
  public NHLStatsSchema(IDependencyResolver resolver): base(resolver)
  {
     Query = resolver.Resolve<NHLStatsQuery>();
     Mutation = resolver.Resolve<NHLStatsMutation>();
  }
}

Finally there is a single GraphQLController which handles the API requests and has an instance of the schema:

    [Route("[controller]")] 
    public class GraphQLController : Controller
    {
        private readonly IDocumentExecuter _documentExecuter;
        private readonly ISchema _schema;

        public GraphQLController(ISchema schema, IDocumentExecuter documentExecuter)
        {
            _schema = schema;
            _documentExecuter = documentExecuter;
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
        {
            if (query == null) { throw new ArgumentNullException(nameof(query)); }
            var inputs = query.Variables.ToInputs();
            var executionOptions = new ExecutionOptions
            {
                Schema = _schema,
                Query = query.Query,
                Inputs = inputs
            };

            var result = await _documentExecuter.ExecuteAsync(executionOptions).ConfigureAwait(false);

            if (result.Errors?.Count > 0)
            {
                return BadRequest(result);
            }

            return Ok(result);
        }
    }
}

Having a single schema and API endpoint seems to be "How GraphQL is done" according to a few parts of the internet.

However this seems to result in a very very large Query and Mutator file, with many many dependencies injected (repositories, services etc) and many, many methods in the file.

This is an Enterprise applicattion with 300+ database tables and lots of complex backend business rules. The application is a monolith with a layered architecture with a UI layer, Services layer and Data Layer.

How can I architect this to make this more maintainable?

I have read up on Schema Stitching or Federation (https://www.apollographql.com/docs/graphql-tools/schema-stitching/) However, I'm not sure if that's the right approach as the examples seem to be using that to address microservices or API endpoints from different apps. (https://blog.apollographql.com/graphql-schema-stitching-8af23354ac37)

Was it helpful?

Solution

I found the solution to this after a lot more Google searching. Eventually I was led here:

https://stackoverflow.com/questions/55188636/graphql-net-how-to-separate-the-root-query-into-multiple-parts

According to the docs the root query can be split into virtual groups:

public class RootQuery : ObjectGraphType
{
    public RootQuery()
    {
        Name = "RootQuery";
        // defines the articles sub query and returns an empty anonymous type object
        // whose only purpose is to allow making queries on the subtype (ArticlesQueryType)
        Field<ArticlesQueryType>("articles", resolve: context => new {});
    }
}

// defines the articles specific queries
public class ArticlesQueryType: ObjectGraphType
{
    public ArticlesQueryType(IArticleService articleService)
    {
        Name = "ArticlesQuery";
        Field<ArticleType>(
            name: "article",
            arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "id" }),
            resolve: context =>
        {
            var id = context.GetArgument<int>("id");
            return articleService.Get(id);
        });
    }
} 
Licensed under: CC-BY-SA with attribution
scroll top