Firebase Functionsでデプロイしているバージョンを表示したい

おっす!オラFirebase好き彦!

いまやってる新規サービスはFirebase Functionsを使って開発してて毎日のようにデプロイしてるんだけど、妙な挙動に遭遇したときにそれがどのバージョン*1のFunctionで起こったことなのか特定する必要が出てきた。Functionが実行されるときにそのバージョンがわかればいいな〜と思ったんだけど、環境変数等でパッと取得できそうになかったのでデプロイされているバージョンのコミットハッシュをファイルに書き出して読み込むようにしてみたぞ!もっと良い方法があったら教えてくれよな!

1) コミットハッシュを生成するnpm taskを追加する

一番安直にgitコマンドでコミットハッシュを取ることにした。

<!-- functions/package.json -->

"scripts": {
  "commit_hash": "echo `git rev-parse HEAD` > COMMIT_HASH"
}

npm run commit_hash で動作確認する。 COMMIT_HASHecho 'COMMIT_HASH' >> ../.gitignore とでもしておこう。

2) デプロイ時にcommit_hashを実行する

predeploycommit_hash を追記する。

<!-- firebase.json -->

"functions": {
  "source": "functions",
  "predeploy": [
    "npm --prefix \"$RESOURCE_DIR\" run build",
    "npm --prefix \"$RESOURCE_DIR\" run commit_hash"
  ]
}

--prefix \"$RESOURCE_DIR\" がないとプロジェクトルートの package.json からタスクを探そうとするので注意。

3) Functionから読み込む

あとはFunctionから読み出してログに書くなりレスポンスに含めるなり好きにできる。

import { readFileSync } from 'fs'

function readCommitHash(): string {
    const commitHash: Buffer = readFileSync('COMMIT_HASH')
    return commitHash.toString('utf8')
}

4) deploy

$ firebase deploy

いや〜Firebaseっていいですねっ!

*1:Function自体のバージョンではなく自分のアプリケーションのバージョンね

「ブロリーです…」

若者がドラゴンボールを読んでみたら大しておもんなかった的な記事が非常に話題であります。これに関しては「せやねん」と「せやかて工藤」の両方の感情がございます。

一番申し上げたいことを最初に書くと、結局のところ、不可逆なほどこの世に絶大なるインパクトを与えてしまった作品の凄さというのはその前後を目撃した者にしかなかなか伝わらないものなのであります。

件のエントリの「どこかで見たことのある絵」「よくあるストーリー」という評がいみじくもドラゴンボールの凄さそれ自体を表しているのです。ドラゴンボールが現在に脈々とつづくこの絵柄やストーリーを作ったのです。だから時をさかのぼって現在の作品とドラゴンボールを比べるのは、モーツァルトを聴いて「ありきたりな曲調」という感想を述べるのと同じくらい無意味なことです。浦沢直樹が「浦沢直樹の漫勉」で「大友克洋の衝撃は結局いまのひとが大友克洋の漫画を読んだってわからないんですよ。いまは大友克洋『以後』の世界なんだから」というようなことを言ってましたがまったく同じことです。

 

ちょっと話は逸れるんですが僕は同じような切なさともどかしさをダウンタウンに感じています。よく若い人が「ダウンタウンなにがおもろいねん」「普通やん」と腐すのを聞いて胸を傷めています。たしかに彼らは歳をとって昔ほどのキレはなくなったかもしれないけど、ダウンタウンの登場は我々の世代にとってドラゴンボールに匹敵する衝撃だった。

バナナの皮に滑って転んでみんなが笑うドリフ的お笑い観を、ゆるいスピードと低いテンション、斜に構えたニヒルでシュールな2000年代のお笑いに変えた。現代お笑いの基礎を作った偉大なコンビなんです。いわば示準化石なんですよ。それ以前と以降で時代がまったく違うんです。いまの普通を彼らが作ったんです。いまだにごっつええ感じのDVDを観ることがあります。いまの人が見ても面白いと思うけど、やっぱりどうしても「この10年前を知ってる人」がこれを見る衝撃とは違うと思うな。

 

