バスキュール技術ブログ

バスキュールが得意とするインタラクティブエンジニアリングを、あますことなくお届け!

インスタレーションを支えるバックエンドの話

こんにちは、丸山です。
12月に開催されていたCOFFEE MOMENT ENSEMBLE(以下、CME)や願いの森というインスタレーションイベントでバックエンドを担当しました。

CME動画

願いの森動画

この記事では『インスタレーションを支えるバックエンドの話』というテーマでCMEを具体例としてお話します。

構成

全体の構成は以下のようになっています。

cme arch

スマホアプリ→Nestle内のAppサーバー→CME Appサーバー→店舗内機器
という流れで"コーヒーを淹れた"というイベントが伝播されます。
CME Appサーバー以降がバスキュールの担当範囲になります。

Appサーバー

Appサーバーは主にWeb APIとWebSocketを実装しています。

“コーヒーを淹れた"というアクションに対してイベント発火とメインビジョン用の統計処理を行っています。 統計は都道府県別ランキング、時間帯別の小計、人気のメニューなどがあります。

f:id:bascule-dev:20170407192702j:plain

インフラは素朴にALB, EC2, RDS, ElastiCache(Redis),S3という構成で、
Webアプリは主にRailsを、WebSocketまわりでNode.jsを使っています。

クリエイティブ系におけるRails最高

私は負荷、外部要因などを気にしなくていい案件ではデフォルトでRailsを使っています。
RailsというとWebサービスで使われているイメージがありますが、インスタレーションを含めクリエイティブ系においてRails(と周辺gem)は最高に使いやすくて、

  • クリエイティブ系は基本的にアプリを使い捨てていくのでセットアップ機会が多い。Railsはセットアップ時間が短くて最高。
  • 画像や動画などのメディアを扱うことが多く、基本的にS3にあげる。carrierwave最高。
  • ユーザー用に画像や動画の動的生成をよくするのでジョブキューをよく使う。sidekiqよくできてて最高。
  • 管理画面が基本的に必要。これも使い捨てなので手間をかけたくない。ActiveAdmin楽さとカスタマイズ性のバランスが最高。
  • いつもJSON APIを組むがフロントエンドのメンバー用にドキュメントが必要。これも使い捨てなので手間をかけたくない。grape-swaggerAPI実装がそのままドキュメントにつながって楽だしドキュメント更新忘れがち問題も起きにくくて最高。

ということで、最高です。

クリエイティブ系は常に新しいことをやっていきたいという意識なのでそこに集中したくて
それ以外はとにかく楽をしたい、というところでRailsと周辺gemは熟れてるし強いなと感じます。

現場ハブ

CME Appサーバーと店舗内の各機器をつなぐ、中継であり中央指令的なPCを現場ハブと呼んでいます。 現場ハブはNode.jsで実装しています。

Appサーバーに対してはWebSocketのクライアントとして、店舗内の各機器に対しては適したプロトコルに応じてWebSocket、OSC、UDPで通信しています。
現場ハブの特徴としてFSMをベースに実装している点があります。
イベント系ではよくFSMを使っているのですが、割とおすすめなのでここではFSMに絞って紹介します。

状態遷移とイベント発火の多いインスタレーションにFSM

FSMでググる空飛ぶスパゲッティ・モンスター教がトップに出てきますが、そちらではなく有限オートマトン の方です。 wikipediaにはいろいろ小難しいことが書いてありますがコードを見た方が早いかと思います。
Node.jsにもいろいろFSMの実装がありますがMachina.jsが個人的には好みでよく使っています。
サイトに載ってる車の例のように、FSMを使うと状態遷移と各状態において操作をしたときの処理をキレイに分けることができます。

今回の案件では

  • default - 定常状態
  • dispStats - 統計表示状態
  • performing - 演奏状態
  • suspend - 緊急時の中断状態

という4つの状態を定義しました。
CMEでは例えば

  • 演奏開始命令がサーバーから飛んできたときに
    • 定常状態なら演奏を開始する
    • 統計表示状態なら表示が終わるまで待機して終わってから演奏開始
    • 演奏状態なら何かがおかしいのでエラーログに記録する(演奏は待ち行列で管理しているのでキュー処理が間違っているなど)
    • 中断状態なら中断が解除されるまで待機
  • ピタゴラ装置から演奏完了イベントが飛んできたときに
    • 定常状態なら何かがおかしいのでエラーログに記録する(演奏をタイムアウトして定常状態に強制的に戻したあとに飛んできたなど)
    • 統計表示状態も同上
    • 演奏状態なら状態を定常状態に戻す
    • 中断状態なら無視する

といったようなことをしていて、これをif文で書いていくと死ねます。(↑では2つの処理を例にしているけど、これが5〜20個くらいある)
インスタレーション系だとだいたいこんな感じになるんじゃないかと思います。

FSMでこれを記述するとif文で書くときと構造が逆になる(状態をインターフェース、各状態をその実装クラス、各処理がメソッドになってポリモーフィズムするような感じ)ため、すっきり書けるし分岐し忘れのようなことも防ぐことができます。

分岐し忘れについてはMachina.jsだと

{
  //...
  performing: {
    '*': function (eventName) {
      logger.error('nohandler', eventName, this.state);
    }
  },
  //...
}

のようにワイルドカードで処理を定義しておくと未実装の場合のハンドリングをすることもできます。

まとめ

インスタレーションを支えるバックエンドでは、新しい体験づくりを支えるスピード感と安定感を心がけています。
バスキュールではインスタレーションイベントに力を入れており、フロントエンド、バックエンド、デバイスなど問わずインスタレーションしたい仲間を募集しています。