0

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)

  • "How can I architect this to make this more maintainable?" - not using a recent, still to be proven backbone, written by a social media company, with completely different needs than you, might be a good start. – whatsisname Jan 9 at 2:28
  • 1
    @whatsisname I am exploring whether it can or can't be made maintainable with the technology in an Enterprise context. Advocating not even exploring it and shutting down the question entirely does not help anyone learn anything new about the technology. The whole point of doing investigations, and POCs is to explore the tech and make a measured technical decision whether to use it or not. – k29 Jan 9 at 7:06
0

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);
        });
    }
} 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.