Clubhouseのユーザーインターフェイスを支えるObjective-Cの確かな信頼と実績

f:id:laiso:20210202231917j:plain

ClubhouseのiPhoneアプリは各所でお馴染みのObjective-Cライブラリが使用されており、アプリ自体は最先端のムーブメントながらもUIからはシニアの職人技を感じます。根拠はないですがアプリの実装もObjective-Cでゴリゴリ書いてそうです。

ここではそんなObjective-Cライブラリの一部を紹介します。

IGListKit

https://github.com/Instagram/IGListKit

Instagram開発チームのコレクションビューの差分描画最適化のノウハウが詰ったライブラリです。

アプリの肝となるフィード系の画面で使われています。

UIScrollView+InfiniteScroll

https://github.com/pronebird/UIScrollView-InfiniteScroll

無限スクロールを実現するライブラリです

FlagPhoneNumber

https://github.com/chronotruck/FlagPhoneNumber

TEL入力フィールドに登場します

DZNEmptyDataSet

https://github.com/dzenbot/DZNEmptyDataSet

empty state として使われています

SZTextView

https://github.com/glaszig/SZTextView

標準テキストフィールドの代替ビューです

autorelease

ありがとう Brad Cox

Hotwireの感想

Hotwire

f:id:laiso:20210124034255p:plain

https://hotwire.dev/

  • Turboを中心としたウェブアプリケーションアーキテクチャの要素技術やコンセプトをPRするための名称
  • Hotwireというライブラリがあるわけではない
  • 役割としてはMicro FrontendsとかReactのlearn once, write anywhereなどに似ている
  • アプリケーション実装言語非依存だけど現状Railsアプリケーションしか実用できる基盤がない

Hotwireの思想

  • アプリケーション開発者の生産性を上げることを目的にしていること
    • サーバーサイド言語でフロントエンドを実装したいアレではなかった
  • プログレッシブ(段階的に利用可能)であること
    • 必要な技術だけを使い無駄なことをしないことで効率化する
    • Hotwireが列挙する技術は1つづつ有効にできる
  • クライアントサイドでViewを差分更新する現在の主流のシングルページアプリケーション開発のオルタナティブなこと
    • 既存の方法は効率が悪いという作者たちの考えが反映されている
    • とくにビルドツール依存の開発が目の敵にされている
    • 逆にSPAが必要ない人やSPAのクライアントサイド開発者には刺さらなそう

Hotwireの仕組み

  • TurboというJavaScriptライブラリがある
  • これがウェブブラウザ上で動き続けることでアプリケーションに動的な機能を提供する
  • 通常すべてのドキュメントリクエストを乗っ取り、XHRで取得したコンテンツ(HTML)部分だけをブラウザではなくTurboが置き換える
  • turbolinksと呼ばれていたRailsの機能のことで、JavaScriptイベントの挙動に副作用が生じてよく無効化されていた。これがTurbo Driveという概念になった

Turbo Frame

  • Turbo Driveが書き換えるコンテンツを <turbo-frame> タグ以下で制御する仕組み
  • <turbo-frame> 内にある <a> タグや <form> タグでイベントが発生した時にXHRのレスポンスの内容を現在のViewに差分更新する
  • コンテンツ自体はサーバーサイドで出力された結果になる
  • <turbo-frame> 外のコンテンツを書き換えるオプションが用意されており画面の共通部分の更新などはここを経由する

Newボタンをクリックする。このボタンのマークアップがaだったりformだったりする。

Turboによって https://app.hey.com/folders/new へリクエストが送信されると <turbo-frame> でダイアログの中身が返される

iframeのようにこの中身を直接開ける

Turbo Stream

  • ユーザー側のイベントではなくサーバー側から更新をPushしていて更新する仕組み
  • それぞれのサイバーサイドフレームワークが各自の機能を使ってブラウザからWebSocketやSSEでメッセージを待ち受け、決った仕様のHTMLをDOMのAPIに渡すとあとはTurboが画面更新をする
  • それを turbo-stream のタグに定義してアプリケーション開発者がテンプレートに埋め込めるようにする
  • 制約上、サイバーサイドフレームワークへの依存が強い。turbo-rails ではAction Cableを使って実現されている

Stimulus

  • Turboがサーバーサイドで生成したHTMLをいかに画面に適応するかという役割を「のみ」を担っている
  • 更新されたHTMLに対してボタンが押された時にCSSクラス名を変えたいのだとか、クライアントサイドだけで完結すればいい処理を書く場所が必要でそれがStimulus
  • 必須ではないがTurboはアプリケーションで実行されるJavaScriptのロジック面倒を見てくれないので何かしろクライアントサイドのコードを管理する仕組みが必要
  • 特定のHTMLのブロックに対して割り当てるふるまいをJavaScriptのclassとして記述していく
  • その仕組みがHTMLをブロック単位で配信するTurboと相性が良い

Turbo Native

  • 動作環境がウェブブラウザではなくiOS/AndroidアプリのWebView上になる
  • https://hey.com/apps/ で利用されてる
  • WebView上のアプリケーションが効率よく動作するためにクライアントサイドSDKが配布されていて、アプリケーション開発者はこの基盤の上で利用する

Strada

  • Turbo NativeからiOS/AndroidアプリのネイティブAPIをブリッジして呼び出して開発できるようにする仕組み。未リリース
  • ウェブアプリケーションをブラウザとiOS/Androidアプリに共通でホストするという点でアプローチがIonicに近い
  • ただしStradaはリモートのウェブサイト経由でネイティブAPIを実行しようとしているところがIonic(Cordva/Capacitor)と設計が異なる
  • 通常この方法はAppStoreのガイドライン接触し問題になるソフトウェアが過去にいくつかあった

2.5.2 Appはバンドル内で完結している必要があります。他のAppを含め、指定されたコンテナエリア外に対するデータの読み書き、またはAppの特徴や機能を導入したり変更したりするコードをエリア外からダウンロード、インストール、実行することは許可されません。 https://developer.apple.com/jp/app-store/review/guidelines/#performance

  • ただしCodePushのように「既にバンドルされているネイティブ実行コードではないアセットファイルを外部から更新する」というスキームで回避できている例もある(しかし結局レビュワーガチャによってリジェクトされている)

https://github.com/microsoft/react-native-code-push#store-guideline-compliance

勘違いしていたこと

  • Phoenix.LiveView インスパイヤなのでWebSocketやロングポーリング必須だろう
    • Turbo Streamを動かす場合に必要なだけだった。WebSocketサーバーがなくても他の機能は利用可能

ReactやVueを使った開発と比較して期待できそうな点

  • サーバーサイドレンダリングがデフォルト
  • 実装するレイヤーが減ってシンプルになる
    • APIエンドポイントとクライアントがいなくなり、DBからデータを取得してViewまで落とし込むまでの距離が短かくなった
    • React Server Components の解説でもこの利点が書いてあった

