見出し画像

「北欧、暮らしの道具店」初のアプリができるまで(API開発編)

こんにちは。気がついたら入社から2年が経っていたエンジニアの廣瀬です。先日、入社以来の個人的な悲願でもあった「北欧、暮らしの道具店」のアプリ(iOS)をついにリリースしました!わーい! 🎉🎉🎉

アプリ開発に関してインタビューしてもらった記事も会社のウェブサイトで公開されています(記事内の「わからなくてもいい廣瀬さん」の方が僕です)。

インタビューでも色々と話したのですが、ここでは API の開発に携わったエンジニアとしての視点で、アプリの開発スタートからリリースまでを振り返ってみたいと思います。

3月、ハノイ(ベトナム)に行く。

スクリーンショット 2019-12-16 14.20.20

今回、UX / UI デザインと API の開発は社内でやりつつ、アプリ(iOS / Swift)の実装は、ベトナムに拠点を置く株式会社Sun Astarisk(Sun* Inc.)という会社にお願いすることになりました。

僕らと Sun* がどのように開発を進めてきたかは、社外取締役である倉貫さんが詳しくまとめてくれています。(途中から有料になっていますが、開発に関する部分は無料で読めます。僕は課金していないので続きに何が書いてあるかは知りません 🤔)

僕はアプリにデータを渡す API の開発を担当するエンジニアとして、ハノイでのキックオフミーティングに同行したのですが、言葉の壁もあり、ミーティング自体は正直不安を感じるところもありました。

それでも、ミーティングの後に行った夜の食事会はなかなか盛り上がり、最後の方には一緒に「メイクグッドプロダクト!」とか言っていたので、何とかなるかなーという気分で日本に帰ったことを覚えています。

そして、この時点でリリース目標は今年の「秋」ということに決まりました(そして最長で何月までを「秋」と呼んでいいのかを調べた結果、11月まではいけるらしいぞということが分かりました)。

API の設計に着手

村田「最初にやるべきことをポストイットに書いて張り出した時は、絶望の淵に立ちました。」
廣瀬「マジ?は僕も思いました(笑)。」
https://kurashicom.jp/6618 より)

帰国後、早速 API の設計に取り掛かったのですが、最初から大きな課題に直面します。そしてこの半年はこの課題との闘いそのものでもありました。

現在、「北欧、暮らしの道具店」は、買い物だけではなく、様々な記事コンテンツやポッドキャスト、短編ドラマまで提供しています。「読みもの」と呼んでいる記事の中にも、商品レビューからコラム、レシピ、お坊さんへの人生相談まで様々です。いま我々がアプリを作るならば、そんな「ただのECではない何か」を体現するものにしたい。

しかしながらその姿は、年月を重ねるなかで漸進的に出来上がったものです。技術的には普通の EC としてつくられたアプリケーション(Laravel)と WordPress を組み合わせたものに過ぎず、運用を最大限に工夫することで「そう見せている」だけだと言えます。つまり、多種多様なコンテンツを RESTful でいうところの「リソース」として捉えようとしても、対応するデータ構造はどこにも存在しないわけです。

着手早々、色々悩んだ挙句、まず以下の方針を立てました。

① API は今の「北欧、暮らしの道具店」を忠実に表現するものとする。アプリ(API / iOS)に既存のデータ構造に由来する制約や例外的なロジックを持ち込まない。
② 既存のデータ構造はできるだけ変更しない。既存のコード、UI (フロント・管理画面)、社内のメンバーが行なっている制作・運用にできるだけ変更を強いない。

アプリが社外の開発であること、またウェブとは違ってアップデートがユーザーに委ねられるということを踏まえると、ウェブ側以上に負債の返済が難しい。よって「なんかわかんないけど DB の定義上そうせざるをえない」みたいなものはできるだけ排除して、アプリの API として「素直」な設計・仕様にするべきだと考えました。

とはいえ、そんな API を実現するために「素直」ではないデータ構造(スキーマ等)の方に手を入れるとなると、それに依存する既存のアプリケーションも改修せざるを得なくなり、それだけでサービスの作り直しに相当するプロジェクトになってしまいます。違う!それもいつかやるべきだと思っているけれど!いまはアプリを作りたいんだ!

腐敗防止層パターン

スクリーンショット 2019-12-16 14.17.35

この相反する2つの方針を両立するために採った戦略が、「腐敗防止層パターン」です。

① DB (MySQL) や WordPress のデータを、理想的な構造にマッピング・加工した上で、Elasticsearch に格納する。
② API の Controller が返すリソースは原則 Elasticsearch のデータをマスタとする。DB や WordPress に直接問い合わせない。

