GraphQL API が必要になったので、ASP.NET Core で実装できるか試してみた。ライブラリは現状 GraphQL 一択。
GraphiQL っていう、GraphQL を試すのに便利な Web UI があるので、それも利用した。ASP.NET Core のミドルウェアを使えば組み込むのは簡単。
必要になったのはクエリだけで、ミューテーションはまだ。なので今回は、ASP.NET Core MVC で GraphQL のエンドポイントを用意し、Entity Framework Core と組み合わせてデータベースに保存されたデータを取得するところまでをやってみた。
using GraphiQl; using GraphQL; using GraphQL.Types; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace HelloGraphQL { //============================================ // 1. Entity Framework Core で使うクラスを定義 //============================================ public class Team { public int Id { get; set; } public string Name { get; set; } public IList<Player> Players { get; set; } } public class Player { public int Id { get; set; } public string Name { get; set; } public string Position { get; set; } public int Number { get; set; } public int TeamId { get; set; } public Team Team { get; set; } } public class ApplicationDbContext : DbContext { public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { } public DbSet<Team> Teams => Set<Team>(); public DbSet<Player> Players => Set<Player>(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Team>(e => { e.Property(x => x.Id).IsRequired(); e.Property(x => x.Name).IsRequired(); e.HasKey(x => x.Id); e.HasMany(x => x.Players); }); modelBuilder.Entity<Player>(e => { e.Property(x => x.Id).IsRequired(); e.Property(x => x.Name).IsRequired(); e.Property(x => x.Number).IsRequired(); e.Property(x => x.Position).IsRequired(); e.Property(x => x.TeamId).IsRequired(); e.HasKey(x => x.Id); e.HasOne(x => x.Team); }); } // テスト用のデータを登録する public void EnsureSeedData() { if (!Teams.Any()) { Teams.Add(new Team { Name = "バルセロナ", Players = new List<Player> { new Player { Name = "メッシ", Number = 10, Position = "FW", }, new Player { Name = "スアレス", Number = 9, Position = "FW", }, }, }); Teams.Add(new Team { Name = "レアルマドリード", Players = new List<Player> { new Player { Name = "ベイル", Number = 11, Position = "FW", }, new Player { Name = "モドリッチ", Number = 10, Position = "MF", }, }, }); SaveChanges(); } } } //============================== // 2. GraphQL で使うクラスを定義 //============================== // Player に対応する GraphQL の型 public class PlayerType : ObjectGraphType<Player> { public PlayerType() { Field(x => x.Id); Field(x => x.Name); Field(x => x.Position); Field(x => x.Number); Field(x => x.TeamId); } } // Team に対応する GraphQL の型 public class TeamType : ObjectGraphType<Team> { public TeamType() { Field(x => x.Id); Field(x => x.Name); Field<ListGraphType<PlayerType>>("players"); } } // クエリを定義 public class HelloGraphQLQuery : ObjectGraphType { public HelloGraphQLQuery(ApplicationDbContext context) { Field<ListGraphType<TeamType>>( "teams", arguments: new QueryArguments( new QueryArgument<IntGraphType> { Name = "id", }), resolve: ctx => { var query = context.Teams.AsQueryable(); if (ctx.HasArgument("id")) { var id = ctx.GetArgument<int>("id"); query = query.Where(b => b.Id == id); } var teams = query.Include(b => b.Players) .ToList(); return teams; }); } } // GraphQL のスキーマを定義 public class HelloGraphQLSchema : Schema { public HelloGraphQLSchema(IDependencyResolver dependencyResolver) : base(dependencyResolver) { // 今回はクエリだけ。ミューテーションは後で。 Query = dependencyResolver.Resolve<HelloGraphQLQuery>(); } } //==================================================== // 3. ASP.NET Core MVC で GraphQL エンドポイントを実装 //==================================================== // クライアントから送信されてきた GraphQL をバインドするモデル public class GraphQLModel { public string OperationName { get; set; } public string Query { get; set; } } [Route("graphql")] [ApiController] public class GraphQLController : ControllerBase { readonly IDocumentExecuter documentExecuter; readonly ISchema schema; public GraphQLController(IDocumentExecuter documentExecuter, ISchema schema) { this.documentExecuter = documentExecuter; this.schema = schema; } [HttpPost] public async Task<IActionResult> Post([FromBody] GraphQLModel model) { var result = await documentExecuter.ExecuteAsync(new ExecutionOptions { Schema = schema, OperationName = model.OperationName, Query = model.Query }); return Ok(result); } } //============================================================== // 4. いつものように ASP.NET Core 用の Startup と Program を作成 //============================================================== public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); //-------------------- // ここから GraphQL 用 //-------------------- services.AddTransient<IDocumentExecuter, DocumentExecuter>(); services.AddTransient<ISchema, HelloGraphQLSchema>(); services.AddTransient<HelloGraphQLQuery>(); // IDependencyResolver が使うために GraphQL 用のクラスを // DI コンテナに登録しておく必要があるみたい。 services.AddTransient<TeamType>(); services.AddTransient<PlayerType>(); var sp = services.BuildServiceProvider(); services.AddSingleton<IDependencyResolver>(x => new FuncDependencyResolver(sp.GetService)); } public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext context) { // GraphiQL をホストして手軽に GraphQL を試せるようにする app.UseGraphiQl(); app.UseMvc(); // テスト用データベースが無ければ作る context.Database.EnsureDeleted(); context.Database.EnsureCreated(); context.EnsureSeedData(); } } public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); } }
デバッグ実行して /graphql
に Web ブラウザでアクセスすると、GraphiQL の UI が表示される。全チーム取得するクエリを実行してみると、ちゃんとデータベースからデータを引っ張ってこれた。余談だけど、GraphiQL は補完が効いて、これを使わないなんてとんでもない、ってレベル。
次に、指定した id に該当するチームだけ取得するクエリを実行してみると、こちらも上手く動いた。
クエリの解析は GraphQL.NET がやってくれるけど、データベースからデータを引っ張ってくるところは自分で書く必要があって、なかなかツラミがあった。
あと、今回みたいな簡単なクエリを実装するだけでも結構苦労した。GraphQL 自体の仕様だけでなく、ライブラリの使い方も知る必要があったし、何より、REST API と GraphQL API では考え方が違いすぎて、切り替えるのが大変。GraphQL 脳にならないといけないな。
GraphQL API だと URL を考える必要は無くなったけど、どんなクエリやミューテーションを提供するか考える必要があるので、API 提供側の苦労はさほど変わらない印象だ。