不安な点

  • ReactやVueを前提とした便利なUIライブラリの存在
  • JavaScriptビルドパイプラインは回避することができるけど謎のJS/CSSをインストールするだけのgemを管理し続ける問題は残ったまま……(Railsの場合)
  • Turbo自体の学習コスト。テンプレートを細かく分けて記述するようになって、どこで何が起きているのか把握しずらかった
    • ReactやRxやMVVMにも複雑さを感じていたが慣れたので、いずれ気にならなくなるかも……
  • React以降にUIをデータと関数でプログラミングできるようになって前進した手応えがあったが、Stimulusで生DOM API+手続きコードのBackbone.js時代に戻されてしまったかのような不安
  • 実際 hey.com コンソールを開くと大量の contoller.js クラスが……
  • TypeScriptは諦めてるような雰囲気(ブラウザで動かないものだから)
    • 一旦型に保護されたプログラミングに慣らされると、取り上げられた時に不安になる

実際のプロジェクトでの利用

  • 今の主流のシングルページアプリケーションのスタックについてはモバイルアプリのAPI開発との共通化が影響していると思っているのでHotwireを全面的に使うか? という判断はWebView製のモバイルアプリを許容できるか。というところにかかっていると思う
  • WebView製のモバイルアプリがどんなものかというのは実際のHeyのiOS/Androidアプリで試すことができる
  • WebView製のモバイルアプリが不要、もしくは許容できるとしてもプロジェクトに充分なフロントエンドエンジニアやモバイルアプリエンジニアが居たとしたら技術スタックごとに分担した方が効率がいいという意見の人は多いと思う
  • なので「フロントエンドエンジニア+React Native+サーバーレスアーキテクチャで最小構成ではじめる」例のように、「RailsエンジニアがDBとAPIとSPAを同時に作らなくていい方法」として局所的な活用方法に最初はなるのではないか
  • 最小のRailsアプリケーションを開発して、少しづつシングルページアプリケーション化を取り入れていくという時の用途にも使える
  • 今までの考え方からすると、ある時えいやでSPA化するということをみんなしていた

この技術が分からん2020

My Ninja Book 285/365

2020年に作ったソフトウェアや開発技術をふりかえる で分かったことばかり書いたけど相変わらずなんべん勉強しても分からんな〜と思うことも多いのでそれもリストアップしてみることにした。

SQL

10年以上触っているはずだけど集合のイメージが頭に入ってこなくて全然文を組み立てられずにいる。ゆるふわORMを適当に使ってる。

CSS

10年以上触っているはずだけど制約のイメージが頭に入ってこなくて全然レイアウトを組み立てられずにいる。ゆるふわTailwindCSSを適当に使ってる。

Unity

何回もダウンロードして教材を買ってるんだけど。アセットを組み立てて何か意味のあるものを作るっている状態まで行かない。Flashは使いこなしていたはずなのになぜ

UIデザイン

作る時に一定の理屈っぽいこだわりがあるんだけど、何か自分で作るというところまでいかない上に、深く理由を考えたことすらなかったので、こだわりがあるけど関心が低い。という変な状態だと思う。

VSCode

IntelliJ IDEA派だからなんだけど。VSCodeで使うことを前提とされたツールとかを見て大丈夫かなこれと思ったりする。

GoやRust

最近のシステム言語を身につけた方がキャリアの広がりができてよいとずっと思っているんだけど、何か別の言語で簡単に同じことできるじゃんという邪念が邪魔して真剣に取り組めてない。書けば分かるよ? 書けばな〜。必要性がな〜。と言い訳している

ネットワーク

サブネットとかルートテーブルとか概念は分かるがいつもはい…はい…と念じながら整えている感覚があり気持ち悪い。

数学

ありがち。必要→納得→興味→関心のパイプラインにすら到達していない気がする。

競プロ

アルゴリズムコンピュータサイエンス、数学ぐらいの具象化されたものより抽象的なものを求めているのか楽しさが分かってなくてのめりこめずにいる*1

あと単に基礎知識が不足している。

英語

英語がしゃべれないと死ぬ(比喩)ような環境に飛び込んでたらいける論があるけど普通に生活に支障が出ているし生存バイアスから漏れてしまっている。

人間の気持ち

オライリーブックス『詳解 人間の気持ち』が欲しい

そもそも——

プログラミング自体が数年がかりでできなかった。同時期にネットをはじめた知人がPerlCGIテキパキ組んで仕事をしているのをうらやましく見ていた。

その後、数年わからん続けて本屋で 改訂版 FreeBSD徹底入門 を買って読んでみたけど全然わからなくて読むのをあきらめた。

そのまた数年後 みるみるプログラミングがわかる本 を読んで壁を超えた気がする。この本自体が初学者にすごくおすすめというわけではなかったんだけど、特徴ある点としてはプログラミング言語とは言語の違いではなくて開発ツール(IDE)の違いですってことが書かれていたと思う。現在も開発環境おたく気質があるからそこが性に合っていたんだろ思う。

あと、自分は高等教育を受けることに失敗しているので、独学してみて分からなく挫折する状態に慣れているんだと思う。良い面もあるし悪い面もありそう。

分からんのスコープ

ソフトウェアで何かやりたいことがあるとして

  • やり方を知らべる
  • ドキュメントを読む
  • 実行する

という行動が取れる自信がある時は自分はそれを「できる(できそう)」にカテゴリしてるんだけど、他の人と話していたらそれは実績がないから「できない(やったことない)」に区分されるという意見を聞いてそうなんだと驚いたことがある。

それ以来は、分からないけどやったらできそうではある。となるべく正確に答えることにしている。

*1:https://b.hatena.ne.jp/entry/4695815452087964034/comment/redreborn を読んで思ったけど抽象的という喩えよりいい言い方があるのかもしれない

2020年に作ったソフトウェアや開発技術をふりかえる

概要

よくある年末っぽい日記の記事です。

だいだいこれどうりのバランスでソースコードも書いてる

  • 言語はなんでもいい時はNode.jsで書く。移植性が高いので。複数人でメンテしそうな時はTypeScriptを採用し、プライベートの時は型を完全に無視する
  • PHPはほぼLaravel。ビジネスのみの関係
  • Swiftはそんなに書いた記憶がないけどアプリのメンテをしてたと思う
  • Vueも仕事で使っていたけど最近はReactに傾いてる
  • Objective-Cは書いてない
  • グラフに含まれてない部分だとAndroidアプリでKotlinを使って、データ分析でPythonを書いた

このグラフは GitHub Profile Summary Cards っていう便利ツールを使わせてもらって自動生成している。