こうすることで、いくつかのメリットが得られます。まず、API そのもの(アプリケーションレイヤ)の実装はそのために構築されたデータを前提とするので非常にシンプルになります。また、すべてが Elasticsearch に格納されているので、リストのソート順から全文検索まで、クエリ(Query DSL)を駆使すれば容易に実現できます。

そしてアプリからのアクセスは基本的に Elasticsearch に対するリクエストになるので、ユーザー増による負荷への対応も Elasticsearch のスケーリングに問題を集約できます。複雑に絡み合う既存の DB で、エンドポイント毎に発行される SQL を状況に応じて都度チューニングするといった運用は、現在の我々の体制ではとてもじゃないけど無理だと思いました。

(実際は、在庫などの一部のクリティカルな情報は DB に問い合わせていますが、あくまで部分的な範囲に留めています。)

この方式が採用できるのは「北欧、暮らしの道具店」が CGM(ユーザーがコンテンツを生成するタイプのサービス)ではないため、DB と Elasticsearch の同期における遅延を比較的許容できる性質のサービスだからこそでもあります(言うなればそこは社内都合)。なので、まずはリリース速度を優先して、同期に時間はかかるけれど全データを洗い替えする実装でスタートする、という判断もできました。

走りながら考える

スクリーンショット 2019-12-16 15.00.41

API の実装において、どこで何をするべきか、想定される問題はどこで対処するかの大枠は見通しが立ちました。あとは要(かなめ)であるデータの変換です。前述したとおり、API 開発の9割はこれだったと言っても過言ではない。あとの1割はひたすら YAML を書いていた気がします(アプリ開発チームとは OpenAPI / Swagger で仕様を共有していました)。

既存のデータ構造から別のデータ構造をつくるという話なので、三角のものを四角にするようなものです(映画の『アポロ13』でそんなシーンがあった気がする)。もう、そこは、泥臭く、既存のスキーマやアプリケーションコード、果てはビュー(HTML)まで読み解いて作っていきました。何度も「どうしてアプリ作るとか言い出したんだろう」とか「人類には早すぎたのではないか」とか思うこともありました。

実装が困難だったというよりも、大変だったのは「ソフトウェア設計やオブジェクト指向における保守性の高い凝集・結合という軸」と「保守性のためにこそ既存のデータ構造が抱えている課題を無闇に分散させてはいけないという軸」との間で、アプリ開発の進捗に追い掛けられながら、日々、大小様々な判断を下していかなければならなかったことです。

(アプリ開発チームが優秀だったこともあり、さながらマンガの原作とアニメのように「アニメに追いつかれない = 彼らの手を止めない」という締め切りに追われ続けました。)

例えば、ウェブとアプリ(API)は、データソースを異にしている以上、両者は本質的には「似て非なる世界」です。一見、共通化できそうなロジックであっても、「本当に共通化して良いのか?本当に同じ意味なのか?」ということを常に問い続ける必要があります。実はウェブとアプリとで意図が違うということがあれば、あえて重複させる方が正しい(保守性が高い)ということだってあります。

そういった判断をするためには、アプリでやりたいことやリリース後にやっていきたいこと、そして UI の意図(『誰のためのデザイン?』で言うところのデザイナーの概念モデル)に対する理解が必要です。しかしながら、UI の設計も開発と並行してやっていたので「その時点では判断できない」ことは往々にしてありました。究極的には「リリースしてみないと分からない」なんてこともあります。一体どうすればいいんだ。

それでも何とかするのが自分の役目だと思って、結果的には負債とも言える無理な実装もいくつか残しながらも、最初に立てた方針(アプリに既存の都合を持ち込まない、かつ既存のデータ・コードに手を入れない)の正しさを信じて最後まで進めたという感じです。本当にこれで良かったのかなと思うところも多々ありますが、そこは今後の評価に委ねたいと思います。

我を斬り 刃鍛えて幾星霜
子に恨まれんとも 孫の世の為
(『るろうに剣心』より)

……とは言っても、運用していくのも自分たちなので、いつか僕自身が過去の自分を罵る日が来るのかもしれません。

ネイティブと WebView

せっかくネイティブアプリをつくるのだから、ウェブでは実現が難しかったネイティブ実装ならではの機能や操作感をお客様に提供したい。けれども商品ページや記事といった我々の資産のほとんどはウェブ(HTML)にあります。

それらを今回、すべてネイティブに置き換えるとなると、あまりにもスコープが広がり過ぎてしまいます。そこで、バージョン 1.0 として出すにあたって、以下のような作りになっています。

・リソース一覧の表示はネイティブ実装(既存の HTML は流用しない)
・リソース単体の表示は WebView (既存の HTML をアプリ向けに調整)

・リソースの取得はすべて API を経由する。WebView の URL も必ず API のレスポンスを用いる。