いま求職中でして、よくコーディングクイズを解いています。コンピュータ・サイエンスの世界には「動的計画法」というアルゴリズムがあります。これは、普通に取り組むと解けないような膨大な組み合わせから最適解を得るようなときに効力を発揮するアルゴリズムなんですが、この基本的な考え方は「以前のループの結果を保存しておいて、いまの方が結果が良ければ置き換える」というものです。

漫画も、お笑いも、コンピュータ・サイエンスも過去の偉大な資産があって現在があります。現在は過去の延長線上にしかなく、いまの我々が過去より少し良いものを簡単に手にできるからと言って、それによって過去の栄光にいささかのケチがつくものではありません。先人の達成には常に敬意を払いたいものであります。

 

ちなみに僕がドラゴンボールで一番好きなシーンはタオパイパイが自分の投げた石柱に乗って移動するシーンですね。物理学的には、投じた石柱に飛び乗る跳躍力があれば目的地までそのままジャンプできることになるそうです。

それじゃあ来週もまた見てくれよなっ。

エンディングはスタンド・バイ・ミーで宜しく頼む

常に冷静沈着、無表情、チート忍術で相棒のケン一氏のテンションを上げたり下げたりする伊賀の忍者ハットリくん

普段の彼の姿からは想像もつかないが、ハットリくんが涙を流す回があるのをご存知だろうか。

アニメ第187話「忍法虫変化?!の巻」である。

 

ある日ケン一が学校から帰ると、テレビのクイズ番組に当選したことを知らせるハガキが届く。家族が勝手に応募していたのだ。

優勝賞品はヨーロッパ旅行とあって、お父さんは大張り切り。ケン一にクイズの猛特訓を施すが、人一倍のあがり症とあってケン一は本番前にハットリくんに「緊張をほぐす忍術を授けてくれ」と懇願する。

ハットリくんは「拙者がキリギリスに化してついてゆくから安心するように」とケン一を諭す。果たしてハットリくんはキリギリスに化したが、実際にはただのキリギリスを虫かごに入れて渡して自分は隠れ、ケン一を安心させただけなのであった。

 

特訓の甲斐とハットリくんの加護(と思い込んでいる)あってケン一は順調に正解を重ね、ヨーロッパ旅行にあと一歩まで迫る。

ところがテレビ収録のあまりの照明の熱にキリギリスが瀕死の状態に陥っていることに気付いたケン一は狼狽し、目前にある栄光をすべて放棄して泣きながらキリギリスを介抱することを選ぶ。

傍観していたハットリくんも「ケン一氏、そこまで拙者のことを…」と感涙する。そして、このままキリギリスが死んでは二度とケン一の前に姿を現すことができないと悟ったハットリくんは伊賀の里に伝わる秘伝の薬を使ってキリギリスを蘇生させ、元気になったキリギリスが飛び跳ねた瞬間煙幕を張ってキリギリスと入れ替わる。「いやあ、ご心配をかけたでござる…」

 

真の友情は伊賀忍者の覆面すらも湿らせるのでござった、ニンニン。

「芸能人やからってかしこまらんでええからね☺️」

部屋に入るとチュートリアルの徳井じゃない方に着席を促された。

「気にせんでええよ、おれもITとか勉強して行きたいなと思てたとこやし☺️」

部屋にはチュートリアルの徳井じゃない方しかいない。

「ほんで、なに?本書いてんの?Androidの、テスト?

 うーん、ええんちゃうかなと思うけどね俺は。

 ほんで、なに?下世話なハナシやけどどのぐらい売れんの?

 千?千かー。うーん。

 まあ、アレやん?書くことが大事いうかね。ほら名刺代わりにもなるし」

名刺代わりなぁ。

