hakobera's blog

技術メモ。たまに雑談

Tuppari - WebSocket on Your Cloud -

Tuppari とは

東京Node学園 6時限目で発表した Node.js で作られた Pusher クローンです。
簡単に言うと WebSocket を利用した大規模 Broadcast に特化したサービスです。

インフラとしては Amazon Web Services (以降、AWS)上で動かすことに最適化されていますが、AWS以外でも動かすことは可能です。つまり、クラウド上に自分自身の Pusher を構築することが可能になります。

現時点では Pusher の機能には追いついていませんが、今後も鋭意開発を進めていくので、是非使ってみてください。
あと、WebSocket を使うので、IE9 以下では動きません。

8/15 追記: v0.2.0 から IE もサポートしました。
詳細は以下の記事を参照して下さい。
Tuppari コミュニティ始めました & IE に対応しました - Scalaとlift のはずだった ・・・

Tuppari 公開記念ハッカソン

興味があればどうぞー。
ご希望があれば、Tuppari の使い方、各自の環境へのインストール方法などサポートします。

Node塾番外編 Real-time Web ハッカソン - [PARTAKE]

ソース

CLI & npm モジュール: https://github.com/tuppari/tuppari

サーバ実装: https://github.com/tuppari/tuppari-servers

試してみよう

Tuppari は各自のインフラで動作させることを想定していますが、インストール済みサンプルとして、 https://api.tuppari.com でサービスを稼働させてありますので、まずはお試しください。

公式ドキュメント: https://github.com/tuppari/tuppari/wiki

Tuppari Command Line Interface のインストール

まずは、Tuppari CLI を npm でインストールします。

npm install -g tuppari
アカウントの作成

まずは、アカウントを作成します。
アカウント名は他人と同じものは使用できません。

tuppari register [YOUR_ACCOUNT_NAME]
Password: [ENTER YOUR PASSWORD]

Create new account name "[YOUR_ACCOUNT_NAME]" success.
ログイン

アカウントを作成したらログインします。

tuppari login
Account Name: [ENTER_YOUR_ACCOUNT_NAME]
Password: [ENTER_YOUR_PASSWORD]

Login success.
アプリケーションの作成
tuppari create [YOUR_APPLICATION_NAME]

Create a new application named "[YOUR_APPLICATION_NAME]" success.
Following is the application Information to access from your code.
{
  "applicationId": "[SHOW_YOUR_APPLICATION_ID]",
  "accessKeyId": "[SHOW_YOUR_ACCESS_KEY_ID]",
  "accessSercretKey": "[SHOW_YOUR_ACCESS_SECRET_KEY]"
}

ここで出力される情報は後で使うので、メモしておいてください。

HTML ファイルの作成
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <!-- 1. Include the Tuppari client JavaScript libraries on your page from CDN -->
  <script src="http://cdn.tuppari.com/0.1.0/tuppari.js"></script>
  <script>
    // 2. Instantiate connection object to tuppari server. 
    var client = tuppari.createClient({
        applicationId: 'YOUR_APPLICATION_ID' // Replace this with your Application ID.
    });
    // 3. Create channel you want to use.
    var channel = client.subscribe('your_channel');
    // 4. Listen for some events on you channel
    channel.bind('your_event', function (data) {
      console.log('An event was triggered with message: %s', data);
    });
  </script>
</head>
<body>
</body>
</html>
Node.js のコードからイベントを発行する

package.json と app.js を以下のように作成します。

package.json

{
  "name": "helloworld",
  "version": "0.1.0",
  "dependencies": {
    "tuppari": "0.1.0"
  }
}

app.js

var Tuppari = require('tuppari');

var tuppari = new Tuppari({
  applicationId: 'YOUR APPLICATION ID',
  accessKeyId: 'YOUR ACCESS KEY ID',
  accessSecretKey: 'YOUR ACCESS SECRET KEY'
});

var channel = tuppari.join('your_channel');

channel.send('your_event', 'hello world', function (err, res, body) {
  if (err) {
    console.error(err);
  }
  console.log(res.statusCode, body);
});

