このトピックでは、EdgeRoutine (ER) とGraphQLを使用して高速APIゲートウェイを構築する方法について説明します。 ERは、Alibaba Cloud CDNポイントオブプレゼンス (POP) にデプロイされるエッジサーバーレスソリューションです。 このトピックでは、GraphQLはERと連携して、天気予報Webサイトのゲートウェイとして機能します。 このトピックで説明するプロセスを使用して、Dynamic Content Delivery Network (DCDN) を使用してGraphQLゲートウェイを開発することもできます。
背景情報
インターネット技術が急速に進歩し続けるにつれて、世界中でGraphQLを使用してAPIを提供する企業が増えています。 同様に、Alibaba Cloudの技術チームは、APIの記述、公開、呼び出しにのみGraphQLを使用します。 Facebook、Netflix、GitHub、PayPal、Microsoft、フォルクスワーゲン、Walmartなどの他の世界をリードする企業もGraphQLを使用しています。 世界的な調査によると、GraphQLはフロントエンド開発者の間で人気があります。 GraphQLはBackend-for-Frontend (BFF) パターンのゲートウェイ層に適しています。 GraphQLを使用すると、特定のビジネス要件を満たすために、高速サービスフレームワーク (HSF) APIやサードパーティのRESTful APIなどのAPIを自己管理型サービスファサードに統合およびカプセル化できます。 GraphQLは、RESTful、MTOP、MOPENなどのHTTPベースのゲートウェイと簡単に統合できます。 GraphQLは、サーバーレスまたはFaaSアーキテクチャのゲートウェイ層にも適しています。 1つのHTTPトリガーのみを使用して、すべてのGraphQL APIを呼び出すことができます。
GraphQLは、APIのクエリ言語であり、既存のデータを使用してクエリを実行するためのランタイムです。 GraphQLは、API内のデータの完全でわかりやすい説明を提供します。これにより、クライアントは必要な情報のみを要求できます。 これは、APIの進歩に役立ち、強力な開発者ツールの開発を可能にします。 GraphQLの詳細については、 GraphQL公式サイト
ERとGraphQLの統合
ERは、Alibaba Cloud CDN技術チームによって開発されたサーバーレスコンピューティングプラットフォームです。 ERは、W3Cと同様の標準に準拠したService Workerコンテナを提供します。 ERは、世界中のAlibaba Cloud POPのアイドル状態のコンピューティングリソースと堅牢なアクセラレーションおよびキャッシュ機能を使用して、高可用性、高パフォーマンス、分散、および柔軟なコンピューティングサービスを提供します。 ERの詳細については、EdgeRoutineとは何ですか?をご参照ください。
GraphQLをBFFゲートウェイ層として使用できます。これにより、多数のクエリ要求を処理でき、読み取り専用クエリ要求の応答は時間の経過とともに変更されません。
上の図では、GraphQLクエリ要求のプロキシとしてERが使用されています。 初めてクエリ要求が開始されると、システムはERからGraphQLゲートウェイ層に要求を送信し、次に、HSFを使用してGraphQLゲートウェイ層から指定されたアプリケーションにクエリ要求を送信します。 システムは、要求されたコンテンツを取得し、そのコンテンツをPOPにキャッシュする。 システムは、TTLに基づいて、後続のクエリ要求がERまたはGraphQLゲートウェイ層のどちらに向けられるかを判断します。 要求されたコンテンツがPOPにキャッシュされている場合、アプリケーションの1秒あたりのクエリ数 (QPS) は大幅に減少します。
ポートアポロGraphQLサーバー
Apollo GraphQL Serverは、広く使用されているオープンソースのGraphQLサービスです。 Node.jsバージョンは、BFFパターンに基づいて設計されたアプリケーションで使用されます。 Apollo GraphQL ServerはNode.js指向のプロジェクトです。 ただし、ERはService Workerに似たサーバーレスコンテナを提供します。 Apollo GraphQL ServerはERに移植する必要があります。
ステップ1: TypeScript言語で開発環境と足場を構築する
ERコンテナでTypeScript開発環境を構築します。 Service WorkerのTypeScriptライブラリを使用して、コンパイル環境をシミュレートします。 Webpackをオンプレミスのデバッグサーバーとして使用し、ブラウザーでService Workerを使用してedge.jsスクリプトを実行します。 次に、Webpackソケットに基づくネットワーク通信を有効にして、ホットリロードを実装します。
手順2: HTTPサーバーへの接続の確立
次のサンプルコードでは、ApolloServerBaseクラスをインポートし、HTTPサーバーへの接続を確立して、ERコンテナに適したApollo GraphQLサーバーを作成する方法の例を示します。
'apollo-server-core' から {ApolloServerBase} をインポートします。'./handlersから {handleGraphQLRequest} をインポートします。/**
* ポートアポロGraphQLサーバーにER。
* /
エクスポートクラスApolloServer extends ApolloServerBase {
/**
* 指定されたパスでGraphQL Postリクエストをリッスンします。
* @ paramパスあなたが聴きたいパス。
*/
async listen(path = '/graphql') {
// start() メソッドが呼び出される前にlisten() メソッドが誤って使用された場合、例外が発生します。
this.assertStarted('listen');
// addEventListenr('fetch', (FetchEvent) => void) はERによって提供される。
addEventListener('fetch', async (event: FetchEvent) => {
// ERからのすべてのリクエストをリッスンします。
const { request } =イベント;
if (request.method === 'POST') {
// POSTリクエストのみを処理します。
const url=新しいURL(request.url);
if (url.pathname === path) {
// パスが要件を満たしている場合は、handleGraphQLRequest() メソッドを使用してリクエストを処理します。
const options = await this.graphQLServerOptions();
event.respondWith(handleGraphQLRequest (これ、要求、オプション));
}
}
});
}
}
ステップ3: handleGraphQLRequest() メソッドの実行
handleGraphQLRequest() メソッドは、チャネルモードで機能します。 このメソッドは、HTTPリクエストをGraphQLリクエストに変換します。 Apollo GraphQL ServerはGraphQLリクエストを受信し、GraphQLレスポンスを返します。 次に、このメソッドはGraphQL応答をHTTP応答に変換します。 Apolloは、handleGraphQLRequest() メソッドに似たrunHttpQuery() メソッドをネイティブに提供します。 ただし、runHttpQuery() メソッドでは、Node.js環境のbufferなどの組み込みモジュールを使用する必要があります。 このメソッドは, Service Worker環境ではコンパイルできません。 次のサンプルコードは、handleGraphQLRequest() メソッドを実行する方法の例を示しています。
'apollo-server-core' から {GraphQLOptions, GraphQLRequest} をインポートします。'./ApolloServer' から {ApolloServer} をインポートします。/**
* HTTPリクエストからGraphQLクエリを解析し、クエリを実行します。 次に、実行結果を返します。
* /
非同期関数のエクスポートhandleGraphQLRequest ()
サーバー: ApolloServer、
リクエスト: リクエスト,
オプション: GraphQLOptions、): Promise<Response> {
gqlReq: GraphQLRequestを許可します。
try {
// HTTPリクエスト本文からJSON形式のリクエストを解析します。
// リクエストはGraphQL型で、クエリ、変数、およびoperationNameが含まれます。
gqlReq = await request.json();
} catch (e) {
新しいエラー (「リクエスト本文をJSONに解析するときにエラーが発生しました」) をスローします。
}
// GraphQLリクエストを実行します。
// 実行が失敗した場合、例外は発生しません。 ただし、「エラー」を含む応答が返されます。
const gqlRes = await server.exe cuteOperation(gqlReq);
const応答=new response (JSON.stringify({ data: gqlRes.data, errors: gqlRes.errors }), {
// content-typeがJSON形式であることを確認します。
ヘッダー: { 'content-type': 'application/json'} 、
});
// GraphQLレスポンスのメッセージヘッダーをHTTPレスポンスにコピーします。
Object.entries(gqlRes.http.headers) の (const [key, value]) {
response.headers.set (キー、値);
}
応答を返します。}
例: Alibaba Cloud CDNに基づいて開発されたGraphQLを使用した天気予報
この例では、Alibaba Cloud CDN上に構築された高速GraphQLゲートウェイについて説明しr tianqiapi.com。 2次カプセル化は、サードパーティの気象サービスで実行され、このゲートウェイを使用して配信されます。
tianqiapi.comは、有料ユーザーのQPSクォータの制限を指定します。 ユーザーは1日あたり300回までm tianqiapi.comから気象条件を照会できます。 ほとんどの場合、天気予報は頻繁に変化しません。 ユーザが特定の都市の気象条件を照会する要求を初めて開始すると、その要求はo tianqiapi.comに向けられる。 同じ都市の気象条件を照会するための後続の要求は、気象データがキャッシュされている最も近いPOPに向けられる。
概要o f tianqiapi.com
tianqiapi.comは天気予報サービスを提供し、1日あたり数千万のQPSを処理します。 次のコードは、特定の都市の気象条件を照会するために開始されるサンプルのHTTPリクエストを示しています。 この例では、南京が使用されています。
HTTPリクエスト
リクエストURL: https://www.tianqiapi.com/free/day?appid={APP_ID}&appsecret={APP_SECRET}&city=% E5 % 8D % 97% E4 % BA % AC
リクエスト方法: GET
ステータスコード: 200 OK
リモートアドレス: 127.0.0.1:7890
リファラーポリシー: strict-origin-when-cross-origin
{APP_ID} および {APP_SECRET} は、APIアカウントを指定します。
HTTPレスポンス
HTTP/1.1 200 OK
サーバー: nginx
日付: 8月19日2021日木曜日06:21:45 GMT
Content-Type: application/json
転送-エンコーディング: chunked
接続: キープアライブ
Vary: Accept-Encoding
アクセス制御-許可-オリジン: *
Access-Control-Allow-Credentials: true
コンテンツエンコード: gzip
{
air: "94" 、
都市: "南京" 、
cityid: "101190101" 、
tem: "31" 、
tem_day: "31" 、
tem_night: "24" 、
update_time: "14:12" 、
Wea: 「曇り」、
wea_img: "yun" 、
勝利: 「南東風」、
win_meter: "9km/h" 、
win_speed: 「レベル2」
}
API操作
非同期関数のエクスポートfetchWeatherOfCity(city: string) {
// URLクラスはERに実装されています。
const url=新しいURL('http:// www.tianqiapi.com/free/day');
// tianqiapiによって提供される無料のアカウントを使用します。
url.searchParams.set('appid '、'2303 ****');
url.searchParams.set('appsecret' 、'8Yvl **** ');
url.searchParams.set ('cityy' 、city);
const応答=await fetch(url.toString);
応答を返します。}
手順1: GraphQL SDL言語でのカスタムスキーマのコンパイル
次のサンプルコードでは、GraphQL SDL言語を使用して、実装するAPIのスキーマを定義する方法の例を示します。
type Query {
現在のAPIのバージョン情報を照会します。 "
versions: バージョン!
"指定された都市のリアルタイム気象データを照会します。
weatherOfCity (名前: String!): Weather!
}
"""
シティ情報
"""
type City {
"""
都市の一意の識別子。
"""
id: ID!
"""
都市の名前。
"""
name: String!
}
"""
バージョン情報
"""
typeバージョン {
"""
API のバージョン番号です。
"""
api: String!
"""
'graphql' NPMバージョン番号。
"""
graphql: 文字列!
}
"""
気象データ
"""
type Weather {
「現在の都市」
city: シティ!
「最後の更新時間」
updateTime: 文字列!
「気象条件コード」
code: String!
「中国語の気象状況」
localized: String!
「昼間の気温」
tempOfDay: フロート!
「夜の温度」
tempOfNight: フロート!
}
ステップ2: GraphQLリゾルバーのデプロイ
次のサンプルコードは、GraphQLリゾルバーをデプロイする方法の例を示しています。
'graphql' から {version as graphqlVersion} をインポートします。'../api-version' から {apiVersion} をインポートします。'../tianqi-api' から {fetchWeatherOfCity} をインポートします。export関数のバージョン () {
return {
// FaaSと比較して、ERは展開速度が遅くなります。
// そのため、各デプロイの前にapi-version.tsファイルのバージョン番号を手動で変更する必要があります。
// クエリによって返されたバージョン番号が変更された場合、GraphQLリゾルバーはPOPにデプロイされます。
api: apiVersion、
graphql: graphqlVersion、
};
}
export非同期関数weatherOfCity (親: any、args: { name: string }) {
// APIを呼び出し、レスポンスの形式をJSONに変更します。
const raw=awaits fetchWeatherOfCit y(args.name).then((res) => res.json());
// 元のレスポンスを定義されたAPIにマッピングします。
return {
city: {
id: raw.cityid、
名前: raw.city、
},
updateTime: raw.update_time、
コード: raw.wea_img、
localized: raw.wea、
tempOfDay: raw.tem_day、
tempOfNight: raw.tem_night、
};
}
手順3: サーバーの作成と開始
サーバーオブジェクトを作成します。 次に、graphqlパスをリッスンするようにサーバーオブジェクトを起動して設定します。
// 「apollo-serverからのimport {ApolloServer} 」は使用されなくなりました。
'@ ali/apollo-server-edge-routine' から {ApolloServer} をインポートします。'../graphql/schema.graphql' から {default as typeDefs} をインポートします。'../resolvers' からリゾルバとして * をインポートします。// サーバーを作成します。
const server = new ApolloServer({
// typeDefsはGraphQLのDocumentNodeオブジェクトです。
// webpack-graphql-loaderによってファイルが読み込まれると、graphqlファイルはDocumentNodeオブジェクトになります。
typeDefs,
// ステップ2のリゾルバ。
リゾルバー、});
// サーバーを起動し、指定されたパスをリッスンします。 必要なコードは1行だけです。
server.start().then(() => server.listen());
ステップ4: ポリフィルを追加
ER環境で記述されたコードをTypeScriptで読み取れるようにするには、tsconfig.jsonにlib
とtypes
を指定する必要があります。
{
"compilerOptions": {
"alwaysStrict": true、
"esModuleInterop": true、
"lib": ["esnext" 、"webworker"] 、
"モジュール": "esnext" 、
"moduleResolution": "ノード" 、
"outDir": "./dist" 、
"preserveConstEnums": true、
"removeComments": true、
"sourceMap": true、
"strict": true、
"target": "esnext" 、
"types": ["@ ali/edge-routine-types"]
},
"include": ["src"] 、
"exclude": ["node_modules"]
}
serverlessまたはFaaSアーキテクチャを使用するアプリケーションと比較して、この例で使用するアプリケーションはNode.js環境では実行されません。 アプリケーションは、Service Workerに似た環境で実行されます。 Webpack 5を使用する場合は、Node.jsの組み込みポリフィルをブラウザー環境に手動で追加する必要があります。
{
...
resolve: {
フォールバック: {
assert: require.resolve('assert/') 、
buffer: require.resolve('buffer/') 、
crypto: require.resolve('crypto-browserify') 、
os: require.resolve('os-browserify/browser') 、
stream: require.resolve('stream-browserify') 、
zlib: require.resolve('browserify-zlib') 、
util: require.resolve('util/') 、
},
...
}
...
}
また、assert、buffer、crypto-browserify、os-browserify、stream-browserify、browserify-zlib、およびutilのポリフィルパッケージを手動でインストールする必要があります。
ステップ5: キャッシュの追加
fetchWeatherOfCity() メソッドを実行するには、実験APIを使用してキャッシュを追加します。
非同期関数のエクスポートfetchWeatherOfCity(city: string) {
const url=新しいURL('http:// www.tianqiapi.com/free/day');
url.searchParams.set('appid '、'2303 ****');
url.searchParams.set('appsecret' 、'8Yvl **** ');
url.searchParams.set ('cityy' 、city);
const urlString = url.toString();
if (isCacheSupported()) {
const cachedResponse = await cache.get(urlString);
if (cachedResponse) {
cachedResponseを返します。
}
}
const応答=await fetch(urlString);
if (isCacheSupported()) {
cache.put(urlString, response);
}
応答を返します。}
globalThisで提供されるキャッシュオブジェクトは、Swiftによって実装されるキャッシュです。 キャッシュキーはHTTPリクエストオブジェクトまたはHTTP URL文字列である必要があり、キャッシュ値はfetch() メソッドを使用して取得できるHTTPレスポンスオブジェクトである必要があります。 サーバーレスアーキテクチャを使用し、EdgeRoutineコンテナで実行されるアプリケーションは、数分または1時間ごとに再起動されます。 グローバル変数は、アプリケーションが再起動されるたびにクリアされます。 オブジェクトキャッシュを使用して、POP上のグローバル変数をキャッシュできます。
ステップ6: Playgroundデバッガの追加
GraphQLを効率的にデバッグするには、公式のPlaygroundデバッガーを追加します。 Playgroundは単一ページのアプリケーションです。 Webpackのhtml-loaderを使用してPlaygroundをロードできます。
addEventListener('fetch', (event) => {
const応答=handleRequest(event.request);
if (レスポンス) {
event.respondWith (応答);
}
});
function handleRequest(request: Request): Promise<Response> | void {
const url=新しいURL(request.url);
const path = url.pathname;
// デバッグを容易にするために、/graphqlのすべてのGETリクエストのPlayground情報を返します。
// POSTリクエストの特定のGraphQL APIを返します。
if (request.method === 'GET'&path === '/graphql') {
Promise.resolveを返します (新しいレスポンス (rawPlaygroundHTML, { status: 200, headers: { 'content-type': 'text/html' } }));
}
}
ブラウザで /graphqlにアクセスし、クエリステートメントを入力します。
クエリCityWeater($name: String!) {
versions {
api
graphql
}
weatherOfCity (名前: $name) {
city {
id
name
}
code
updateTime
ローカライズ済み
tempOfDay
tempOfNight
}
}
[変数] を {"name": "Hangzhou"}
に設定し、[再生] ボタンをクリックします。