それからチュートリアルの徳井じゃない方は、芸人の根性を見せるために僕の目の前で本物の中華鍋を使ってアッツアツの八宝菜を炒めはじめ、直接素手で掴んで白菜をムシャムシャ食べ始めた。こっち!めっちゃこっち見てるから!

「まあITやろ?わかるわかる、ロンブー淳にも言うとくし☺️」

 

という夢を見たよ。

「Androidテスト全書」アーリーアクセス公開と査読のお願い

いま書いている「Androidテスト全書」は初稿がほぼ出揃い、アーリーアクセスが始まりました。僕の担当分では1, 2章をお読みいただけます。

お時間のある方は是非ともフィードバックください。少し時間を置いて読み直してみると、既に自分でも構成のまずい部分や言葉足らずなところが見えてきて忸怩たる思いです。ただ、ここから逃げずに真摯に向き合わないと本は良くなりません。どんな細かいところでも、感じた違和感でも良いので教えていただけると嬉しいです。

 

それから個人的には3章が僕の担当分の山場でして既に数人の友人に査読をお願いしたのですが、こちらももし僕の知人で興味のある方は是非査読していただけると嬉しいです。

  • 非同期処理のテスト
  • DBのテスト
  • RxJavaのテスト
  • 多層アーキテクチャ(MVP)のテスト
  • テストのないプロジェクトにテストを導入する

あたりを書いていて識者の意見を強く求めています。公開前原稿をお渡しする関係で、こちらに関しては知り合い限定となりますが、もしTwitter, Facebook, LinkedIn等でつながっている僕の知り合いでご協力いただける方はこそっと教えてください。

新規事業をひとりで作るノウハウ

生存報告も兼ねて。
 
カリフォルニアに来てもう半年ぐらい経った感覚ですが、実はまだ4ヶ月ほどでした。非常に多くの素敵な方々との出会いがあり、妻も僕も子供もこの皆さまの助けがあってどうにか生きております。どう感謝してよいか言葉にできないほどです。
 
さて、ビジネス上の僕のミッションは次の3つです。

  1. 主に投資や連携目的の交渉(の技術面のサポート)
  2. 日本との連携
  3. 新規事業の開発

どれもなかなか難しいです。会ってアポぐらいなら応じてくれる会社も多いですが、投資や連携といってもバブル崩壊以後経済成長できていない我が国はもはや「商習慣だけめんどくさいのに今やカネも持ってないから相手にしてられない連中」というのは肌で感じます。ご存知の通り、サンフランシスコ・ベイエリアはIT企業会社員が年収5000万円もらうような場所です。なかなか同じ規模感で会話するのが難しいレベルに達しています。

さて、クヨクヨもしていられないので、僕はプログラマの本分たるプログラミングによって少しでも生きた証を残すより他ありません。渡米以来自分の持ち時間の30-40%ぐらいを使って新規事業のプライベートアルファを公開するに至りました。対象ユーザが弊社内記者ということもあり、現時点でみなさまにお使いいただけないのは残念でしょうがないのですが、たったひとりでもサービスを公開まで持っていけるというのは我々にとって明るいニュースなので書いておこうと思います。
 

TL; DR いま新規事業やるならFirebase1択

今回自分が作ったのはウェブサービスですが、とにかくFirebaseが強いです。僕がこれから起業するなら、まずはFirebaseで作って運良く流行りそうならお金がかかる部分だけ書き換えるみたいなアプローチを間違いなく採るでしょう。今回使った技術スタックは次のとおりです。

TypeScript

後述するCloud FunctionsがNodeランタイムで動くのでJavaScriptは好むと好まざるとにかかわらず使わざるをえませんでした。僕は強い静的型付けが好きなのと、FunctionsにTypeScriptの例がたまたま載ってたのでTypeScriptを選びました。Flowを選ばなかったのは「店頭に並んでなかった」ぐらいのことで深い理由はありません。