ファイルを作成後、以下のコマンドで実際のイベント発行します。

cd [YOUR_WORKING_DIRECTORY]
npm install
node app.js

ブラウザのコンソールに以下の文字列が出力されていることを確認してください。

An event was triggered with message: hello world

東京Node学園 6時限目の発表資料

簡単にレーダーチャートが描けるライブラリ Raphael-Radar

最近は Google Chart ToolsHighcharts のように綺麗なグラフがかけるライブラリが増えてきましたが、レーダーチャートをサポートしているものがあまりなく、あっても用途にマッチしなかったので jQueryRaphael を使って、レーダーチャートが描けるライブラリを作ってみました。

作ってみましたといっても、正確には fork して改造になります。

ソース: hakobera/raphael-radar · GitHub
デモ: Raphaël Radar Chart Plugin Sample

サンプル

こんな感じのチャートが書けます。

f:id:scalar:20120509150547p:image

ソースは以下のような感じです。

var objects = [
    { title: "Real Madrid C.F.", 
      offense: 80,
      defense: 90,
      technique: 70,
      strategy: 90,
      physicality: 70,
      mentality: 60,
      draw_options: {
        lines: {'stroke-width':'2', 'stroke':'#39b549','stroke-dasharray':'- '},
        points: {'fill':'#39b549','stroke-width':'0'},
        text: {}
      }
    },
    { title: "FC Barcelona", 
      offense: 100,
      defense: 70,
      technique: 100,
      strategy: 70,
      physicality: 60,
      mentality: 80,
      draw_options: {
        lines: {'stroke-width':'4','stroke':'#0070bb','stroke-opacity':0.7,'fill':'#f7d2a8','fill-opacity':0.6},
        points: {'fill':'#f05a23','stroke-width':'1.5','stroke':'#333', 'size': 6},
        text: {}
      }
    }
];

var paper = Raphael( "id_of_some_div", 460, 360);
paper.radarchart(221, 160, 120, labels, 50, 100, objects);

吹き出し部分はイベントハンドラを設定して書いています。標準機能として取り込むかは検討中です。

      paper.scores.forEach(function(s) {
        s.points.forEach(function (p) {
          console.log(p.score);

          var side = "right";
          var offset = p.attrs.r;
          var popupLabel = paper5.text(p.attrs.cx, p.attrs.cy, p.title + '\n' + p.score.label + ': ' + p.score.rawValue).hide();
          if (p.attrs.cx + popupLabel.getBBox().width > paper5.width) {
            side = "left";
            offset = -offset;
          }
          var popup = paper5.popup(p.attrs.cx + offset, p.attrs.cy, popupLabel, side).attr({fill: "#fff", stroke: "#666", "stroke-width": 2 }).hide();

          p.mouseover(function () {
            popup.toFront().show();
            popupLabel.toFront().show();
          });

          p.mouseout(function () {
            popup.hide();
            popupLabel.hide();
          });

          p.toFront();
        });
      });

Router-Line はじめました

4/18 に開催された 東京Node学園 5時限目 で @KOBA789 さんが自作のルータである Router-Line を紹介していました。

その時から便利そうとは思っていたのですが、最近ちょっとした API サーバを書く時に実際に Router-Line を使ってみて、改めて良いなと思ったので、サンプルを見せながら、個人的に便利だと思う使い方を紹介します。

Router-Line とは

KOBA789/router-line · GitHub

github で「A URL routing module for Node.js」と書いてある通り、Router-Line はシンプルな URL ルータで、それ以外の機能は持っていません。サーバを構築するには Node.js の http モジュールなどと組み合わせて使う必要があります。

個人的には API サーバのルーティングには、Express (Connect) を使うより、すっきりかけて、速度も速いので合っていると感じています。

致命的な欠点は、サンプルがないこと、ですかね、はい。ドキュメントがほぼ皆無で、最初どうやってサーバと組み合わせるか悩みました。自分はテストコード読んで理解しましたが、初心者にはお勧めできない、的な雰囲気になってしまっているのがとても勿体無い。(ので、この記事を書きました)