記録方法

  • コードを書く時はおもむろに ~/tmp 以下にディレクトリ掘ってIDEを開きはじめるので実質そこがプロジェクトサンドボックスみたいになってる。
  • なのでその場所を時系列に並べてチェックしていくことで何をやっているのか分かった。
  • あとはGitHubとかTwitterとかに記録が残っていた。

フロントエンド

ReactやVueに本格入門した年だった。

Nuxt

SPA+外部APIアクセスで開発するのに最適なウェブアプリケーションのプロジェクトがあったので採用した。

SSRが不要なのでvue-cliを使うか迷ったけれど、コミュニティの充実ぶりを見てNuxtにした。

複数人でメンテする可能性もあるので最初からTypeScript化した。苦心したのはStore層であんまり型が生かせてない書き方しちゃったり、vue/test-utils を使ったユニットテストがうまく構成できずにVueコンポーネントでラップしてテスト書いてたという部分。こういうのって他所の開発チームに実際入ってみないと作法がよく分からないなと感じた。

Amplify

外部APIAWSで動いてて、それにあわせてデプロイ先をAWS Amplify コンソールにしたがこれがすごい楽でよかった。また使いたい。

いっさいドキュメント系読まないでも、よくある既存のPasSのあの機能みたいなものね〜という感覚ですんなり運用できた。

Amplifyのバックエンド系の機能は全然使ってないので、そこは別の学習コストがあるのかもしれない。

Next

DBに数千件ある既存のコンテンツのページを生成する、各ページもSPAとして動的な機能を持つっていうのを作っていてv10.0が出て丁度良さそうだったNextを使った。

最初はFirebase Hostingへgenerateしたhtmlをアップロードしていったんだけど、ビルド時間の膨大化問題に悩まされてこれはSSRが必要になってくるなと思いVercelに移行した。

Vercelに移行してSSRを併用しはじめたらNext印象がまた変化しました。魅力をひとことで説明するのは難しいのだけど「いかに静的に確定する部分を増やして、それを CDN に置くか」 っていう文章を読んでそれや! としっくりきてしまった。

アプリケーションを書く視点で見ると、サバクラ2階層MVCの手続き型のコードだった部分が丸ごと宣言型のコードになるのでだいぶシンプルになったように感じました。

ちょうど2014年ぐらいにReactをはじめて使った時のDOM操作の複雑な手続きが一気になくなった感覚がウェブアプリケーション全体に拡張されているかのようです。

Gatsby

Nextのプロジェクトと似た構成で、数千コンテンツのページを生成するツールを作ってた。こっちはJAMstackという言葉に踊らさせていた時期だったのでNetlifyにデプロイしていた。

HerokuのPostgreSQLをパブリックで空けてデータを突っ込み、Netlifyのビルドサーバーから gatsby-source-pg 経由で吸う。という構成にしていた。

GraphQLの参照系のパワフルさはこのGatsbyの利用で体験できた。最終的にはもしろGraphQLフレンドリーになるようにポスグレのスキーマ側を調整したりしてた。

Gatsbyもnext buildと同じく最終的にはビルド時間の最適化との闘いになりそうだなと思った。Gatsby社のプラットフォームにインクリメンタルビルドの仕組みが提供されているけど。自力でそれを構成するには骨が折れそう(できる人は別のアプローチも取れそう)、という部分もVercelと似ている。

Laravel

はじめてまともに使ったけどRails並によくできていた。

強いて言うならPHP以外だとよかった。が、Symfony Components にもとづく物であろうのでPHPでなければ駄目だった。ありがとうPHP

PHP自体も久しぶりに書いたけどあんまり変わってなかった。ウェブに出回っている利用例の情報量は多いのだけど、Kubernetes環境+nginxの組合せで使っている人たちがあんまりいなかったのでインフラ構築に苦労した。

Viewは時代に逆行してSPA完全無視でbladeとlaravel-admin活用して作ってる。その方が目的が早く達成できそうだったのでそうした。

Helm

2019年はKubernetesとKustomizeに入門したので、今年はHelmfileでHelmパッケージとしてシステムをデプロイできるようにしてみた。2019年に導入しかけて、難しいなーと思い見送った時よりウェブ情報が充実していたのですんなり理解できて良かった。

Rails

3.x系のまま誰にもメンテされなかったアプリケーションを引き取り、サーバーの体調が悪化した時に救急手当をしばらくしていたんだけど「これ? Dr.コトーじゃん・・」と思ったのでSlackのアイコンをDr.コトーにした(とくに意味はない)

2020年まで精一杯生きて静かに息を引き取った。

Python asyncio

クローラー開発でPythonを使うことを決め(参考実装がPythonだった)、素朴なPythonブロッキング処理を書いていたんだけどそうすると長時間化していって動作環境のGitHub Actionsの従量課金に苦しめられることになったので並列実行を検討しはじめた。

今どきのPythonはasync/awaitでノンブロッキング処理書くぞと噂で聞いていて知っていたので挑戦してみた。Node.jsで慣れてるのですんなり使いこなすことはできたけど、どうしても外部ライブラリが非同期処理に対応できていなくて最終的にマルチプロセス+スレッドプール+asyncioで非同期と多重にしたら現実的なラインまで改善したので良しとした。

この過程でGitHub Actionsを月4000分以上使うなら1人チームのGitHub Enterpriseを申し込んだ方が安い。というニッチなtipsを得た。

自然言語処理機械学習

Pythonプログラミングのリハビリをして気をよくしたので、かねてからの懸案だった「数理的な知識をつける努力をせずに機械学習ツールを使いこなせるようになりたい」という実現に乗り出すことにした。

データ収集、前処理、トレーニング、テストなどの必要な手順の概念と、代表的なアルゴリズムチュートリアル。sklearn,gensim,kerasなどの組込み方を覚えたので。他人からアドバイスを受けて何か実践する。ぐらいのことができるようになったと思う(自分でウェブ検索しても出てこないような問題のアプローチを1から考える、というのは難しい)

Android

Jetpack Compose

ちいさなアプリを書いてだいたいどういう感じか把握した。

[Jetpack Compose] Glide経由でコンテンツ一覧を表示してタップされたらブラウザで開く

SwiftUI版も作ったと思うだけど、コードが残っていなかった。何も思うところがなかったのだろう

トルコ語アルファベット

トルコでのみアプリが落ちるという不具合があって、調べてみたらKotlinのenumJavaのStringに変換する処理で端末のロケールが使われトルコ語アルファベットになり正常に動作しないという現象が起きていた。

トルコ語アルファベット自体をこの時はじめて知ったので勉強になった。

Flutter Engine

Flutterは明かに今までのモバイルクロスプラットフォームとはアーキテクチャが違うので、その内部構造から理解したいなぁと思い。C++で実装されたエンジン部分のソースコードをチェックアウトして動かしたりしてみた。

