AWS API Gateway + Lambda と WordPress REST API によるヘッドレス WordPress 入門(1)

WordPressはプラグインやテーマによる拡張性が高く、それゆえに世界中のウェブサイトの 30% 以上で利用されていますが、CMS 本体、プラグイン、テーマ、ミドルウエア、データベースが一枚岩な構造(モノリシック)であるために、スケールアウトやスケールアップなどなど、エンタープライズなユースケースでは様々な問題が発生します。

今回の記事では、これらの問題を解決するための一つの方法として、WordPress のテーマシステムを捨てる(ヘッドレスWordPress)というアプローチについて解説していきます。

この記事で紹介する仕組み

今回採用する仕組みは以下のような感じです。

  • 従来の WordPress でいうテーマの部分には AWS の API Gateway と Lambda を使用する。
  • WordPress では、真っ白なテーマを有効化し、REST API のエンドポイントとしてのみ利用する。
  • API Gateway の手前には CloudFront があったほうがさらにベター。

この方法のユースケース

  • トラフィックが多くてスケールアップやスケールアウトに疲れちゃった。。。
  • PHP でフロントをゴリゴリ書くのしんどい。今さら WordPress に学習コストを払いたくない。
  • たとえば Kintone などの他の API とも連携が必要など、WordPress ではカスタマイズ性に不安。
  • CI も含めたモダンなワークフローで運用コストを抑えたい。
  • REST API 部分だけ弊社のような WordPress のプロに外注することも可能。

中には、WordPress を捨てれば?という声もあるかと思いますが、コンテンツを入力するためのインターフェースがあり、かつ REST API も用意するとなると、これをスクラッチで用意するのは大変です。

WordPress にはカスタム投稿タイプという仕組みがあり、REST-API もデフォルトで用意されているため、コンテンツを入れるための箱は数分で用意することができますのでこれを利用しない手は無いでしょう。

メリット

パフォーマンス

WordPress に限らず PHP で書かれたウェブシステムは、すべての データベース処理が直列で行われます。

WordPress では、デーフォルトのテーマでさえも1ページを表示するために 30回以上の SQL が発行されており、さらにサードパーティのプラグインによるデータベースアクセス、カスタム投稿タイプなど、ありとあらゆる要素でデータベースアクセスが増加します。(一般的な企業サイトでも60回弱?)

WordPress の REST API では、各エンドポイントで必要なデータベースアクセスしか行いませんので(SQL の発行が 10回ちょっとになる)、たとえばカスタム投稿タイプがいくつあっても、個々のエンドポイントのパフォーマンスに与える影響は微々たるものとなります。(実はまったくないとは言えないのですが。。。汗)

また、今回紹介する仕組みでは、Lambda の Node.js を使用するため、複数の API アクセスが発生してもこれらは非同期で同時に行われます。

そのため、ユーザーから見た表示速度を大きく改善することができます。

開発

WordPress のように、ビューとバックエンドが密接に結びついたシステムでは、テスト一つとっても非常に困難なものとなります。

WordPress を REST API としてのみ使用すれば、テストは REST API  のエンドポイントにアクセスして期待通りの JSON が得られるかどうかを確認すればよく、Lambda 側ではモックアップの JSON を使ったテストを行うことができます。

そのため、バックエンドの開発者(WordPress側)とビュー側の開発者は、JSONのスキーマだけ合わせれば同時並行で開発を始めることも可能になります。

また、ビュー側の処理が WordPress とは分離されているため、WordPress に対する学習コストはほぼ皆無となります。

一方で、サーバーサイドで DOM が使えるなど、一般的なフロントエンドの開発手法をそのまま利用することができ、Kintone などの他の API との連携も容易になります。

サーバーサイドで DOM をごにょごにょすれば、極端な話、モックアップ用の HTML をそのまま所定のディレクトリにホイって入れるだけなんてこともできそうですね。(やってみたら意外とそれだけでは済まなかったけど。笑)

さらにいえば、WordPress では、リクエストがあったページに関係の無い部分でも、有効化されたプラグインにバグがあればページ全体が真っ白になりますが、Lambda ではそういうことも起こりにくいですし、言語の仕様上、”エスケープ漏れ”による XSS のリスクも低下します。(Nodeのフレームワークの多くは、むしろエスケープしないで出す方法をググらないといけない。笑)

保守性

上述したように、WordPress側の保守性は劇的に向上します。

単に API のレスポンスが期待通りに帰ってくることだけを確認するだけでも十分になりますので、Node の Mocha + Chai を使って以下のような感じのテストを書いて定期的にぶんまわすといいでしょう。

/**
 * カスタム投稿タイプ `info` に対するテストの例
 */

const chai = require('chai');
const assert = chai.assert;
const request = require('request');

const URL = process.env.WP_API_URL;

describe("`/wp-json/wp/v2/info/` のテスト", () => {
	it("ステータスコードが 200 である。", (done) => {
		request.get(`${URL}/info/`, (error, response, body) => {
			assert.deepEqual(200, response.statusCode);
			done();
		});
	});

	it("記事のリストが得られること。", (done) => {
		request.get(`${URL}/info/`, (error, response, body) => {
			const posts = JSON.parse(body);
			assert.isTrue(!! posts.length);
			assert.isTrue(!! parseInt(posts[0].id)); // 記事のID
			assert.isTrue(!! posts[0].title.rendered); // 記事のタイトル
			done();
		});
	});

	it("クエリー `?_embed` の動作テスト", (done) => {
		request.get(`${URL}/info?_embed`, (error, response, body) => {
			const posts = JSON.parse(body);
			assert.deepEqual(200, response.statusCode);
			assert.deepEqual("string", typeof posts[0].content.rendered);
			done();
		});
	});
});

WordPress の API には、容易に認証機能をつけることも可能です。また、WordPressに IP アドレス等で制限をかけることに対しても独自の運用ノウハウ等は不要となり、WordPress 本体やテーマ、プラグインなどの脆弱性に対しても堅牢となります。

<?php

/**
 * WordPress の投稿や固定ページの API に API Key による認証を設ける例
 * 実際には投稿の一覧用のエンドポイントにも同様のアクセス制限が必要
 */
class Custom_WP_REST_Posts_Controller extends \WP_REST_Posts_Controller
{
	public function __construct( $post_type )
	{
		parent::__construct( $post_type );
	}

	public function get_item_permissions_check( $request )
	{
		$request_token = $request->get_header( 'x_kiilife_api_token' );

		$result = parent::get_item_permissions_check( $request );

		if ( $result && ! empty( $request_token ) ) {
			if ( $this->verify_api_key( request_token ) ) {
				return true;
			}
		}

		return false;
	}

	private function verify_api_key( request_token )
	{
		// Something to check `$request_token`.

		return (bool) $result;
	}
}

 

API Gateway + Lambda 側に関しては、サーバーレスのワークフローをそのまま利用することができます。

Serverless Framework を使用すれば、sls deploy --stage=staging という感じで、ステージング環境をほいほい増殖させることができますので、大規模なアップデートと日常的な改修を同時変更で進めることも簡単になります。

https://serverless.com/

CircleCI などの CI サービスとの連携も可能になり、現在のプロジェクトでは、master ブランチへのコミットはステージングにデプロイ、git tag 1.0.0 のようにタグ付けされると本番環境にデプロイというワークフローを採用しており、これによって意図しないエラーでも簡単に戻せるという安心感があります。

まとめ

以上、今回の記事ではとりあえずここまでとします。

次回の記事では、WordPress 側の REST API の実装方法や、ヘッドレス WordPress として使うための WordPress のカスタマイズ方法について説明していきます。