どういうことかというと、例えばリソース単体の WebView で表示する URL を、リソースの ID 等からアプリ側で組み立てるようにしてしまうと、後方互換性を確保する限り URL を変更することができなくなってしまいます。これは「アプリに既存の都合を持ち込まない」という方針にも反します(既存の URL がまさに色々な都合の結果そのものとも言える状態なので)。

{
  "id": 42
  // ⇒ https://example.com?xxx_id=42
  // → このロジックをアプリ側に持たせると URL を簡単には変更できなくなる
}

なので、こうしておきます。

{
  "id": 42,
  "html_url": "https://example.com?xxx_id=42"
  // → この URL はこちらのタイミングで変更できる
}

たとえ将来的に表示をネイティブに置き換えることになっても、アプリ側の「API にリクエスト → リソースの取得 → それを表示」という基本的な処理は変わりません。リソースに対応する表示部分の実装を変更するだけで済みます。また、このフィールドを残しておけば、ネイティブに置き換えられていない過去のバージョンに対する後方互換性も確保できます。

スクリーンショット 2019-12-16 14.17.57

また、「API からリソースを取得する」という原則があるので、WebView でありながらネイティブ実装が混在しているような画面(現在のバージョンであれば、商品ページのカート追加ボタンはネイティブ実装です)も、ネイティブ部分を構築するために必要なデータは API のレスポンスに含めておくことで実現できます。言い換えれば、HTML の URL はリソースの1つの属性に過ぎないということです。

画面遷移

厄介なのは HTML 内に含まれたリンクです。HTML はウェブと共有しているため、ウェブ上では今まで通りの遷移を維持しつつ、アプリ上では遷移先をネイティブ / WebView にマッピングしなければなりません。WebView も単純なアプリ内ブラウザではなく、リソースの種別によって違う画面として作られています。そしてそのマッピングのロジックはサーバサイドに持たせておきたい。

そのために、WebView 内で遷移が発生したとき(= リンクをタップしたとき)は、その都度、遷移する前に API に対して遷移先を問い合わせるようにしました。すべてのリソースとネイティブ / WebView の画面に一意となる識別子を振って、アプリから遷移先の URL を API に投げると、対応する識別子を返す仕組みです。

スクリーンショット 2019-12-16 16.11.35

この仕組みの欠点は、遷移に伴って必要となるリクエストが増えてしまうので、その分の遅延が発生してしまうということです。通信状態が悪いときはその遅延は更に大きくなってしまうかもしれません。

この問題を避ける方法として、HTML 内のリンクにアプリ内での遷移に必要な情報を予め併記するという方法も考えられます。しかしながらこれは今の商品ページや記事の制作に大きな負担増を強いてしまうことになります。HTML をパースして URL (href) を置換するという方法もありますが、複数のアプリケーション(Laravel と WordPress)が混在している現状では、そのロジックの更新・保守が重荷になります。

表示にかかる時間が増えてしまうという大きなトレードオフなのでかなり悩みましたが、現状置かれている状況と後方互換性や拡張性とのバランスを考えて、今はこれが最善だろうと判断しました。

操作に対する遅延は UI 上の表現でいくらかはストレスを緩和できる余地があることと、この仕組みは Dynamic Links (いわゆる「アプリで見る」リンク)などにも流用できるだろうという算段もあっての判断でした。

(実際に、この仕組みのおかげで Dynamic Links は遷移先の URL そのものをパラメータとして渡すだけという作りにできたので、特別な変換を用いなくても生成することが可能になりました。)

VMD(ビジュアルマーチャンダイジング)

スクリーンショット 2019-12-16 15.37.41

ウェブの「北欧、暮らしの道具店」においてトップページはお店の顔です。サイトに訪れたお客様の「今日は何があるかな?」に応えるために、その日の新商品やおすすめを毎日(毎営業日)更新しています。アプリにおいては起動時のホーム画面が同じ役目を果たすことになります。

現状の運用を考えると WebView が妥当な気はします。お店としてその日にお客様に見てもらいたいものがあり、それは内容だけでなく、何を大きく見せるかやどういう順番で見せるかなども大事で、その表現の幅は最大限確保したい。普通にネイティブで実装してしまうと、レイアウトの変更はアプリのアップデートを待たなければならなくなり、これはやりたいことを実現できているとは言えません。

(余談ですが、僕の奥さんは滅多にアップデートしないようで、たまにスマホを見る機会があると App Store のアイコンに付いているバッジのカウントに驚愕します。それもあって、開発にあたっては「ユーザー、とくに私たちのお客様を中心とした層はアプリをあまりアップデートしない」という前提を常に置いていました。)

提供したいものを提供するために技術があるのに、技術によってそれが制限されてしまっては本末転倒です。けれども、お店の顔だからこそ、最もユーザーが気持ち良い画面であってほしい。つまりネイティブで実装したい!