Dartの部分を別の処理系に置き換えるには何が必要? という観点で読んでいた。1-2月に取り組んでてだいぶ間が空いてしまったのですっかり今は忘れてしまったけど。DartVMからSkia命令を叩く一連の流れを把握したと思う。

Mac DE Xamarin

Covid-19 Radar 関連でひととうりVisual StudioやらXamarin SDKを揃えたんだけどもう残ってない。

mhlwリポジトリは今見てみたらコミットが積まれていた。やるじゃんパーソル

https://github.com/cocoa-mhlw/cocoa/commits/master

Hiraganize

ウェブページを全部ひらがなに変換してしまうiPadのウェブブラウザ。漢字を読めない人とかに便利だと思う。

expo(React Native) + kuromoji.js でJSベースでテキスト変換処理をがんばっていたがモバイル端末で動かすには重過ぎたので途中からlibmecabとSwiftで書き直した。

ネイティブアプリの強みを生かしてSafari Actionにして、アプリ起動しなくても今見てるページをSafari上でそのまま変換するってところまで作った (https://twitter.com/laiso/status/1225438961738235905)

その後、漢字重要だよねということに気付いたので漢字は残しふりがなをふるブラウザに拡張してみた。

今は放置してる

プリント検索アプリ

私立小学校というアナログ推進団体と利害関係を持っているんだけど、彼等が毎日毎日 大量のミッションクリティカルなA4書類を配布してきて毎回紛失して困っていたので電子化して検索できるようにした。

これには ionic というフレームワークを使ってまずAngularベースのウェブアプリケーションを構築して

  1. 写真を取ってCloud Storageに上げる
  2. Cloud FunctionsでCloud Vision APIを叩いて文字情報を読み取る
  3. 文字情報をAlgoliaでインデックスして検索可能にする

という設計にした。ついでにオンブラウザでJPEG -> PDF変換機能も実装してみた。

ionicのいいところとしてはウェブアプリケーションとして作ってみて、その後モバイルネイティブAPIが必要になった段階でフォールバック的にWebViewでラップしたアプリに拡張できる点だと思う。

Angularが第一級市民だったので使ったけど今はクロスコンポーネント化されてReactやVue3向けのインターフェイスも提供されている。

note関連ツール

unnote

https://github.com/laiso/unnote

なんとなくこんな検索結果になったおもしろいなと思って作っていたやつ

ウェブサービスとして提供している人がいるのでそれを使うのがいいと思う。

noteのバックアップ/エクスポートファイルを無料で作成 - noteエクスポートβ

md2note

Electron下からnoteの非公開APIをNodeで叩いてパースしたMarkdownファイルで無理矢理記事を作成しちゃうやつ。

汎用的なツールにできないので自分だけ使っている状態……

findlist

https://gist.github.com/laiso/85b6e8666569a0ed6c86391f70e783cc

Twitterの非公開リストのパーミッションが壊れる障害があった時に、連番で探索するスクリプトを組んでた(いい話)

ここをこう持つ

歴代Xcodeのダウンロードサイズ

Xcodeがデカ過ぎることにむかついて作った

Qiitaからはてなブログにデータ移行するコード

Qiitaからはてなブログに記事を一括インポートする - Qiita

LINE BOT経由でYoutube動画をダウンロード

ペアブラウジング

なんとなくこういうツールが作れそうだと思ってやってみたけど用途はよく分からない

まとめ

  • 環境に恵まれているのもあってとにかくずーっとコード書いてた
  • 一方プロジェクトにはすぐ飽きてしまう。やりきる力が来年の鍵
  • 生活や健康には悪影響があると思うのでほどほどにできたらいいなと思う
  • あとこういうプログラミングが趣味な人は学習コストを低く見誤る傾向があると思った
  • この記事を自分で読んでて思ったけどこのまま行くと「町の変人発明家」ポジになってしまうのでは????

個人開発者とCovid-19 Radarプロジェクト

f:id:laiso:20200622042515j:plain
Endless road | During our roadtrip we turned off the highway… https://www.flickr.com/photos/98063470@N00/326044514

GitHubリポジトリ Covid19Radar に対して起ったことがかなり特殊な状況だったため、開発を追い掛けていた視線からレポートをします。

この記事の著者について

  • 代表作のない個人アプリ開発者(かなしい)
  • Covid-19 Radar Japan の人ではない
  • GAFAMやCode for Japan の人でもない

4/8 Covid-19 Radarを発見する

  • Covid-19 Radarとは、この時点ではシンガポールのTraceTogetherの日本版を目指した個人開発者 廣瀬一海さんのアプリのリポジトリ
  • 4月にContact Tracing技術について調べていたら偶然見つけた

  • 4/8時点で人が集っていて活発に開発されていた。Facebookで協力を呼び掛けていたらしい*1
  • sinsai.info みたいなムーブメントかな。ぐらいの認識だった

4/10 Apple/Google APIの開発が発表される

AppleとGoogle、 新型コロナウイルス感染症対策として、 濃厚接触の可能性を検出する技術で協力 - Apple (日本)

  • この時点では発表だけ。ベータリリースされたら使ってみるかぐらいの感想だった

4/15 コード・フォー・ジャパンもContact Tracingやってるらしい

5/5 APIを使えるアプリは各国1つのみ

  • ここで個人開発者オワタと思った

6/13 突然Covid-19 Radarが日本代表になっていたことを知る

  • コード・フォー・ジャパンのプロジェクトどうなったの? とかあれは個人プロジェクトでは? という疑問があった

6/15 開発が米マイクロソフトに移った??

  • 日経新聞の飛し記事が出る

  • 「「コード・フォー・ジャパン(CFJ)」にアプリ作成を任せる方針」が覆った理由が全然わからない
  • 5/11に投稿されたらしい 接触確認アプリ「まもりあいJapan」開発の経緯と今後について を読むと開発主体がCFJから厚生省に移ったことが要因らしい
  • Covid-19 Radarの作者はたしかに日本マイクロソフトの人だったから、会社を巻き込んだプロジェクトにしたのかなー、と思った
  • ちがうらしい

  • 今思うと厚生省で「大企業のほうが安心だ」と意思決定したおじいちゃんも「米マイクロソフトに発注したぞ!」と本当に認識していたのかもしれない

6/19 アプリがリリースされる

  • 開発者インタビューなどが世に出る
  • GitHubリポジトリだけを見てるとチームで開発してる感がなかったのでどんな開発体制なんだろうと疑問だったんだけど https://diamond.jp/articles/-/240905 でデザインやリレーションを担当している人が存在することを知る。あれ、開発は本当に1人でやっているの??

GitHubリポジトリを見始める

  • 日本初のApple/GoogleのContact Tracing APIを使ったアプリが出たぞということで技術的な関心駆動でコードを読み始める(Xamarinわからん過ぎるのでとりあえずチェックアウトだけして中身見てなかった)
  • この時点でいくつかのことが分かった

  • アプリサイドを廣瀬さん、サーバーサイドをdarkcrashさんが中心に開発している

  • runceelさんalbilagaさんなどがXamarin系の修正プルリクエストを送って手伝っている
  • norijiさんが前述の記事で出てくるデザイン作業で貢献している方
  • changeworldさんという人がもの凄い勢いでIssueやプルリクエストを捌いている

過去にAd-Hoc版ビルドのテスト版が配布されていたことを知る。デバイス登録上限に達するぐらい参加者がいたらしい(注: このアプリは5月末にリリースされたiOS 13.5にアップデートした端末か事前のiOSベータ版配布を適応しないと動作確認すらできない)

iPhone のテスターの方へ

この時の配布ツールApp Center用のライブラリが本番バイナリに入ってデータ送信していたらしく、mala(呼び捨て)が発見して取り除かれた。

Getting Started

  • アプリをビルドしてみて修正しようと思ったけど全然やり方がわからない
  • 開発環境構成ドキュメントはまだ存在しなく、なんとなくGitHubの外にチャットなどがあって、そこで開発が進んでいるのかな〜と思いはじめる
  • ふて寝する

6/20 なんとかなったぜ

  • いろんな方法を探っていたら結果うまくいってコードを編集して動作確認できるようになった
  • iOSのビルド設定が廣瀬さん固定になっていたので誰でもビルドできるような変更をプルリクエストしてみる

iOS Bundle SigningOption to 'Automatic' by laiso · Pull Request #471

  • ここでなんとApple/Google APIのデバッグは廣瀬さんの環境でのみしかできないことが判明(たぶん)。超SPOFじゃん・・と震える

(カルチャーフィットしなさそうなので厚生労働省には入らないことにしました)

結局Exposure Notification APIをモック化する仕組みが既に用意されていて(有能)それを使うことにした。

About messages when sharing the app on line

「LINEでシェアすると表示がおかしい」というIssueを見たので検証して直してみる

change appStoreUrl to valid URL by laiso · Pull Request #474

「俺の修正のおかげで直ったわ〜」と達成感を味わっていたらmala(呼び捨て)が出てきてLINEのサーバーで修正されてしまう(チートでは??)

Line breaks not works in a mail app

  • iOSのメールアプリでお問い合わせするとエスケープ済みの改行コードが入るらしい
  • メール改行テキストの修正のパッチを提案する(普通にググってうまくいくか試しただけ)

Line breaks not works in a mail app · Issue #479

この問題翻訳ファイルの元の問題を修正する必要あるんだけど、翻訳ツールがWindowsでしか動かないことを知る(Macの人居なかったのか・・!)

App crashes

  • App crashes · Issue #450 が再現しやすいので修正を試みる
  • ソースコードをいじって検証してみたけど前述のとうりSPOFで世界がヤバいので僕の環境ではデバッグすらできなく、直ったのかどうかわからんけどとりあえずプルリクエストにしてみる

fix a crash on StopExposureNotification() by laiso · Pull Request #484

  • このあたりで前述のApp crashes Issueのやりとりを見た人たちが「公共サービスをこのような認識で開発されているのはいかがなものか?」と騒いでるらしいことを知人に教えてもらう
  • 僕は彼等に対して距離を置くことで軽蔑やNOを示したいので、透明なおじさんは見えませんよみたいな感覚で見なかったことにした(不具合の事実の報告は重要なのでチェックだけする)
  • とはいえ当事者の人たちはダメージを受けていたらしく「SPOF〜〜」と見守っていた

Xamarinについて

  • 知人iOSエンジニアなどが「コントリビュートしたいけどな〜技術スタックがな〜Xamarinがな〜」と嘆いているのを見かける
  • たしかにXamarin+Mac+iOSは少数派ゆえ茨の道っぽい……

これまでの流れで思ったこと

  • 国の意向で日本代表の感染症対策アプリ開発が個人開発アプリに丸投げされたっぽい(そんなことあるの??)
  • 関係者の発言(公開された情報)によるとリリース日が事前に決まっていたらしい
  • Covid19 RadarはMPL v2.0に基くソフトウェアだけどプロジェクトは一般的に知られる「GitHubで開発が行なわれているOSSプロジェクト」ではなく個人開発者とその周辺のボランティアだった
  • GitHubリポジトリがとりあえずある(元が個人開発アプリなので)。というだけの状態
  • 今後このプロジェクトは厚生省及び開発委託先によって管理されるらしい
  • 「大企業のほうが安心だ」と意思決定する人がこのGitHubリポジトリを維持するものなのか
  • userUuid および secret の廃止 · Issue #514 のような提議をできる場はあるのか
  • 現在改善が進んでいるMPLのコードベースがどうなるのか
  • 人類がはじめて直面する事態に対してアプリケーションのプラットフォームが特例的に出した仕組みを使って、1つの国で1つのアプリを1人が作る*2異常な状況なので何が正解なのか誰も分からない……
  • 廣瀬さん関係者の方々お疲れさまでした

06/24/2020:この記事を投稿した後にわかったこととか

高木浩光さんの発言の反応について

  • App crashesのIssueは高木さんの投稿(RT?)から話題になっていたのだと後から知った
  • インストールしてセットアップ完了をさせる人数が重要で、それが生死に関わる問題のアプリケーションなので、感情や責任に対する議論に終始して「たいした問題じゃない」という言葉で弁護するのは賛同できないなと思った
  • 自分なら「クラッシュの回避はとても重要である」という意見になる。最新の発言しか読んでないけど高木さんも似たような指摘をしていた
  • そもそもフレームワークレベルで呼んだら捕捉できる例外を返して欲しい
  • あとあれは正常系だと思う(用語の話)
  • そして他に指摘されていた不具合はリリース当日の既にmasterブランチで修正されているものもあり(日付表示の問題など)、v1.0.0はその時点のスナップショットが世に出された印象だった(言いたいこととしてはフィードバックの修正が進んでいる)

HER-SYS(新型コロナウイルス感染者等情報把握・管理支援システム)

  • 接触確認アプリはHER-SYSとの連携を予定している(陽性者登録のシステムなど)
  • 接触確認アプリの開発はHER-SYS開発の契約の一部である
  • HER-SYSの発注先がパーソルプロセス&テクノロジーである

これが知りたかった情報にもっとも近かった(楠正憲さんは内閣官房情報化統括責任者補佐官でもある)

Covid-19 Radarへ

  • 接触確認アプリはパーソルプロセス&テクノロジー以下へ委託される
  • HER-SYSの基盤はMicrosoft Azureだった *3
  • Covid-19 Radarも4月時点でAzure+.NET+CosmosDBを利用してAPIが実装されていた
  • まもりあい JAPANのAPIサーバはAWS+Node.js+Firestore*4 で実装されていた
  • Serverless FrameworkのプロバイダをAWSからAzureに移行する。これは互換性の面では可能だがFirestoreは維持するとなるとマルチクラウド構成になってしまう
  • まもりあい JAPAN以外にもContact Tracingアプリ開発プロジェクトはあったし、スクラッチから開発するという選択肢もあった
  • パーソルプロセス&テクノロジーが「アプリ開発」の部分の再委託先を決めないといけない。それには時間がかかる
  • リスクを減らすには技術的な相性を加味する必要があるのではないか
  • (それ以外にも採用してるライセンスや開発担当者の身元など様々な要素があると思うけど……)
  • そして再委託先の決定+開発開始のリードタイムを埋めるべく作業を任されたのがCovid-19 Radar Japan?(ここまでは勝手な推測)
  • なので「無報酬で依頼を受けるべきでなかった」は一面的な見方だと思う

こういうこと?

  • 政府にとって第1イシューは「早期にリリースする」ことだった
  • 早期にリリースするために各自が出した答えが現在の状況と体制
  • 早期リリースは実現された

今後

  • 完全にメンバーが変わるのなら、バックエンドはまだしもアプリ部分はネイティブ実装(Swift/Kotlin)に置き換えられていく可能性は充分あるなと思った(そちらのが信頼のおけるサービスになるのなら)
  • GitHubリポジトリの運用も維持される可能性はありそう(他国事例や東京都サイトに続けて政府のIT施策アピールにもなる)。ただ委託先との契約上どうなっているのか次第なのかもしれない

お詫び

  • 文章を読み返してみたら厚労省やXamarinに対して貶めるような表現が多くバランスを欠いていたなと反省しました。謹んでお詫び申し上げます
  • 記事のコメントを読んでいたら著者について、実態よりCovid-19 Radar Japanのインサイダーっぽく受け取られているなと感じました。とくに開発に積極参加しているわけではなく、メンバーと交流があるわけではないのであくまでも部外者の視点であることを強調します

どんなプログラミング技術の学習に投資すべきか考える時にやってること

はじめに

とにかく次の10年を生き残りたい - 怠惰を求めて勤勉に行き着く を読んでいて、かー自分もここ10年ぐらい同じような内省をし続けていたではないかと深く共感したので、その過程で身に付けたやり方を書くことにしました。

目的

プログラミング技術を学習する目的を決めます。僕の場合は

  1. ソフトウェアエンジニアとしての市場価値を上げる(他のエンジニアから尊敬されたい)とか
  2. 素晴しいアプリケーションを作れるようになりたい(エンジニア以外からも尊敬されたい)

というものがあります。人によってはこれが「GAFAMNに入り渡米してメジャーデビュー」「OSSで一発当てる」「とにかくお金を稼いでアーリーリタイヤ」など様々かと思うので各自考えてください。

テーマ

目的が決ったら次は学習するテーマを決めます。僕の場合は「○○エンジニア」と呼称されている領域ごとに「この分野で先進的なネタは何だろう」というのをリサーチします。テックニュースやGitHubトレンド、口コミ、求人情報などから探します。

それを視覚化します(箇条書きでもなんでもいいです)、僕はMarkdownで下書きして、マインドマップにしました。

f:id:laiso:20200110000247p:plain

大テーマである「○○エンジニア」の部分は人によって違うはずです。

僕は素晴しいアプリケーションを作れるようになりたいので適切な技術を選定できるように分野を広めに保っているのだと想います。

たとえば「Netflixで英語学習するツール」を実現したい時、ブラウザ上で動くNetflixのサイトにデータと機能を組み込んで連携させるのが一番良い方法だな、と思ついたとします(実際にそういうツールが存在します)。

これにはウェブアプリーケションが動作する仕組みとブラウザ拡張についてのフロントエンド開発の知識が必要になってきます。

ところがiOSアプリの開発スキルだけあってもこのアイデアを実現できたりはしませんし、そもそも方法を知らないと思い付かないかもしれません。逆にネイティブアプリでしか実現できないことが手持ちの知識の中にないとそれによって作るものが制限されてしまうこともあるかと思います。

しかしなるべくなら枝は少ない方が好ましいと考えています。枝を減らすコツは「別の技術で同じものを作れる」状況をなくすことです。例としてはRubyでもPythonでもWebサーバーは作れますが、SwiftでないとiOSアプリは作れないので、増やすべきはSwiftです。

ここに出てくる各固有名詞の製品自体を作りたい人はもっとCSの基礎技術を拾うべきでしょうし、数学、デザインや語学、データサイエンス関連も含める人もいるかもしれません。

先進的なネタの基準

ソフトウェアエンジニアとしての市場価値を上げるという目的があるので、「先進的なネタ」というのは今現在の業界で起っていることで次の時間軸にあるものを選びます。

たとえばRuby on Railsを使ってWebサービスを作っている企業があり「今後はマイクロサービス化しGo言語に移行したい」という動きがあるとします。それを見据えた時に学習のネタとなるのはマイクロサービスだったりGo言語だったります。無事これらの技術を習得する頃に、世の中に「マイクロサービス化してGo言語に移行したプロジェクト」の総数が増えていれば、需要も上っていてエンジニアとしての市場価値も増すので投資のリターンを得てやったねというわけです。

テーマに優先順位をつける方法

大テーマである「○○エンジニア」の部分は数が多ぎ過ぎなので、どれを優先させるべきかというのを考えます。

ここでの基準は

  1. 普段の業務でやらない分野
  2. 業務でやる分野だが自分の担当ではない
  3. 普段の業務でやっていること

の順で優先付けします。

これは「適切な技術を選定できるように分野を広めに保つ」という考えからまんべんなくステ振りできる方法として考えました。

「それぞれの分野の理解が浅くなってしまうのでは?」という器用貧乏症候群については、こう考えます

  • 普段の業務では深い理解を求め
  • 学習フェースでは広い理解を探索する

こうすることで効率よくハードスキルのT字の延せるのだと考えています。

いかがでしたか?

そもそも私はどの技術的なトピックが将来発展するのかという予測は不可能だと思っています。現実の世界では常に変化し続ける環境の要因が複雑に絡みあっているからです。

なのでビジョンを持って、常に変化し続ける環境に適応する(あとは運)。というやり方がしょうにあっているのだとソフトウェアエンジニア人生で学びました。

みなさんも独自の方法論を教えてください

Cloud FunctionsをGoで書く。またはFirebaseのためのマイクロサービスアーキテクチャ

f:id:laiso:20191216155312j:plain

Firebase Advent Calendar 2019 の17日目です。16日目はKesin11さんの「Firebase Emulator Suiteをフル活用してTDDで開発しよう」でした。

はじめに

FirebaseプロジェクトでCloud Firestoreを利用する時は通常Node.jsによるCloud Functionsでトリガーとなる処理を記述します。その他には関連するAPIサーバー、WebアプリのフロントエンドのSSR、バックエンドの非同期処理など、多くの場面でCloud Functionsが活用されています。

この開発→デプロイサイクルをお手軽に行ってくれるのがfirebase-toolsというnpmモジュールです。JavaScriptでFunctionを実装し、firebase deployコマンドを実行するだけでFirebaseプロジェクト用のCloud Functionsが自動で登録されます。

解決したい問題

firebase-toolsは便利に使っていたのですが、私たちのプロジェクトでは日々いくつものCloud Functionsのモジュールが増えてゆく過程で以下の問題が発生しました。

  1. デプロイ対象となるソースコードの量が増え、デプロイ完了までの時間がかかるようになった(--onlyオプションを付けても遅い)
  2. SSRを含むフロントエンド向けビルド、APIサーバーとして機能するFunction、Firebase固有の処理、などの依存が単一の package.json で管理されていてモジュール更新の影響が大きかった
  3. 複数のFirebaseプロジェクトでプライベートリポジトリにある自作モジュールを共有したかった
  4. Cloud Tasksや複数Functionへの分散した水平スケールでは実現できない、リアルタイムの大量データ処理を垂直方向にスケーリングしたかった

そこで私たちはまずはじめにfirebase-toolsの仕組みやCloud Functionsがどのように動作しているのかを理解して、最終的にfirebase-toolsで構成していたFunctionのビルドを徐々にマイクロなモジュールに分割していくことにしました。

Cloud Functions for Firebaseの仕組み

Cloud Functions for Firebase*1

Cloud Functionsは - リソース - イベント - 関数名

という組合せでFunctionを管理しています。特定のリソースAに発生したイベントαにアップロードしてビルド済みのプログラムから指定の関数を実行します。

/**
 * [Google Cloud Firestore トリガー](https://cloud.google.com/functions/docs/calling/cloud-firestore?hl=ja)
 */

const Firestore = require('@google-cloud/firestore');

const firestore = new Firestore({
  projectId: process.env.GCP_PROJECT,
});

exports.makeUpperCaseOne = (data, context) => {
  const {resource} = context;
  const affectedDoc = firestore.doc(resource.split('/documents/')[1]);

  const curValue = data.value.fields.original.stringValue;
  const newValue = curValue.toUpperCase();
  console.log(`Replacing value: ${curValue} --> ${newValue}`);

  return affectedDoc.set({
    original: newValue,
  });
};

という関数を持つ index.js のみのコードを用意して

# package.json を生成する
npm init && npm i @google-cloud/firestore

gcloud functions deploy makeUpperCaseOne --runtime nodejs8 \
  --trigger-event providers/cloud.firestore/eventTypes/document.create \
  --trigger-resource "projects/$GCP_PROJECT_ID/databases/(default)/documents/messages/{docId}"

というコマンドでデプロイすると(gcloudコマンドは別途セットアップします)

projects/$GCP_PROJECT_ID/databases/(default)/documents/messages/{docId} のリソースに対して providers/cloud.firestore/eventTypes/document.create というイベントが発生した時に 関数 makeUppercaseOne() をトリガーする。

というCloud Functionsのリソースが登録されます*2

試しにコンソールからFirestoreのドキュメントを作成してみます

実行されドキュメントが更新されました。

同等の処理を行うFunctionをCloud Functions for Firebaseで実装してみることにします。

以下のようにfirebase-functionsモジュールを使ってFunctionを定義すると

const functions = require('firebase-functions');

exports.makeUppercase = functions.firestore.document('/messages/{documentId}')
    .onCreate((snap, context) => {
      const original = snap.data().original;
      console.log('Uppercasing', context.params.documentId, original);
      const uppercase = original.toUpperCase();
      return snap.ref.set({uppercase}, {merge: true});
    });

firebase deploy --only functions の内部で先程のgcloudコマンド同様のリソースができるわけです。

gcloud functions deploy makeUpperCase --runtime nodejs8 \
  --trigger-event providers/cloud.firestore/eventTypes/document.create \
  --trigger-resource "projects/$GCP_PROJECT_ID/databases/(default)/documents/messages/{docId}"

元のFunction用のコードからソースコードをそのままに1つの関数だけを抽出して、フォルダ構成としてはこのような独立したnpmパッケージとして管理できるようになります

functions/makeUppercase/
├── index.js
├── node_modules/
├── package-lock.json
└── package.json

Goランタイムへの置き換え

Cloud Functionsへ直接デプロイすることができたので、次にFunctionをGo言語で実装してCPUメモリあたりの実行速度の高速化を図ります。ボトルネックがI/Oではない典型的なデータ処理のFunctionはこれだけでスループットが向上が期待できます。

var projectID = os.Getenv("GCLOUD_PROJECT")
var client *firestore.Client

func init() {
    conf := &firebase.Config{ProjectID: projectID}

    ctx := context.Background()

    app, err := firebase.NewApp(ctx, conf)
    if err != nil {
        log.Fatalf("firebase.NewApp: %v", err)
    }

    client, err = app.Firestore(ctx)
    if err != nil {
        log.Fatalf("app.Firestore: %v", err)
    }
}

更新した値をFirestoreに書き込み更新する場合、Firestore Clientの準備をします。

init() はランタイムにより指定されているインスタンス毎の初期化処理で、Node.js版でいうグローバル変数にセットアップ済みの値を入れておく方法を似ています。

type FirestoreEvent struct {
    OldValue   FirestoreValue `json:"oldValue"`
    Value      FirestoreValue `json:"value"`
    UpdateMask struct {
        FieldPaths []string `json:"fieldPaths"`
    } `json:"updateMask"`
}

type FirestoreValue struct {
    CreateTime time.Time `json:"createTime"`
    Fields     Message    `json:"fields"`
    Name       string    `json:"name"`
    UpdateTime time.Time `json:"updateTime"`
}

type Message struct {
    Original struct {
        StringValue string `json:"stringValue"`
    } `json:"original"`
}

func MakeUpperCaseGo(ctx context.Context, e FirestoreEvent) error {
    fullPath := strings.Split(e.Value.Name, "/documents/")[1]
    pathParts := strings.Split(fullPath, "/")
    collection := pathParts[0]
    doc := strings.Join(pathParts[1:], "/")

    curValue := e.Value.Fields.Original.StringValue
    log.Printf("Uppercasing: %q", curValue)

    newValue := strings.ToUpper(curValue)
    data := map[string]string{"original": newValue}
    _, err := client.Collection(collection).Doc(doc).Set(ctx, data)
    if err != nil {
        return fmt.Errorf("Set: %v", err)
    }

    return nil
}

Function本体の処理です。Go言語の型システムの仕様上Firestore内の値のパース処理を、Node.js版でいう firebase-functions にあたるユーティリティがないためドキュメントパスの解決などを自分で実装する必要があります。

デプロイします。

gcloud functions deploy MakeUpperCaseGo --runtime go111 \
  --trigger-event providers/cloud.firestore/eventTypes/document.write \
  --trigger-resource "projects/$GCP_PROJECT_ID/databases/(default)/documents/messages/{documentId}"
func TestFunc(t *testing.T) {
    var projectID = os.Getenv("GCLOUD_PROJECT")
    jsonStr := `{
      "original": {"stringValue": "hello"}
  }`
    var message Message
    var err error
    err = json.Unmarshal([]byte(jsonStr), &message)
    if err != nil {
        log.Fatal(err)
    }
 
    value := FirestoreValue{
        Name:   "projects/" + projectID + "/databases/(default)/documents/messages/1",
        Fields: message,
    }

    result := MakeUpperCaseGo(context.Background(), FirestoreEvent{Value: value})
    if result != "HELLO" {
        t.Error()
    }
}

動作確認をGoのユニットテストとして記述できます。

写真のリサイズFunctionをGoで実装してみる

Functionのイメージ*3

ユーザーがアプリケーションから写真を登録したらシステムで必要なサイズの画像を自動で生成するようなFunctionをGoに置き換えてみます。

  1. Document更新トリガで関数を実行
  2. パースしてきたURLから画像をダウンロードしてくる
  3. リサイズを実行
  4. 画像をCloud Storageに保存

という一連の流れです

conf := &firebase.Config{ProjectID: projectID}
opt := option.WithCredentialsJSON([]byte(os.Getenv("SERVICE_ACCOUNT_JSON")))
app, err := firebase.NewApp(ctx, conf, opt)

Firebase Admin SDKの初期化時にクレデンシャルを含むJSONファイルのパスを指定するのではなく、環境変数から読み込むようにします(confも同じ形式にできるのですが、秘密情報を含まないためコードで指定しています)。

gcloud functions deploy Resizing --set-env-vars SERVICE_ACCOUNT_JSON=$(cat secretkey.json)
// var fbStorage *storage.Client
fbStorage, err = app.Storage(ctx)
if err != nil {
    log.Fatalf("app.Firestore: %v", err)
}

Cloud Storageのクライアントも init() で初期化しておきます。

type User struct {
    ProfileImageUrl struct {
        StringValue string `json:"stringValue"`
    } `json:"profile_image_url"`
}

ドキュメントの構造体をこのように定義しました。profile_image_url というキーに画像がアップロードされたURLが保存されます。

func Resizing(ctx context.Context, e FirestoreEvent) error {
    url := e.Value.Fields.ProfileImageUrl.StringValue
    cli := http.Client{}
    resp, err := cli.Get(url)
    if err != nil {
        log.Fatal(err)
    }
    src, _, err:= image.Decode(resp.Body)
    if err != nil {
        log.Fatal(err)
    }

    size := 320
    img := imaging.Resize(src, size, 0, imaging.Lanczos)
    encoded := &bytes.Buffer{}
    jpeg.Encode(encoded, &*img, nil)

    bucket, err := fbStorage.Bucket(bucketName)
    if err != nil {
        log.Fatal(err)
    }
    path := fmt.Sprintf("resized_images/%dx.jpg", size)
    obj:= bucket.Object(path)
    writer := obj.NewWriter(ctx)
    io.Copy(writer, encoded)
    defer writer.Close()

    return nil
}

Function本体です。imaging(https://github.com/disintegration/imaging )を使い 320x にリサイズしてアップロードします(別途Storageへの追加をトリガーにしてUserドキュメントにパスをセットします)

検証してみたところNode.js版での公式ドキュメントでの解説にあるようなImageMagickのconvertコマンドを外部で実行するような方法*4と比べて、ファイルに書き出しがない部分がうまく効いて大量のリサイズが一度の実行でできそうでした(リサイズ処理の品質に差がでるかもしれないので別途評価が必要です)。

デプロイ速度やFunction起動速度について

Cloud Functionsのデプロイはローカルにあるソースコードを対象として、依存モジュールが記述されている package.jsongo.mod から自動的にクラウド環境でビルドが走るようです。

firebase-tools を使ったデプロイを行っていた時は、functions/ にあるすべてのファイルが対象になり1つのFunctionのリソースへアップロードされていました(GCPコンソールからアップロード済みファイルが取得できるので確認できます)。

またFirebaseユーザーの間でFunctionの高速化テクニックとして環境変数から探索して、Node.jsの依存モジュールの動的読み込み制御する方法が知られています。*5

これらの方法と比べてアーキテクチャ的に改善する可能性はあるなと思いつつも、まだ安定性やアーキテクチャの評価中なので詳しくは比較できていない状態です。

ビルド+デプロイツールの改善

firebase-toolsを使わくなることで、複数のFunctionの依存を管理するための方法や開発やデプロイを楽にする方法を別途用意しなければいけません。

Functionを分割して複数の依存を管理するパッケージができたことで、ソースコードはmonorepoの状態になります。そのためlerna(https://lerna.js.org/ )やBazel(https://www.bazel.build/ )のようなツールが機能する環境になるかもしれません。

ただfirebase-toolsを使った環境は並行して維持できるので、段階的に移行して検討するつもりです。

GCPサービスを使ったさらなるFirebaseアプリケーションの拡張

※Firebase & Google Cloud Platform*6

Cloud FunctionsはGoランタイムの他にはPythonランタイムもあり、そちらでも同様にトリガーFunctionが書けるので何か活用法があるかもしれません。

また各FirebaseやGCPサービスには対応したREST APIが公開されていることも多く、SDK対応言語以外でもクライアントを自作して拡張することができます。

もちろんCloud Functionsですべてを行うことにこだわらずとも、HTTPリクエストをトリガーとしたAPIサーバーのFunctionや、SSRを実行して動的HTMLを返す常に待ち受けしているFunctionは、同時処理数に優れたCloud Runに移すことができそうです。

またCloud Run同士で連携して(RESTやunary gRPC)サーバー間通信でシステムを拡張の目的でも利用できそうです。

他にはCloud SchedulerとCloud Tasks、Cloud Dataflowを使ったバッチ処理。Firebase AnalyticsとCloud FirestoreをBigQueryにエクスポートして分析し、その結果をシステムに反映させるなどを私たちも既に行っています。

まとめ

このようにFirebaseはGCPの既存の仕組みを使い易くラップしたものなので、必要に応じてGCPのリソースを活用して最適化することができます。

Firebase Advent Calendar 2019 - Qiita

明日の担当はVexus2さんです。お楽しみに。

参考文献