エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

Microservices の GraphQL スキーマを1つにまとめる

エンジニアリンググループの冨岡 (@jooohn) です。出張でNYにきています。NYへの出張は二度目なのですが、同僚のChris (彼はUK, JP, USと三カ国のM3を渡り歩いています!) とWashington, D.C.にいくなどして休日も満喫しています。

f:id:jooohn:20191007074434p:plain
バーガーは野菜

f:id:jooohn:20191007075417p:plainf:id:jooohn:20191007100112p:plain
Washington, D.C. にて。NYCからバスで4hほどでいける。

現在はM3 USAが運営するニュースサイトMDLinxのリニューアルプロジェクトに関わっています。そこで利用しているGraphQL (Apollo) の活用事例を紹介します。

新しいMDLinx の構成

f:id:jooohn:20191007123429p:plain

新しいMDLinxでは上図のように、k8sクラスタ内にいくつかのサービスが存在するマイクロサービス構成になっています。各サービスではGraphQLを共通のインターフェースとして利用しており、webhook用のエンドポイントなどの特殊な場合を除きGraphQLのAPIを呼びます。

MDLinxの編集部はContentfulを利用して記事を作成します。この際、一部Contentfulのネイティブ機能では足りない部分(古い記事のアーカイビングなど)はUI Extensions機能を利用して編集部にContentfulから離れることなく機能を提供しています。このUI Extensionsが Content Serviceを利用する場合には、Content ServiceのGraphQLのAPIを利用します。

MDLinxの編集部が触るサービスは1つなので問題になりませんが、一般ユーザー側は各サービスの機能を広く利用します。この際各microservicesのエンドポイントをバラバラに叩くのではなく、Gatewayサーバーが他のサービスのGraphQL schemaを1つにまとめて、フロントエンドからは1つのschemaに見えるようにまとめています。

図にあるLegacy MDLinx Serverのように、すぐには移行できない機能もGraphQL schemaの一部として提供できています。フロントエンドはGatewayが提供する唯一のGraphQLスキーマだけを知っていればいいことになり、古いAPIを気にする必要はありません。GraphQLのインターフェースによってバックエンドが隠蔽されるため、サービスの分割・統合・移行などもやりやすくなりますね!

この機能を手軽に実現できるようにしているのが、Apolloが提供するApollo Federation (Schema stitching) です。

Schema stitching (deprecated)

Schema stitching (deprecated) - GraphQL Tools - Apollo GraphQL Docs

Apollo Federationの説明をする前に、Schema stitchingという機能を紹介します。

Schema stitchingはまさに上記で説明したような、「複数のスキーマを統合して1つのスキーマとして提供する」という機能です。統合する際にスキーマをある程度コントロールすることができ、query名にprefixをつける、一部のqueryを無視するといったことができます。今回の私達のユースケースでは、Content ServiceのGraphQL schemaは内部(MDLinx編集部)向けのものも含まれており、それをフィルタリングして一部の一般ユーザー向け機能のみ公開するといったことができます。

便利な機能ですが、5月にdeprecatedとなってしまい、代替として次に紹介するApollo Federationが提供されました。

Apollo Federation

Introduction - Apollo Server - Apollo GraphQL Docs

Apollo Federationは、いくつかのマイクロサービスのユースケースにフィットするよう作られたSchema stitchingの後継のような機能です。

Schema stitchingでは、いくつかのサービスを1つのschemaにまとめるマージ機能を主目的としている印象がありました。一方Apollo Federationは各サービス間の連携をより強力にサポートしています。

# reviews service
type Review {
  body: String
  author: User @provides(fields: "username")
  product: Product
}

extend type User @key(fields: "id") {
  id: ID! @external
  reviews: [Review]
}

extend type Product @key(fields: "upc") {
  upc: String! @external
  reviews: [Review]
}

上記はhttps://www.apollographql.com/docs/apollo-server/federation/introduction/#a-first-look の例ですが、User,Productは他サービスのSchemaから提供されているものです。Reviewsサービスは、他サービスで提供されているUser,Productを利用してReview型を提供している他、UserProductにそれぞれreviewsというフィールドを拡張しています。

このように、microservicesの各サービス間での依存関係を上手く表現できます。

ApolloはJSのプロダクトですが、他の言語で実装されていてもFederation specification - Apollo Server - Apollo GraphQL Docsに従うことによってFederationサーバの一部として機能できます。これもGraphQLによってインターフェースと実装を分離された恩恵ですね!(なおMDLinxのサーバーサイド全てNode.js, TypeScriptなので素直にApollo Serverを使っています。)

一方でschemaの統合機能はかなりおおざっぱになってしまいました。

queryを非公開にする機能がなくなっているなど、Schema stitchingでできたことができなくなっています。 https://github.com/apollographql/apollo-server/issues/2812

現在MDLinxではこの制約のためSchema stitchingを一旦利用し続けていますが、今後サポートする予定だそうなので楽しみです!

まとめ

MDLinxでのGraphQL (Apollo) の活用方法として、複数microservicesのschemaを1つのGraphQL schemaとして提供する事例を紹介しました。

GraphQLはAPIリクエストを効率的にまとめる・絞ることも重要な役割の1つですが、インターフェースと実装を分離することによってサーバーサイドの実装に選択肢も与えてくれています。GraphQLがあくまでインターフェースとして存在していることは、私がGraphQLを好きなポイントの1つです。

We are hiring!

エムスリーでは、NYのバーガーが好きな人や、GraphQLでインターフェースを分離したい人を募集しています!

jobs.m3.com