そこで考案(?)したのが、画面のレイアウト(表現)を提供する API です。予め想定される UI コンポーネントを定義し、ネイティブ側に実装した上で、API からはコンポーネントの種類とそこに詰め込まれるデータを返します。その他の商品や記事といったリソースを提供する、いわゆる RESTful らしい API とはまったく違う性質を持つものです。

そしてトップページの管理画面を作り直し、1つの管理画面で更新した内容を、ウェブ側では今まで通りの HTML として出力しながら、アプリに対してはアプリ用のレスポンス(JSON)に変換した上でこの API を通して提供しています(ある意味 representation を実現しているとも言える)。今回、既存のコードで唯一大きく手を入れたのがこの部分です。

スクリーンショット 2019-12-16 17.57.33

予めコンポーネントを用意しておくという点で、その変更が難しくなってしまうという欠点はあるのですが、既存の運用コストを大きく増やすことなく、かつネイティブ実装でありながらウェブと同様に「その日にお客様に見てもらいたいもの」を少なくとも既存の運用に近いレベルで柔軟に表現できる、そんな仕組みにできたのではないかと思っています。

既存のアプリケーションと運用、後方互換性と前方互換性、ウェブとアプリの技術的な制約といったいくつもの条件が重なるなかで、さらに「ユーザーはなかなかアップデートしない」という前提を置きながら、トレードオフを鑑みてどこまでコントローラブルにしておくか、というのも API 開発の大きなテーマの1つでした。

この半年を振り返る

スクリーンショット 2019-12-16 16.19.34

振り返ってみると、ほとんど綱渡りみたいな開発・実装だったなーと思わざるをえません。

それでもなんとか「秋」までにリリースまで漕ぎ着けることができ、そしてお客様からいくらか評価して頂けるものを提供することができたのは、開発に携わった社内・社外のメンバー、そしてアプリリースとその後の運用に向けて準備を進めてきた開発以外のメンバー全員の努力があってこそだと思います。

また、前職は Rails ではありましたが Elasticsearch を扱ったプロジェクトにも携わっていたので、その経験があって本当に良かったなーと思います。WordPress のデータを扱う上でも、前職の前職ではウェブサイト制作を主にやっていて WordPress ばかり触っていたので、人生、どこで何が役に立つか分からないものです。プライベートで iOS アプリを作っていたことも API 設計やアプリ開発チームとのコミュニケーションで役に立ちました。

加えて、この綱渡りを乗り越えられた要因の1つとして、僕は「開発をデザイナーと二人三脚で進めることができた」という点があると考えています。僕はこの半年、ずっとデザイナーの村田(冒頭に掲載したインタビューの「わかりたい村田さん」の方)とディスカッションばかりしていた気がします。僕だけでなく、チームとしても、開発に関するミーティングは常にエンジニアとデザイナーとが同席した上で進められました。

プロダクト開発において “What”(何を作るか)と “How”(どうやって作るか)という区別があります。今回の僕たちの体制であれば、デザイナーが「何を」、エンジニアが「どうやって」を担当していたと言えます。この両者の関係はよく「上流」と「下流」のように見做されることがありますが、僕は、そうではなく、両者は双方向に依存する関係にあって、工程として分割・分担することは難しいものだと考えています。

「何を作るか」の選択肢の幅は、「どうやって作るか」即ち「何が作れるか」がその最大値になるはずです。将来的な拡張性まで含めた「どうやって作るか」のディティールは「何を作るか」をどこまで深く理解できるかによって大きく左右されます。とくに今回のような「綱渡り」のような開発において、できる限り “What” と “How” それぞれを最大化するためには、両者の連続的で緊密な連携が不可欠だったと思います。

最後に

スクリーンショット 2019-12-16 14.18.24

忙しい一日の始まりと終わりに一息つける
いつ開いても北欧、暮らしの道具店の世界に戻ってこれる
(アプリのステートメントシートより抜粋)

開発の初期に共有されたステートメントシート(アプリの開発目的みたいなもの)のなかでこの部分がとても印象に残っていて、開発中に迷ったときはいつも「夜、布団のなかでスマホを取り出して、開いたときに一息つけるアプリになっているか」を判断の拠り所にしていました。

お客様にとって、そういうアプリになっていれば、開発に携わった一人のエンジニアとして幸いです。

***

おまけ ①

画像9

ハノイでの打ち上げで iOS エンジニアと乾杯したときの写真。「API に乾杯」と言ってもらえて嬉しかった。(API に乾杯ってよくわかんないけど)

おまけ ②

クラシコムではエンジニアを募集しています!Android アプリのリリースをはじめとして、やりたいことはまだまだいっぱいあります!

最後まで読んでいただきありがとうございました 🙇