トランスパイラとは

木村功作氏:富士通研の木村です。「FaaS上のコードをもっとシンプルに書くためのトランスパイラ」というタイトルで発表させていただきます。

まず自己紹介させてください。木村と申します。

株式会社富士通研究所で、ソフトウェアの開発効率化・自動化の研究開発を行っています。最近発表したところだと、JAWS DAYSとか、あとAPSECというソフトウェア工学系の国際会議とか、そういったところで話をしています。

こちらのURLに論文のプレプリント版を載せていますので、もし興味がある方はこちらをご覧ください。

タイトルにもある「トランスパイラ」ってどういうものか、みなさんご存知でしょうか。最初に三宅さんがTypeScriptの話をしていましたけれども、TypeScriptも広い意味で言うとトランスパイラと言えます。

トランスパイラというのは、ソースコードからソースコードに変換するコンパイラのことを言います。コンパイラというと、高級度合いの違う二つの言語間で変換するような意味合いを持つかと思いますけれども、ここはあくまで同じレベルの言語の間で変換するというものになります。

例を挙げますと、TypeScriptもそうですし、あとはコードフォーマッターのPrettierとかwebpackとか、これらの一番ベースとなっているBabelというトランスパイラが有名です。

これらは同じJavaScriptの中で、型がある世界からない世界に変換したり、ぐちゃぐちゃだったものをきれいにしたり、読めるけど大きいものを読めないけどすぐにアップロードできるようなものに変換したりとか。あとは、まだない言語仕様を使えるかたちに変換したり、というようなものがあります。こういったものを日々使わないとコードが書けないぐらい、当たり前に使われている大事な技術だと考えています。

新しいものから古いもの、というのはこんな感じですね。

Node.jsのバージョン6までは下の書き方しか対応していなかったので、async/awaitをいち早く使いたかった方たちはBabelを使って変換していました。

コードをもっとシンプルに書くためのトランスパイラ

こういうトランスパイラなんですけど、サーバーレスの文脈で言うと、シンプルなものからちゃんと動くものに変換するものがあるといいなと思うわけです。サーバーレスで運用はだいぶ楽になってきたんですけれども、コードを書くところでまだあまり議論がなされてないのかなという感じもしまして。運用は楽になるけど、依然として書くコードはそれなりにある、という認識だと思っています。

サーバーレスなので、できるだけコードは書きたくないんですけど、かといってまったく書かないわけにはいかない。つまり自分が書きたいビジネスロジックはちゃんと書かないといけない、というものになります。

あと、Node.jsだと非同期処理の仕組みとして、コールバックやPromiseなどいろんなものがあるんですけれども、それを使ったAPIの非同期呼び出しだとか、そういったものを書かないといけない。難しいんですよね、そういったものを書くのは。けっこう大変です。

さらにFaaSにデプロイするときには、できるだけ軽くしてからアップロードしたいと考えるわけですけれども、そこで新しいプログラミングモデルを導入するにあたって、新しい追加のものを導入したいとは思わないわけですよね。それによってサイズが大きくなってしまうので。

そういったことを考えたときに、いい感じのシンプルさで書けて、実際の複雑な非同期処理に書き換えてくれるようなトランスパイラはないかな、と思うわけです。

で、あります。

というか今回、作りました。ということで、この「Escapin the Transpiler™(以下Escapin)」というトランスパイラをここでご紹介したいと思います。GitHubに公開していまして、npmかyarnで一発でインストールできるものになります。

Escapinの特徴

特徴としては、Serverless Frameworkと連携できるということと、DynamoDBテーブルとかS3バケットが連想配列みたいに扱えるということと、Swagger/OASで書かれたREST API呼び出しがJSONオブジェクト操作みたいになるということ。また最後に、JavaScriptで一番苦しいところである非同期処理のことを忘れられる、というものがあります。

これがEscapinのトランスパイル手順になります。

大きく3つ手順があるんですけれども、そこに追加でTypeScriptのコンパイラAPIを使った関数の型の判別をやっています。これらのトランスパイルには、Babelのtraverseとtemplateも使ったりしているんですけれども、最終的に左側のコードが右側のFaaSにデプロイするコードと、Serverless Frameworkで動かせるserverless.ymlに変換できるというものになります。

最初の部分、コールバック分解がどういうものかについてご説明します。これは平たく言うと、中途半端な非同期を一旦、同期側に全部倒すというものになります。

こんな感じで、左側のコールバックが入っている処理は、戻り値が返ってきたときにコールバックの中が実行されるというものになるんですけど、それを同期的に書くと右側のような感じになります。

関数の戻り値を受けてdoSomethingする、というような感じですね。それで、エラーが起きたらhandleErrorする。こういったものに一旦変換して、同期的に書き直します。

APIとクラウドに特化した詳細化

これから、APIとクラウドに特化した詳細化をしていきます。ここでこのようなimport文を使って、Swagger/OASのYAMLファイルのURIをモジュール名みたいに書いて、インポートできるようにしています。

こう書くことによってAPIという特別なオブジェクトができあがって、以下このようなメソッドとパスに対応したオブジェクト操作で、API呼び出しを書くことができるようになります。

ここではapi.items[id]というような書き方をしているものが、このメソッドリクエストのGET、「/items/:id」というようなAPI呼び出しに変換できるという例になっています。

これがAWSに対する詳細化の例になります。

ここでちょっとJavaScriptの構文から外れるんですけど、タイプアノテーションでDynamoDBとかS3を指定することによって、空のオブジェクト変数がDynamoDBテーブルなのかS3バケットなのか、判別できるようにしています。それで、こうやって宣言したオブジェクト変数、objに対する操作が、それぞれのAPI呼び出しに対応することになります。関数は直接、AWS Lambdaにマップされるものになります。

関数型の判別というところでは、繰り返しになりますけど、TypeScriptのコンパイラAPIを使っています。ここでは以下の4種類に、ゆるく判別しています。「asynchronous」はPromiseを返すもの。「error-first-callback」はPromisifyできるもの、つまり第一引数がerrorオブジェクトであるようなコールバックを引数に持つもの、「general-callback」はそれ以外のコールバックを引数に持つもの、「general」はそれ以外。大きくこの4つに判別します。

ここで、どれがPromiseを返してコールバックを受けるようなものなのかとチェックしていくと、厳密にやるとかなり大変なので。ここではちょっとだけサボっています。詳細はソースコードを公開していますので、そちらをご覧ください。

こういう詳細化を行ったあと、一番最後に、同期で書かれていたものを非同期に直してやる必要があります。ここではgetItemという関数に対して非同期化をやるんですけれども、こいつはもともとerror-first-callbackであったので、この関数の正しいシグネチャーとして、コールバックを付け直してあげます。さらにawait new Promiseで囲ってやる、というものになります。

error-first-callbackの場合はこういう変換になるんですけれども、それ以外のgeneral-callbackの場合はとくに変換なし、というルールを作っています。

最後になるんですけれども、今回OSSで公開しましたので、おもしろそうと思った方はぜひ触ってみていただければと思います。実は今日公開できたものなので(笑)、しばらくちょっと不安定な状態が続くと思いますが、それはご容赦いただければと思います。

実際に触っていただいて、「こんな機能ほしい」とか、改善案とか、感想とか、そういったご意見・ご要望をどんどん聞かせていただければと思います。

以上となります。ありがとうございました。

(会場拍手)