まったく初めてでしたが次の2冊を読んで各30分の計60分でとりあえず書けるようになったのでおすすめです。そもそもC言語の子孫の言語なんてどれも青森弁と山形弁ぐらいの違いしかありません。

比較的快適に開発していますが、関数オーバーロードがさながらC言語のプロトタイプ宣言であるところや、これらで引数に取るインタフェース型がtype erasureによりinstanceofが使えないことから少し持って回ったようなハックが必要な点などいくつかまだ慣れないところはありますが、許容範囲というところです。

tsc watchは文句なく素晴らしく、息をするようにトランスパイルしてくれます。JSをまったく意識することがありません。

ユニットテスト

ユニットテストchai, mocha, ts-nodeを使っています。このエントリを真似しました。

Unit testing node applications with TypeScript — using mocha and chai

async/awaitを多様しているのでプラスしてchai-as-promisedを入れています。次のように使います。

import * as chai from "chai";
import { expect } from 'chai';
import * as chaiAsPromised from "chai-as-promised";
import 'mocha';
import { NLP } from '../nlp';

before(() => {
    chai.use(chaiAsPromised);
    chai.should();
});

describe('NLP', () => {
    const nlp = new NLP();

    it('should return [Amazon Alexa]', async () => {
        const keyword = "Amazon Alexa の取材";
        const list: string[] = await nlp.analyzeEntities(keyword, defaultFilter);
        expect(list).is.not.empty;
    });
});

ts-nodeでテストケースを個別に実行する方法がよくわからんかったので

{
  "name": "functions",
  "scripts": {
    "testall": "mocha -r ts-node/register src/**/*.spec.ts",
    "test": "mocha -r ts-node/register src/spec/$spec.spec.ts"
  }
}

みたいにして全体実行はnpm run testall, 個別実行はspec=[クラス名] npm run testみたいにしています。
もっといい方法があったら是非コメントください。

namespaceがイマイチ使いこなせない

なにぶん全てひとりでやっており誰もコードレビューしてくれないので経験者からすると眉をひそめるような作法で書いている可能性が高いです。
たとえばnamespaceはKotlinのpackageの感覚でディレクトリを掘ってファイルも分けていますが、次のようにfoo.tsとbar.tsを別ファイルに分けて同じ名前空間の下にぶら下げた場合、

// my_service/foo.ts
export namespace MyService {
    export class Foo {}
}

// my_service/bar.ts
export namespace MyService {
    export class Bar {}
}

利用する側で名前空間がバッティングしてるとimport * as aliasするしかなくてなんか不格好です。これはこういうもんなんでしょうか🤔
たぶんTSの作法がある気がするのでTS Wayをどなたか教えてください。

// import {MyService.Foo} from './my_service/foo'  no good!!
// import {MyService.Bar} from './my_service/bar'  no good!!

import * as foo from './my_service/foo'
import * as bar from './my_service/bar'

export class Baz {
    qux() {
        const hoge: foo.MyService.Foo = new foo.MyService.Foo()
        const fuga: bar.MyService.Bar = new bar.MyService.Bar()
    }
}

Cloud Functions

いわゆるサーバレスにファンクション単位でデプロイできるやつです。これはとにかく素晴らしいです。

export const foo = functions.https.onRequest(async (request, response) => {
    try {
        const token: Credentials = await readToken(request.query.account)
        const resp = doSomething(token)
        response.send(resp);
    } catch (error) {
        sendError(response, error)
    }
});

こんなの書いておくだけで設定したエンドポイントに対応するファンクションが起動します。
HTTPトリガの他にバックエンドで各種イベントトリガに応答して処理を行うことができます。後述の通りPub/Subも使えます。
これの何がいいって、フレームワークだのミドルウェアだのつまらない知識をためなくても一筆書きでとりあえずサービスを提供できるんですよね。