Router-Line 事始め

Router-Line を利用した API サーバの雛形を書くと以下のようになります。

var http = require('http'),
  url = require('url'),
  Router = require('router-line').Router;

var router = new Router();

function send(res, status, contentType, body) {
  res.writeHead(status, { 'Content-Type': contentType });
  res.end(body);
}

/*
 * ここからルーティングの設定
 */
router.add('GET', '/', function (req, res) {
  res.end('hello world');
});

// ここまでルーティングの設定

/*
 * HTTP サーバと連携させる。以下は個人的に使っているテンプレートコード。
 */
var app = http.createServer(function (req, res) {
  var uri, r, handler;

  uri = url.parse(req.url, true);
  r = router.route(req.method, uri.pathname);
  if (r) {
    req.param = function (key) {
      return r.params[key] ? r.params[key] : uri.query[key];
    }
    handler = r.value;

    if (typeof handler === 'function') {
      handler(req, res, r.params);
    } else if (typeof handler === 'string') {
      send(res, 200, 'text/plain', handler);
    } else {
      send(res, 200, 'application/json', JSON.stringify(handler));
    }
  } else {
    send(res, 404, 'application/json', JSON.stringify({ reason: 'This not the URL you are looking for' }));
  }
});
app.listen(process.env.PORT || 3000);
console.log('Server listen on port %d', app.address().port);

Router-Line の基本

Router の作成
Router = require('router-line').Router;
var router = new Router();

普通ですね。今回は1つしか使いませんが、何個でも作れます。

Routing Handler の設定
// HTTP メソッドを指定する add メソッド
router.add('HTTP メソッド名(GET|POST|PUT|DELETE)', 'マッチさせる URL', Routing Handler [function, string, object なんでも OK]);

// 全HTTP メソッドを一括指定できる ANY メソッド
router.ANY('マッチさせる URL', Routing Handler [function, string, object なんでも OK]);

基本的には add() しか使わない。というか、add() しか使ったことない。

router.add('GET', '/', function (req, res) {
  res.end('index');
});

これを取得するには Router.route() メソッドを使います。

