このエントリーをはてなブックマークに追加

Server Side React with PostgreSQL

Reactjsいいですよね。うちでもすでにReactで書いたサービスを動かしています。

さて、Reactjsの売りの一つはServer Side Renderingだと思います。すでに各種言語で動かしている方がいらっしゃいます。一例:

でも、ちょっと待って下さい。サーバー側でレンダリングするなら別にAppサーバーにやらせる必要はないですよね。むしろ、データを保持しているDBにやらせれば、データの移動がない分だけ速くなるわけです。

ということで、PostgreSQL上でServer Side Renderingを実装してみました。

PL/v8

PostgreSQLでJavaScriptを動かすにはPL/v8を使います。PL/v8はv8 JavaScript エンジンをそのままPostgreSQL上で動かす拡張です。Amazon RDS上でも使えるようです。

準備

1. PL/v8のインストール

PL/v8はAmazon RDS でも9.3.5以降であれば使えるようですが、今回はUbuntuで用意しました。postgresql-9.4-plv8をapt-getすればOKです。

$ sudo apt-get install postgresql-9.4 postgresql-9.4 postgresql-9.4-plv8 postgresql-client-9.4

(createdbとかいろいろしてDBを作って下さい)

$ psql -c 'CREATE EXTENSION plv8'  # plv8拡張をインストールします

これでplv8が使えるようになりました。(ちなみに ansibleのpostgresql_extモジュールを使うと管理が捗ります)

2. PL/v8でReactjsを読み込む

PL/v8で外部JavaScriptを読み込むのは以下のようにします。まずはReactjsをダウンロードしておき、以下のSQLを実行しておきます。

\set reactjs `cat react-0.13.2.js`

CREATE TABLE plv8_modules(modname text primary key, load_on_start boolean, code text);

INSERT INTO plv8_modules values ('reactjs', true, :'reactjs');

CREATE OR REPLACE FUNCTION plv8_startup()
RETURNS void
LANGUAGE plv8
AS
$$
load_module = function(modname)
{
    var rows = plv8.execute("SELECT code from plv8_modules " +
                            " where modname = $1", [modname]);
    for (var r = 0; r < rows.length; r++)
    {
        var code = rows[r].code;
        eval("(function() { " + code + "})")();
    }
};
$$;

plv8_modulesというテーブルに、必要なモジュールのpathを入れておき、plv8_startup()という関数でこのテーブルからモジュールを読み込んで evalする、ということをします。

ここまでで準備が整いました。

使ってみる

1. アプリを書く

まずはアプリを書きます。サンプルなので、簡単なものを。

/** @jsx React.DOM */
var Name = React.createClass({
    render: function() {
        return (
            <b>{ this.props.name }</b>
        );
    }
});

var Hello = React.createClass({
    render: function() {
        return (
            <td>Hello <Name name={ this.props.name } /></td>
        );
    }
});

これをjsxでapp.jsというファイルに書き出しておきます。

$ jsx --harmony hello.jsx > app.js

その後、このapp.jsを読み込みます。

-- read app.js
\set appjs `cat app.js`
INSERT INTO plv8_modules values ('appjs',true,:'appjs');

これで準備は完成です。

2. JS呼び出し関数を作成

こんな感じのSQLを書いて下さい。plv8_moduleテーブルのappjsからcodeを読み込み、evalしています。

-- Function itself
CREATE OR REPLACE FUNCTION name_render(n text) RETURNS text
LANGUAGE plv8
AS
$$
load_module("reactjs");

var rows = plv8.execute("SELECT code from plv8_modules where modname = $1", ["appjs"]);
eval(rows[0].code);

var e = React.createElement(Hello, {name: n});
return React.renderToString(e);
$$;

なお、requireとかmoduleとかの仕組みがv8そのものにはないようなので、いろいろと苦労しました。もっと良い方法はあるかもしれません。

3. 実際に呼び出してみる

では、実際に呼び出してみましょう。

test=# select plv8_startup();  -- 事前にload_module関数とかを読み込んでおく必要があります
test=# select name_render('world');
                                                                            name_render
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 <td data-reactid=".1lf0zu1tg5c" data-react-checksum="-1307760157"><span data-reactid=".1lf0zu1tg5c.0">Hello </span><b data-reactid=".1lf0zu1tg5c.1">world</b></td>
(1 行)

うまくHTMLが出てきましたね。

普通のSQL関数なんで、そのままなんでも使えます。unnestで配列を行に展開したものを出力してみましょうか。

shirou=# select name_render(n) from unnest(ARRAY['shirou', 'rudi', 'tsukinowa']) as n;
                                                                            name_render
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 <td data-reactid=".delxp6jitc" data-react-checksum="-896062726"><span data-reactid=".delxp6jitc.0">Hello </span><b data-reactid=".delxp6jitc.1">shirou</b></td>
 <td data-reactid=".1yy26e8ifpc" data-react-checksum="-2021250693"><span data-reactid=".1yy26e8ifpc.0">Hello </span><b data-reactid=".1yy26e8ifpc.1">rudi</b></td>
 <td data-reactid=".y9snhgvrb4" data-react-checksum="1022766062"><span data-reactid=".y9snhgvrb4.0">Hello </span><b data-reactid=".y9snhgvrb4.1">tsukinowa</b></td>
(3 行)

ちょっとreactidとかがうるさいですが、ちゃんと出力されていますね。

あとはこれをそのままブラウザ側に返してあげればレンダリングされる、という寸法です。

まとめ

  • ReactjsをPostgreSQLでサーバーサイドレンダリングしてみました
  • PLv8いいよ
  • 筆者は実戦投入した結果については責任を負いかねます