サーバレスというと弊社ではAWS Lambdaを使っていますが僕はCloud Functions 1択でした。それはFirebaseとの連携性です。FirebaseのリアルタイムDBであるFirestoreのドキュメントへのイベントを検知してFunctionをトリガできるのは便利です。他にも認証トリガなんてのもあってアツイです。

Cron Job

単独でcron jobはできませんが、これはGAEと組み合わせればできます。次のエントリに詳しいです。

developers-jp.googleblog.com

端的にいうとGAEの持つCron機能をPub/Subで購読するというものです。GAEを一切使ったことがなくてもこのエントリの通りにすれば'hourly-tick'だの'minutely-tick'だのに反応するファンクションを書いておけばトリガされます。

export const batch = functions.pubsub.topic('hourly-tick').onPublish(async (event) => {
 // do whatever you like
});
Local Emulation

ファンクションはデプロイする前にローカルでいくらでも試せます。

Run functions locally  |  Firebase

リンクにほとんど書いてあるので特に補足はないのですが、firebase serve でローカルにまるっきりfirebase hostingとcloud funcionsのローカル版が立ち上げるのでcurlなりブラウザなりで試すことができます。
firebase functions:shellとかするとTCP/IPではなく対話的シェルのような感じでファンクションごとにエミュレートができます。ここでFirestoreの読み書きもテストできます。
個人的には前者で実際にFirestoreにつなぎに行ってInstrumentedにテストできるので後者はほとんど使ってませんが便利な使い方を今後発見するかもしれません。

それから、OAuth2のリダイレクトとかをテストするときにLocal Emulator環境なのかデプロイ後だったのか区別する必要があり、これはちょっと簡単にわからなかったので環境変数のようなやつを使いました。

Environment configuration  |  Firebase

これはコマンドラインから設定できる環境変数的なものです。

firebase functions:config:set my_service.is_local=true

みたいにして設定するとサーバに直接設定されるんですが、ファンクションのディレクトリに.runtimeconfig.jsonという形式で置いておいて適宜中身を書き換えるとローカルエミュレータではその値が使われます。アクセスするのはローカルもデプロイ後も同様です。

firebase functions:config:get > .runtimeconfig.json
const IS_LOCAL: boolean = JSON.parse(functions.config().my_service.is_local);
const HOST_NAME = IS_LOCAL
    ? `http://localhost:5001/${process.env.GCLOUD_PROJECT}/us-central1`
    : `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com`;

Cloud Firestore

これはドキュメントベースのNoSQLなんですが、JSON状のデータ構造のあるパスを購読しておくとそこに対する読み/書き/削除/更新を全部通知してくれます。
オフライン対応してる、というか、利用者はすべてローカルコピーを真として読み書きする作りになっており、「オフラインになった!」という状態が存在しないと考えることができます。
結果、安全にデータの読み書きができます。
オンラインになるとこのローカルコピーは勝手にサーバのマスターデータともいうべきものと同期をかけます。同期後はそれが前述の更新イベントとして飛んでくるだけというわけです。

ここに敢えて貼りませんが、僕は2014-5年からFirebaseユーザでその頃はFirebaseといえばこのリアルタイムデータベース(いまのFirestoreはその後継)しかなく、僕はそれの連載記事を持っていたぐらい愛好者でした。
で、新規事業にあえてこのリアルタイムDBを使う必要もないんですが、別に使わない理由もなくて、割と雑になんでもデータを突っ込んでいます。ここも高くなったら置き換えるぐらいの気持ちです。早すぎる最適化は不要です。

Firebase Authentication

これは認証を楽ちんにしてくれるライブラリです。

Get Started with Firebase Authentication on Websites  |  Firebase

これもほとんどコメントの必要もないほど充実してるんですけど、認証時にFirebase内で一意なユーザ識別子を持って、あとからGoogleだのGitHubだの別の認証プロバイダで認証したときにそれらを全部ひもづけることができるので、複数の認証プロバイダに対応予定なら激つよです。