var app = http.createServer(function (req, res) {
  var uri, r, handler;

  uri = url.parse(req.url, true);
  r = router.route(req.method, uri.pathname);

URLに一致する handler がない場合は undefined が返ります。
URLに一致する handler がみつかった場合、params, value というプロパティを持った Object が返ります。

{
  params: { Path parameter の解析結果 object },
  value: { add()/ANY() で設定した handler object }
}

以上です。簡単ですね。

応用編

Path Parameter を使う

コロン (:) で始まる文字列は Path Parameter として解析されます。例えば、以下の様な感じです。

/*
 * pathParams には route().params の結果が入っている。
 */
router.add('GET', '/users/:user/apps/:app/:id', function (req, res, pathParams) {
  send(res, 200, 'application/json', JSON.stringify(pathParams));
});

これに対して "/users/user1/apps/app2/3" でアクセスすると、

{"user":"user1","app":"app2","id":"3"}

が返ります。実際に動作するサンプルを heroku においておきましたので、色々と URL を変えて試してみてください。 サンプル

Path Parameter だけど、一部 Option パラメータを指定

URL を括弧で囲むとその部分は省略可能なパラメータとして解析されます。例えば以下。

router.add('GET', '/items/:item(/type/:type)', function (req, res, pathParams) {
  send(res, 200, 'application/json', JSON.stringify(pathParams));
});

これに対して "/items/item1" でアクセスすると、

{"item":"item1"}

こうなって、"/items/item1/type/small" でアクセスすると、

{"item":"item1","type":"small"}

となります

ソース置き場

今回使ったサンプルは以下に置いてあります。

router-line-exercise — Gist

まとめ

なんとなく Node.js なら Express 的な風潮が見受けられますが、API サーバ(画面なし、form から POST なし) を書くなら Router-Line を検討してみても良いと思います、というか Router-Line お勧めです。

鹿駆動勉強会に参加してきました

4/29 に奈良の能楽ホール(重要文化財!)で開催された 鹿駆動勉強会 に参加して、LTしてきました。関東以外の勉強会に参加したのは初めてでしたが、懇親会で twitter でしか知らなかった方々と直接お話できたりして、とても楽しかったです。

当日の流れや各LTの資料、参加報告 Blog などは Togetter に まとまっているので、そちらを参照してみてください。

#shikadriven 鹿駆動勉強会 - Togetter

感想

f:id:scalar:20120429125320j:image

会場はこんな感じで、ここで LT できたのはとても貴重な体験でした。
勉強会は特にテーマ不定で怒涛のLT20連発。内容も多岐に渡っていて、普段聞くことがない内容のものもあり、とても刺激になりました。最近は興味のあるテーマの勉強会にしか言っていませんでしたが、こういう広いテーマの勉強会もまた参加してみたいと思いました。

自分も勉強会運営をしたことがあるのでわかるのですが、LTが多いと時間通りに進行させるのが難しいのですが、当日はほぼスケジュール通りに進行されており、運営の方の手際の良さにとても感心しました。

LTに関しては、@skrb さんの JavaFX が個人的には面白かったです。スライドも JavaFX で組んであって、グリグリ動いていてすごいなと思いました。Swing で UI 組むのは大変だったので、JavaFX でこの辺は便利になって良い感じですね。

What Is JavaFX

他の方の LT の内容も面白かったので、Togetter まとめからリンクを辿ってみてみると良いですよ。

LT の内容

今回はGWの休暇を兼ねて、参加することに意義があったので LT はあまり凝らずに「WebRTC、顔認識、画像合成、画面共有 を全部 JavaScript でやる」という内容を話してきました。

実際の動作画面は以下のようになります。(当日は能楽ホールということで、能面の画像を使いました)

f:id:scalar:20120503163229p:image:w360

左が Mac Book Aire で配信元、左が iPad で共有先です。

ソースは github においてあります。

hakobera/sikakudo-demo · GitHub

WebSocket で PNG 画像をバイナリ転送して、JavaScript で展開して表示してみた

Node.js で WebSocket-Node を使って実装しました。
転送するめぼしい画像が見当たらなかったので、デスクトップをスクリーンキャプチャして転送してみました。

ブラウザはChrome 17以上か、Firefox 11以上が必要です。サーバ側は scrrencapture コマンドを利用している関係で Mac OS X限定です。

デモ

上半分が転送元のデスクトップ、下半分が転送された画像をブラウザで表示したものです。ニコ動のコメントの飛び具合を見るとわかると思いますが、800*600の解像度の画像を、横640に縮小して転送して、1FPSくらいです。(※ これはWebSocket の限界ではありません。速度は向上させる余地はかなりありますが、今回の本質ではないので気にしないことにします)

ソースは github に置いてあります。
hakobera/screencast · GitHub

解説みたいなもの

今回の実装の面白い点は表題の通り「バイナリ転送した PNG 画像を、JavaScript で展開して、Canvas に表示していること」です。

PNG画像の展開には png.js を利用しています。

※コメントで教えてもらった URL.createObjectURL() で描画する方法の方が、クライアントのCPU負荷が低く(Thinkpad X61(Core2Duo 2GHz) Windows 7Chrome 16 で 25% -> 5%)なりました。ブラウザがサポートしている形式を展開する場合は blob 形式でデータを受信して、URL.createObjectURL()で生成したURLをImageで描画するのが良いのではないでしょうか。なお、URL.createObjectURL版のソースコードは blob ブランチに置いてあります。

WebSocket で画像を転送する方法(正確には画像を連続で送信して動画のように表示させる方法)は、今回のも含めて自分が試しただけでも4つあります。

それぞれの現時点での利点と欠点は以下だと思っています。

実装方法 利点 欠点 Chrome FireFox Safari iOS IE9 IE8 IE6
バイナリ(圧縮形式)で転送して、展開して表示 送信データ量が小さい arraybuffer/blog サポートが必要 × × × × ×
バイナリ(RAW)で転送して、そのまま表示 クライアント側の展開がいらないので、クライアント負荷が小さい 送信データ量が極大/arraybuffer/blog サポートが必要 × × × × ×
Data URI(文字列)で転送して、そのまま表示 Safari、Mobile WebKitでも使える 送信データ量が大きい(バイナリ比+33%) ×
画像URLを送信して、ブラウザでロードして表示 送信データ量が極小/Safari、Mobile WebKitでも使える 別途画像配信用のHTTPサーバが必要

IEはWebSocketをサポートしていないので、Socket.IO を使えばできる箇所は△にしています。

ブラウザサポートは次第に解消されていくと思うので、将来的にはバイナリ転送、JSで展開という形に落ち着いていくんじゃないのかな、と思いました。場合によっては独自の圧縮プロトコルも使えるので、高圧縮、暗号化など、色々と応用範囲がありそうですし。

また、WebSocketが gzip 圧縮をサポートするらしいので、画像が転送できれば良いという用途では、RAW形式も生き残るかもしれないですね。

Node.js vs Play vs SAStruts

前置き

Experiences with Node.js: Porting a RESTful Service Written in Java - ZiggyTech

上記記事では、実験的にJava (Jersey + Hibernate on Tomcat) で実装された REST API サーバを Node.js で書きなおしてみたら、少ないリソース(CPU/メモリ使用量)でほぼ同等のパフォーマンスが出せたよ(ただし、O/Rマッパーを使用しない場合)、と書いてあります。この件に関して @koichik さんとやり取りしていた中で以下のような意見を頂いたので、実際にやってみましたという記事です。

さすがに素の Struts 1.x は書き方も忘れたので、日本では結構採用事例があると思われる SAStruts と、最近仕事で使っている Play framework 1.2 を比較対象にします。

比較内容

/orders/ へのアクセスがあると、データベースに50件分のデータを検索しにいき、JSON の配列データに変換して返す REST APIスループット、および、その際の CPUとメモリ使用量を計測します。

実験に使ったソースは github においてあります。
hakobera/node-vs-sastruts-vs-playframework-rest-api-bench · GitHub

計測マシンのスペック

MacBook Pro 2011 early

計測対象の組み合わせ

  • Node.js
    • Node.js v0.6.12
    • express 2.5.8
    • pg 0.6.13 (JavaScript Pure Driver / SQL 直接実行, O/Rマッパーなし)
  • Play
    • Play framework 1.2.4

Java でGC が極端に発生しないようにヒープは最大 512MB を設定、何回は試行後、ヒープサイズが安定してから計測。それ以外はスレッドプールなどはデフォルト設定。全てプロダクションモードでの実行結果。

計測方法

元の記事では JMeter を使っていますが、JMeter だとそれ自体が結構リソースを食うので、今回は Apache Bench を利用しました。

ab -c 64 -n 5000 http://127.0.0.1:8080/orders/

スループットは上記コマンドを5回実行して、最大値と最小値を除く3回の平均値を採用。CPU 使用率とメモリ使用量はアクティビティモニタによる目測。

結果

スループット CPU 使用率 メモリ使用量 スレッド数
Node.js 488 (req/sec) 100.6 (%) 55.6 (MB) 2
Node.js (2 cluster) 739 (req/sec) 200.5(100.5 + 100.0) (%) 115.7 (57.6+58.1) (MB) 12(6*2)
Node.js (3 cluster) 981 (req/sec) 300.7(100.4 + 100.2 + 100.1) (%) 167.1 (56.4+55.4+55.3) (MB) 18(6*3)
Node.js (4 cluster) 1129 (req/sec) 400.8(100.0 + 100.3 + 100.3 + 100.2) (%) 222.6 (55.6 + 55.4 + 55.7 + 55.9) (MB) 24 (6*4)
SAStruts 2237 (req/sec) 388.6 (%) 392.6 (MB) 98
Play 906 (req/sec) 557.8 (%) 365.9 (MB) 53

使っているものが色々と違うので直接は比較できないのですが、なんか元の記事とは傾向が異なる感じなりました。

【追記】github で cluster 化の プルリクエスト をもらったので、cluster のベンチを追加しました。4 cluster 以上は DBアクセスがネックになって、スループットは速くなったり、遅くなったりしてました。環境によっては 3,000 req/sec くらいまで伸びるようです。

まとめ

  • Node.js
    • Node.js はシングルインスタンスなので、1コアしかCPUをつかっていないので、CPU は使っても 100%
    • メモリ使用量が圧倒的に少ない
    • 1スレッド辺りのスループットは最も高い。cluster を使えば、他の2つに追いつけるかも(要検証)
  • Play
    • スループットはそこそこ。Node.js よりは速いが、SAStruts には及ばない
    • CPU 使用率が高い
      • デフォルト設定では(コア数+1)スレッドプールとして確保される
      • スレッドプールの設定で上限を下げることは可能。実運用時はスループットとの兼ね合いで調整する必要あり
      • 試しにスレッドプールを1に設定すると、スループットは 304 req/sec まで落ちるが、CPU使用率: 105% になる
  • 補足
    • CPUコア数の多いマシンでは、シングルインスタンスの Node.js が、複数コアを利用できる SAStruts, Play に対しては圧倒的に遅い
    • 2コアの MacBook Air で試してみると、SAStruts >> Play >= Node.js くらいにはなる
    • Node.js は cluster での絶対性能の向上は図れると思うが、他の2つがデフォルトで複数コアを利用できるのに比べて、運用負荷がかかる

感想

  • Node.js は決して遅くはないが O/Rマッパーを使わないようにしても、RDB へのアクセスを伴うアプリケーションでは絶対性能はまだ Java の方が上。(ただし、これが localhost かつ、HTTP サーバを介さないベンチマークであるので、実アプリでの性能ではないことには注意)
  • SAStruts + S2JDBC は総じて優秀
  • (キャッシュ効きまくっているはずなのに)Hibernate 遅い ので、引きずられて Play がそんなに速くならない
    • とはいえすごく遅いわけではない。元記事の Jersey + Hibernate よりはスループット出ているので
    • Play + S2JDBC とか、Play + Doma とかだと、また違った結果になるかも

Play の evolutions を Heroku で使おうとしてハマった

Database-Driven Web Apps with Play! | Heroku Dev Center

(we don’t recommend setting jpa.ddl to update for a real world production app. Use Play!’s database evolutions instead.)

Heroku の Play のチュートリアル記事に「jpa.ddl=update が許されるのは開発環境までだよねー。本番環境では Play の evolutions を使ってね (・ω<) テヘペロ」(意訳)って書いてあったので、Heroku の Shared Database (PostgreSQL) で試してみたら色々とハマったので原因と解決方法を書いていこうと思います。

お勧めするならやり方くらい書いておいて欲しい・・・

結論

先に結論だけ書いておきます。

  • エンティティは play.db.jpa.Model ではなく、play.db.jpa.GenericModel を継承するようにする
  • id プロパティに @GeneratedValue(strategy = GenerationType.IDENTITY) をつける

注意

上記のチュートリアルをベースにエンティティ名だけ User から Account に変更しています。(User は PostgreSQL予約語でテーブル名として利用できないため。)

エンティティ作成

@Entity
@Table(name = "account")
public class Account extends Model {

	public String email;
	public String fullname;
	public boolean isAdmin;
	
	public Account(String email, String fullname, boolean isAdmin) {
		this.email = email;
		this.fullname = fullname;
		this.isAdmin = isAdmin;
	}

}

evolutions ファイル作成

evolutions ファイルを db/evolutions/1.sql というファイル名で作ります。

# Accounts schema
 
# --- !Ups
 
CREATE TABLE Account (
  id bigserial NOT NULL,
  email varchar(255) NOT NULL,
  fullname varchar(255) NOT NULL,
  isAdmin boolean NOT NULL,
  CONSTRAINT pk_account PRIMARY KEY (id)
);
 
# --- !Downs
 
DROP TABLE Account;

Heroku 上で evolutions を実行

heroku run コマンドを利用して、play evolutions:apply コマンドを heroku 上で実行します。

$ heroku run play evolutions:apply
unning play evolutions:apply attached to terminal... up, run.1
~        _            _ 
~  _ __ | | __ _ _  _| |
~ | '_ \| |/ _' | || |_|
~ |  __/|_|\____|\__ (_)
~ |_|            |__/   
~
~ play! 1.2.4, http://www.playframework.org
~
~ Connected to jdbc:postgresql://xxx
~ Application revision is 1 [f9f2691] and Database revision is 0 [da39a3e]
~
~ Applying evolutions:

# ------------------------------------------------------------------------------

# --- Rev:1,Ups - f9f2691

CREATE TABLE Account (
  id bigserial NOT NULL,
  email varchar(255) NOT NULL,
  fullname varchar(255) NOT NULL,
  isAdmin boolean NOT NULL,
  CONSTRAINT pk_account PRIMARY KEY (id)
);

# ------------------------------------------------------------------------------

~
~ Evolutions script successfully applied!

アプリにアクセス

アプリを実行すると、Bootstrap の Fixtures.loadModels() で落ちます。

A javax.persistence.PersistenceException has been caught,
org.hibernate.exception.SQLGrammarException: could not get next sequence value

どうやら play.db.jpa.Model の id プロパティの @GeneratedValue の strategy が指定されていないため、デフォルトの AUTO が設定されるのですが、これだと PostgreSQL の bigserial から自動シーケンスを取得できない模様。


Confusion over @Id property - error with postgresql database -
play-framework |
Google Groups

Entity を GenericModel を継承するように変更

public class Account extends GenericModel {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;

// ... 以下は同じ
}

これで OK です。heroku restart して再起動すれば、アクセスできるようになります。

考察

じゃ、なんで jpa.ddl=update だと上記の問題は起こらないのかというと、これは Play が内部的に使っている Hibernate の実装のせい(おかげ?)です。

Hibernate はデータベース作成時に hibernate_sequence というシーケンスを作成し、@GeneratedValue アノテーションが付与されたプロパティの値は、そのシーケンスから取得するように実装されています。このため、jpa.ddl=none を指定して evolutions を利用すると、この機能が利用されないのでエラーになるわけです。

個人的にはシーケンスがエンティティ間で共有されて、データベース単位になるのがちょっとどうかなと思うので、本番環境ではお勧めされた通り evolutions を使っていこうと思います。

おまけ

実際のアプリを作成する際には id 以外の属性も共通でつけると思うので、次のようなクラスを作って、それを継承するようにすれば良いんじゃないかと思います。

作成、更新、(論理)削除日時、楽観ロック用のバージョン番号を共通列としてもつエンティティの例

@MappedSuperclass
public class MyModel extends GenericModel {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	public Long id;

	@Column(updatable = false)
	@Temporal(TemporalType.TIMESTAMP)
	public Date createdAt;

	@Temporal(TemporalType.TIMESTAMP)
	public Date updatedAt;

	@Temporal(TemporalType.TIMESTAMP)
	public Date deletedAt;

	@Version
	public Long versionNo;
	
	@Override
        public Object _key() {
            return id;
        }
	
        // Fixtures.loadModels() が内部で Model#_save() を呼んでデータを投入している
        // 作成日時などは YML ファイルに書きたくないので、自動的に設定されるように
        // _save() メソッドを override しておくと便利
	@Override
	public void _save() {
		Date now = DateUtil.now();
		if (id == null) {
			createdAt = now;
		}
		updatedAt = now;
		super._save();
	}

}

おまけ2

デフォルトの Hibernate のデータベースの列名が区切り文字を入れてくれない( isAdmin -> isadmin となる)ので変更する設定。

conf/application.conf に以下を追記

hibernate.ejb.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy

これで isAdmin -> is_admin みたいに Camel Case を Under Score で区切ってくれるようになります。