一点、この方法だとリフレッシュトークンがどうやらもらえない(リフレッシュ自体はできる)っぽくって、リフレッシュトークンがないと裏でCron Jobでトークンを更新しつつ他のAPIを叩くみたいなのができないので、僕は結局後述するGoogleのOAuthライブラリで手認証するのと組み合わせています。

Google APIs Node.js Client

これがとにかく素晴らしい。Google謹製のNodeライブラリ群ですけど、

  1. TypeScriptで書かれていて型情報がそのまま利用できるものがほとんど
  2. 非同期処理はほとんど余すところなくPromiseを返すようになっており、こちらが努力ゼロでasync/awaitできる

もう説明の必要もないですね。async/awaitできると同期的に待ち合わせるように非同期のコードが書けるし、他言語で同様の仕組みをもったプログラマが参入しやすいです。プログラムのバグと苦しみは非同期処理に根ざすものが非常に多いですからね。
一点、Listをmapしながら全てのasyncの完了を待ち合わせるような処理は書き方をすぐ忘れるのでメモしときます。

const tokens: [string, Credentials][] = await readTokens()
const resultAll = await Promise.all(
    tokens.map(async token => {
        const identifier = token[0]
        const credential = token[1]
        return result = await doSomethingWithToken(credential)
    })
)

あとは複数の非同期処理を一気にdispatchしてzipper関数で待ち合わせてまとめるzipみたいなのはちょっとまだ見つけてません。
まあそのものズバリのRxJSってのもあるしなんとでもなりそうです。

Cloud Natural Language

最後ですけどGoogle製の自然言語処理のライブラリを使いました。ノイズの多い文章から品詞ごとに取り出したりするのに非常に便利です。
この部分はサービスが大ヒットしたら自前のライブラリで置き換えても良いのだし、最初にNLP詳しくない僕みたいな者が曲がりなりにも実用に耐えうるデモを作れるのは、この分野では考えにくいことだったので、まったくありがたい話です。

まとめ

「ガチガチにロックインされてそうだけど大丈夫?」的な質問を飲み屋の会話(僕は一滴も飲めませんが)レベルでされたことがあったんですが、それが何か?という感じです。
言語、フレームワーク、思想とプログラマは色んなものにロックインされています。価値検証段階でそれらが何かにべったり依存していることは取るに足らないことだと思います。そもそも僕の稼働時間は僕一人が活動時間の3-4割で実質ひと月ぐらいで書いたので、本当にイケるとなったらいくらでも書き換えたらいいです。

最後にこの開発を開始して僕の人件費を仮にゼロと考えた場合の総コストは…!

f:id:fushiroyama:20180807042638p:plain

ジャーン!開発期間中0ドル、サービスインして本日までの合計は77セントでした!ありがとうございます!

フォーチュンクッキーは恋をしない

ジョジョのサブタイトル風に。

 

中華料理屋でもらえるフォーチュンクッキーマジでいらないんだけど捨てるほどではない。で、ほっとくとパンダエクスプレスヘビーユーザーの私の机の上には一生食われないフォーチュンクッキーがうず高く積み上がってそのうちカリン塔に届きそうである。

 

f:id:fushiroyama:20180526072702j:plain

 

これ何かに似てるなと思ったら、サービス開発時にユーザのことをよく考えずに"良かれと思って"機能を付け足しまくった挙げ句、捨てるには惜しいからそのまま残した結果、本当に必要な機能がウォーリーをさがせになるあの状況だ。要らないものは最初から付けないのがベストなのである。

 

サービス開発でもっとも重要なことは「足す」ことではなく「引く」勇気だ。従ってこれから僕は要るかどうか疑わしい自称・付加価値のことをフォーチュンクッキー・フィーチャーと呼称して、積極的に排除するキャンペーンを実施する。みんな積極的に使ってくれよな!