Simpacker: Rails と webpack をもっとシンプルにインテグレーションしたいのです

技術部の外村(@hokaccha)です。Rails で webpack を使うためのシンプルな gem を作ったのでそれについて紹介します。

Webpacker

Rails で webpack を利用した Web フロントエンドの環境を作る場合、最近では Webpacker が選択されることが多いでしょう。Rails 6 からは Webpacker が標準になることもあり、この流れはますます加速すると思われます。

私自身もこれまでいくつかのプロジェクトで Webpacker を利用してきました。Webpacker は webpack を Rails から簡単に利用でき非常に便利なのですが、使っているうちにいくつか不満な点がでてきました。

一番大きい問題として Webpacker が @rails/webpacker という npm パッケージに webpack の設定を隠蔽し、Webpacker 独自の API や webpacker.yml という設定ファイルを通して webpack の設定をする必要があるというところです。

Webpacker のドキュメントに書いてある範囲内であったり、Webpacker にデフォルトで組み込まれている設定であれば webpack のことを知らなくてもなんとなく webpack が使えるという点は便利なのですが、webpack のことをすでに知っていたり、Webpacker のドキュメントにない設定をしようとした場合、webpack の設定を Webpacker に反映する方法を調べなければいけません。なので使っているうちに、「頼むから webpack の設定を直接書かせてくれ!」となります(少なくとも私はなりました)。

また、webpack のバージョンの Webpacker に引きづられてしまうというのも問題の一つです。webpack 4 がリリースされてから、Webpacker の webpack 4 対応バージョンが正式リリースされるまで1年以上の間がありました。

Simpacker

Webpacker は webpack を知らない人でも簡単に使えるというところがいいところだと思うのですが、もう少しシンプルに webpack とのインテグレーションだけをしてくれるツールがほしいと思い作ったのが Simpacker という gem です。クックパッドでもいくつかのプロジェクトで導入し、うまく動いています。

Simpacker は基本的なところは Webpacker と同じです。manifest.json という、ハッシュ値が付与されたファイル名ともとのファイル名のマッピング情報を持つファイルをもとに、JS や CSS を読み込むタグを生成するヘルパーを提供します。Webpacker と違うのは、Simpacker は webpack 側の設定を一切管理せず、出力する manifest.json のパスしか知らないというところです。開発者は好きな webpack のバージョンを使い、直接 webpack.config.js を書いて、Simpacker で設定されたパスに manifest.json を出力すればいいだけです。

そのため、原理的には webpack でなくても Parcel や Rollup などのモジュールバンドラーでも利用できます。実際に Parcel で動くことは確認してはいています1

例えば webpack では webpack-manifest-plugin などのプラグインで manifest.json を出力できます。これによって以下のような JSON が出力されます。

{
  "application.css": "/packs/application-6ebc34fd09dc6d4a87e9.css",
  "application.js": "/packs/application-6ebc34fd09dc6d4a87e9.js",
}

Rails 側では Webpacker と同じように、javascript_pack_tagstylesheet_pack_tagなどが使えます。

<%= stylesheet_pack_tag 'application' %>
<%= javascript_pack_tag 'application' %>

これは manifest.json の値を読んで以下のようなタグが出力れます。

<link rel="stylesheet" href="/packs/application-6ebc34fd09dc6d4a87e9.css" />
<script src="/packs/application-6ebc34fd09dc6d4a87e9.js"></script>

Simpacker の導入

simpackerをGemfileに追加してインストールしたら以下のコマンドで初期設定のファイルがインストールできます。

$ rails simpacker:install 

これで必要最低限の設定とJavaScriptのコードがインストールされるのでnpx webpackでビルドしてjavascript_pack_tag 'application'のヘルパを書けば動きます。

ただし、これでインストールされる webpack.config.js は本当に最低限の設定と manifest.json を出力する設定がしてあるくらいです。Webpacker ではデフォルトで組み込まれる、Babel や PostCSS、file-loader、webpack-dev-server などは、どれも入りません。これは、利用しないものがデフォルトで色々入るのは個人的にあまり好きではないという理由が大きいところです。

また、Webpacker であれば webpacker:install:react などのコマンドで React をインストールしたりできますが、このような機能も提供していません。Babel や TypeScript などのトランスパイラ、React や Vue などのフレームワーク、CSS も 素の CSS から Sass、PostCSS など様々な組み合わせがあり、それらの組み合わせをインストーラーでまかなうのは複雑すぎると思ったからです。

なので開発者自身が自分が使いたいものを選んで webpack.config.js を設定していく必要があります。webpack や各種言語やフレームワークのドキュメントを見て webpack の設定をがんばってください、でもいいのですが、何も知らない状態から webpack を設定するのはまあまあ難しいことが知られているので、各種設定例を用意することにしました。現時点では以下のような設定例を用意しています。

お察しの通り、半分以上 Simpacker は関係なくて単に webpack の設定です。初期の最低限の設定から、必要なものだけをこれらから選んで設定することで webpack の設定についても知ることができるので、webpack の入門にもいいのではないかと思います。単なるドキュメントでなく実際に動く例なので手元に持ってきて色々試すのにも便利です。

Simpacker にない機能

Webpacker が提供している機能で、Simpacker が提供していない機能もいくつかあります。

デプロイ

例えば、デプロイ関連の機能です。Webpacker では webpacker:compile という rake タスクが提供されていて、このタスクは assets:precompile の後に自動で実行されます。したがって、多くの場合はデプロイの設定を変えずにこれまでと同じようにデプロイできるはずです。

Simpacker はどんなコマンドで webpack のコマンドが実行されるか知りませんし、package manager に npm と yarn のどちらを使うのかも知りません。当然そのような設定項目を足せば実現できますが、シンプルさを重視してこの機能は提供しないことにしています。

とはいえ難しいことはなくて、以下のようなコマンドをデプロイフローに足せば済むはずです。

$ npm install
$ NODE_ENV=production ./node_modules/.bin/webpack

デプロイに関しては上記の設定例にもいくつか例を書いています。

リクエスト時のコンパイル

また、Webpacker には webpack --watchwebpack-dev-server などの監視プロセスを立てなくても、リクエストの処理中に Rails 側で webpack のコンパイルを実行するという機能があります。当然リクエストのたびにコンパイルが走ると遅すぎて話にならないので、対象ファイルの変更があったときだけコンパイルが走りますが、フロントエンドを開発しているときは当然毎回ファイルが更新されてコンパイルが走ることになるので、監視プロセスを別途立てるのが一般的です。この機能は普段フロントエンドを開発しない開発者が別途監視プロセスを立てなくても開発できる、というためのものと理解しています。

この機能は便利なケースもあると思うですが、これを実現するには Simpacker が知らないといけない webpack 側の情報がかなり多くなり、設定が大幅に複雑化してしまうので、一旦対応を見送っています。webpack --watchwebpack-dev-server などのコマンドを foreman などで実行すればよいだけなので、そこまで困ることはないと思っています。

まとめ

Rails 自身がそうなのですが、Webpacker も同じように、いかに簡単に使えるか(easyさ)を重視した仕組みと言えます。それに対して Simpacker はひとつのことだけうまくやる、シンプルさを大事にした設計にしました。そのような設計思想の違いがあるので、easy さを好む人は Webpacker を使うほうがいいと思います。シンプルさを求めて webpack の設定を直接書きたいという場合はぜひ Simpacker も検討してみてください。

クックパッドではWebフロントエンドの開発基盤をつくったりすることが好きなエンジニアも募集しています!!!


  1. manifest.json を作るプラグインが .ts.vue などの拡張子でうまく動かなかったり、ファイル名にハッシュ値をつけるためにエントリポイントとして html が必要だったりするので、現時点では現実的な利用は難しいかもしれません。

Firebaseで運用するKomercoの管理用アプリケーションの開発

こんにちは。Komerco事業部エンジニアの高橋(id:yosuke403)です。「料理が楽しくなるマルシェアプリ」であるKomercoの開発を行っています。

Webサービス開発と聞くとユーザが利用するWebアプリやモバイルアプリの開発を思い浮かべますが、運営スタッフがサービスのデータを閲覧・更新するための管理用アプリケーションの開発も必要になることがほとんどです。

KomercoはバックエンドにFirebaseを活用しているのを一つの特徴としているサービスです。 今回はKomercoの開発事例を通して、Firebaseを用いた管理アプリケーション開発の知見をご紹介したいと思います。

Komercoの管理用アプリケーションについて

KomercoではFirebaseのHostingを利用し、Webで管理用アプリケーション(以下、管理アプリ)を提供しています。

f:id:yosuke403:20190705083445p:plain

Komercoの管理アプリでできることとして、

  • 登録商品の監視
  • 販売許可証の審査
  • コメルコバナシ(アプリ内の記事コンテンツ)や特集(テーマに沿った商品のピックアップ)の更新
  • Push通知

などがあります。

管理アプリの利用者はKomercoのスタッフですが、エンジニア以外のメンバーが触ることが多いです。また、社外の業務委託先にも管理アプリを利用してもらい、一部の業務を依頼しています。

アクセスログを残す

管理アプリからは一般ユーザのプライベートな情報も閲覧できますし、またデータの更新が一般ユーザに影響を与えることもあるので、トラブルに備えて誰が、いつ、どんな操作をしたかを記録する必要があります。

KomercoではデータベースにFirebaseのFirestoreを利用していますが、Firestoreには誰がどんな操作をしたのかをログ出力する機能がありません。 そのため面倒ではありますが、Webフロントエンドから直接Firestoreへはアクセスさせず、Cloud Functionsを経由して読み込み、書き込みを行うようにしています。

Cloud Functionsはイベント駆動でアプリケーションを実行できるFirebase(GCP)の機能です。イベントトリガーにはいろいろな種類がありますが、 functions.https.onCallをトリガーとしてセットした場合、渡されてくるCallableContextのデータから簡単にユーザIDが取得できます。このユーザIDはFirebase Authenticationに登録されているIDで、これを確認することで誰がこのFunctionを呼んだかが分かります。

またCloud FunctionsからFirestoreへのアクセスは通常Firebase Admin SDKを使用しますが、 Firestoreへのアクセスを記録するためにの薄いラッパーを作っていて、それを経由してSDKを呼ぶようにしています。 これによりFirestoreへのデータ読み込み・書き込みが発生すると、誰がFirestoreへどんなアクセスしたか、ログに吐き出されるようになります。

f:id:yosuke403:20190705085904p:plain

こちらは実際に出力されているログの例です。uidに対応するスタッフがproductとadminのドキュメントをgetしたことが分かります。

f:id:yosuke403:20190705083450p:plain

Webフロントエンドから直接Firestoreへアクセスできないのが面倒ではありますが、Firestoreのセキュリティルールが複雑化しなくて済むというメリットもあります。 もし直接Firestoreにアクセスできるようにするなら、一般ユーザ向けアプリ用のルールと管理アプリ用のルールが混在するようになってしまいます。

管理アプリユーザごとの権限を設定する

管理アプリユーザが誤って意図しない操作を行ってしまったり、閲覧してはいけないデータを参照したりしないよう、管理アプリユーザごとにロールを設定しています。 Functionが実行されたときは、まず最初にアクセスユーザのロールをチェックし、リクエストのあった機能の利用権限があるかを判定しています。

現状は簡易な設計をしていて、Firestoreに管理者を登録するコレクションを作成し、各ドキュメントにはユーザIDをドキュメントIDとし、ロールに対応する文字列の配列をフィールドとして持たせています。 Functionが実行されたときにこのドキュメントを参照して権限チェックを行います。

f:id:yosuke403:20190705083510p:plain

パフォーマンス問題

Cloud Functions経由でデータ取得・更新するようになって問題になったのが、管理アプリのデータ表示にかかる時間が長くなったことです。

管理アプリは基本的に限られたスタッフしか利用しないので、Functionが呼ばれる頻度が低く、その結果多くの場合においてFunctionのコールドスタート(実行環境がゼロから初期化されてスタートする状態)が発生しやすい状況になっていました。 コールドスタート時に遅くなる原因はいくつかありますが、大きな原因の一つがFunctionを実行するインスタンスとFirestore間で新規にコネクションを張る処理時間でした。 これはFirestoreへの最初のリクエストの時点で発生します。

Functionが変わればそれを実行するインスタンスも変わるので、コネクションが新規に必要になります。 管理アプリでは画面ごとに異なるFunctionが呼ばれることがほとんどで、新しい画面を開く度に時間がかかり、非常にストレスでした。

f:id:yosuke403:20190705083504p:plain

メモリ割り当てを増やす

Firestoreとのコネクションの確立を速くするにはメモリ割り当てを増やすのが効果的です。

次のグラフはメモリ割り当てを変更してコールドスタートの実行時間を計測したものです。 Firestoreから指定IDのドキュメントを一つだけ取得する処理を行っています。

export const testFunction = functions.https.onRequest(async (req, res) => {
    const result = await admin.firestore().doc('/aaa/bbb').get()
    console.log(result)
    return res.send('succeeded')
})

f:id:yosuke403:20190705083443p:plain

実行時間が半分ぐらいになっているのが分かります。 GCPのコンソールを見れば分かるのですがメモリ使用量としては256Mでも十分で、実はメモリ割り当てに紐付いて変更されるCPU割り当てが効いているものと思われます。 もちろん料金はその分上がるのですが、今のところ管理アプリ用のFunctionは実行回数がそこまで多くないので、高めに設定しています。

Functionを統合して呼び出しごとのコールドスタートを減らす

もう一つの対策として、管理アプリから利用するのFunctionを1つに統一し、クエリパラメータに応じてロジックを分岐するようにしました。 これにより一度確立したFirestoreのコネクションを、どのロジックを実行する場合でも流用できるため、画面遷移ごとにコールドスタートすることは少なくなりました。

f:id:yosuke403:20190705083506p:plain

Function統合のメリット・デメリット

Functionの統合はコネクションの流用以外にもメリットがあります。Functionが分かれないので、先の項目で説明したロールのチェックをはじめとする全Function共通の処理を一箇所に置くことができます。また、Functionの数が減るのでデプロイの負荷も抑えることができます。

Function統合のデメリットとしては、本来Functionごとに分かれるログがひとつに混ざってしまうことです。 しかし、現状限られたスタッフのみが管理アプリを利用している状況であるため、今のところログの量も比較的少なく、Webコンソールの検索機能から十分追えています。

パフォーマンスの改善だけであればメモリ割り当ての変更のみでもよいのですが、Komercoでは以上を踏まえ両方の対応を行いました。

データの検索

管理アプリとしてよく要求される機能がユーザや商品の検索機能なのですが、Firestoreで文字列検索を行うのは難しく、必要ならAlgolia等の外部サービスと連携するする必要があります。とはいえ、わざわざ外部サービスと連携するのは面倒です。

完全な解決作ではありませんが、前方一致だけなら対応できます。 例えばproductコレクションのnameフィールドに対して前方一致検索をかけたい場合、

firebase.firestore()
  .collection('product')
  .orderBy('name')
  .where('name', '>=', input)
  .where('name', '<=', input + '\uf8ff')

と書くことができます。

複雑な検索が不要な場合はこれで対応しています。

Cloud FunctionsのエラーをSlackに通知

Cloud Functionsで発生したエラーはSlackに流してすぐに気づけるようにしています。 Cloud Functionsには直接Slackに通知する機能はありませんが、ログはGCPのStackdriverに記録されているため、StackdriverのError Reporting機能を使うことができます。これによりメール通知としてエラー報告を受け取れます。Gmailでこのメールを受信し、フィルタと転送、及びSlackのEmail Integration機能を使うことでSlackに通知が流れるようになります。

f:id:yosuke403:20190705083412p:plain

プレビュー機能で結果を分かりやすく

管理アプリのいくつかの機能では、データ入力後にユーザにどう見えるかをプレビューする機能をつけています。 管理アプリはエンジニア以外のメンバーが使うことが多いので、自分が入力した情報がユーザにどのように表示されるのかを伝える必要があります。 プレビュー機能があると、管理アプリのユーザは安心して情報入力できるようになります。

例えばこちらはコメルコバナシの編集画面です。右半分がアプリに表示される様子をプレビューしています。

f:id:yosuke403:20190705083440p:plain

こちらはユーザへのPush通知画面画面です。画面下にiOSで通知される様子が表示されています。 FirebaseコンソールにもPush通知を送る画面はありますが、こちらの方が結果がより分かりやすいかと思います。

f:id:yosuke403:20190705083455g:plain

スタッフから喜ばれる機能なので、エンジニアも自主的に開発に取り組んだりしています。

最後に

Komercoの管理アプリを通して、Firebaseの特徴を踏まえた管理アプリの開発の知見をご紹介しました。

管理アプリはユーザの目には直接触れませんが、スタッフの効率に直接影響するため、なるべくよいものを提供したいと思っています。 Firebaseは運用にかかるコストが低く、エンジニアがサービス開発に専念できるのがとてもよいところです。 管理アプリも積極的に開発して全体の効率を上げていきたいです。

Komercoでは、モノで料理を楽しくしたいエンジニアを募集しています。 Firebaseを使ったサービス開発に興味のある方いましたらぜひご連絡ください! ご応募お待ちしております!

www.wantedly.com

www.wantedly.com

おすすめの食べ方を見ながら食材を買える体験を作った話

はじめに

こんにちは、買物事業部のデザイナー兼エンジニアの長野です。

生鮮食品ECサービス「クックパッドマート」の開発チームで、注文ユーザー向けのサービス開発全般を担当しています。

今日は、先日クックパッドマートのiOSアプリでリリースした新機能とその開発プロセスについて、お話ししたいと思います。

クックパッドマートの詳細については、以前にも サービス立ち上げ期の話 や、エンジニアメンバーの連載記事 が投稿されているので、そちらもぜひご参照ください。

食べ方を想像しながら食材を選ぶ

今回リリースした新機能は、クックパッドマートで扱う様々な商品(食材)に対して、その食材を使ったおすすめの「食べ方」を提案する、というものです。

これまでのバージョンのアプリでも、商品におすすめのレシピを紐付けて見せるということは行なっていたのですが、今回のアップデートではより幅広い種類の「食べ方」とそのレシピを見ることができるようになりました。

f:id:yoshiko-nagano:20190627094151p:plain
「食べ方」が表示されるホーム画面と商品詳細画面

例えるなら、お店の人と対面で買い物をするときに「この魚は煮付けでもいいし、塩焼きでも美味しいよ」などと会話して、自分の気分で食卓をイメージして食材を購入する体験のようなものです。

このような機能のかたちに至るまでのプロセスを順を追ってご紹介していきます。

数ある課題の何から手をつけていくか?

カスタマージャーニーマップで現状を把握する

今回プロジェクトの開始時点では、アプリのどの部分に手を入れるか、どのような課題にアプローチするか、何も決まっていませんでした。「色々課題はありそうだけど、何から手をつけよう?」という状態です。そこで、まずは現状を正しく把握するためのカスタマージャーニーマップを作成するところからプロジェクトを開始しました。

f:id:yoshiko-nagano:20190627094202p:plain
作成したカスタマージャーニーマップ

社内でサービスを日常的に使っている数名にインタビューをし、サービスの利用フローとその時の思考の流れをマップに書き起こしました。

本来はよりリアリティのある社外ユーザーの情報を集めたいところですが、まだ開始間もないサービスでユーザー数が少ないこと、社内でも普段使いしている人が複数いたことから、スピード優先で社内リサーチを選択しました。

数名のインタビュー結果から、ユーザー属性によって傾向が見られたので、最終的に3人のペルソナとしてまとめることができました。

非同期型ワークショップで課題を洗い出す

カスタマージャーニーマップからは様々な課題が見えてきます。それらを洗い出すために、チームメンバー全員参加の非同期型ワークショップを行いました。

クックパッドマートのチームは、アプリ開発をするメンバーもいれば、日々の配送を回していく流通系のメンバーなど、担当領域が多岐に渡ります。それぞれの視点から見た課題意識をきちんと洗い出すために、今回は「全員参加」の形式にこだわりました。

とはいえ、総勢25名近いメンバー全員の時間を確保するのは非常にコストが高いです。そこで「非同期」で参加できるワークショップという形をとりました。

ワークショップのやり方は以下です。

  • 開催期間は3日間
  • 3人のペルソナのマップを、席近くの壁3箇所に貼り出す
  • メンバーは好きな時間に席近くのマップを眺めて、気づいた課題と解決策のアイデアをポストイットに書いて貼る
  • 毎日マップの掲載場所を入れ替え、全てのマップに目を通してもらう

f:id:yoshiko-nagano:20190121112748j:plain
非同期ワークショップ会場の様子

結果、3日後には25人の視点から洗い出された現状の課題とアイデアを集めることができました。

課題をマッピングし、優先順位をつける

大量に洗い出された課題は似た観点の課題をグルーピングし、以下の3つに仕分けをしました。

  • 今集中して掘り下げるべきもの
  • 今意識しなくても必然的に取り組むことになるもの
  • 今のフェーズではやらなくてよいもの

f:id:yoshiko-nagano:20190627094514p:plain
グループごとに3色の付箋を貼って仕分けた様子

ここまでくると、現状のサービスの課題とそれらの優先度が自ずと見えてきて、次のトライを集中して考えられる状態になりました。

どうやって課題を解決する?

デザインスプリントで解決策を探る

上記の課題の整理から、直近フォーカスして掘り下げる課題が以下に定まりました。

  • どうすれば、日常の買い物の選択肢になれるか?
  • どうすれば、食材の購入だけに止まらない買い物体験を作れるか?
  • どうすれば、使い続けても飽きないサービスになれるか?

そこで、これらに対する解決策を考えていくためのデザインスプリントを実施しました。

デザインスプリントでは、課題に対して一つのソリューションをプロトタイピングし、ユーザーインタビューを行なって検証するという一連のプロセスを、短期間で集中して行います。(こちらの書籍に詳しく書かれているGoogleの手法を踏襲して実践しています)

f:id:yoshiko-nagano:20190627094601p:plain
スプリント中の様子

f:id:yoshiko-nagano:20190627094627p:plain
家にある食材をベースに食べ方を提案するプロトタイプを作成

ここで作成したプロトタイプがそのまま使えるものになったわけではありませんでしたが、スプリントを通した検証結果から下記のような学びを得ることができました。

  • 食材から単一のレシピへ誘導すると、ユーザーの気分や制約条件(家族の状況、調理時間、使用する調味料や調理器具など)とのミスマッチが起こりやすい (現行アプリはこの状態だった
  • レシピより一段階抽象的な「食べ方(唐揚げ、和え物、煮付けなど)」を複数提示されると、ユーザーは気分に合う料理をイメージしやすい
  • 一つの「食べ方」に対してレシピを複数提示できると、ユーザーは自分の制約条件をクリアしたレシピに出会いやすい

したがって、これらをサービスに落とし込めれば、 いつサービスを訪れても自分にあった料理と必要な食材を見つけることができ、クックパッドマートが日々の買い物の選択肢になれるのではないか という仮説を立てることができました。

本当に実現可能なの?

コアになる技術要素を検証する

スプリントを終えて、試す価値のある仮説が得られたものの『本当にユーザーにとってグッとくる「食べ方」を複数提案できるのか?』というサービスのコアとなる部分の実現可能性には疑問が残っていました。実現できなければプロダクトが成り立たなくなってしまうので、早々に検証を進めました。

幸い、クックパッドには毎日料理をしているユーザーさんが提供してくれるたくさんのデータがあります。これまでのレシピサービスの開発の過程で、レシピデータや検索ログを元にして、食材に対する「食べ方」のデータを返す機能が複数開発されていました。そのような既存機能を活用して、クックパッドマートに適した「食べ方」を提案する方法がないかを検証していきました。

検証の対象としたのは、レシピサービスの検索部分でもすでに利用されている「食べ方検索」機能のロジックや、検索キーワードと関連性の強い食材を抽出するロジック、食材に対して多くのユーザーがよく作っている定番のメニューを抽出するロジックなどです。

f:id:yoshiko-nagano:20190627094722p:plain
クックパッドアプリで提供している食べ方検索機能

検証の方法はシンプルで、クックパッドマートで扱う主な食材キーワードを各機能に投げ、返ってきた結果をスプレッドシートにまとめました。それを、日常的に料理をしている人にユーザー目線で見てもらい、一番グッとくる結果を返せた機能はどれかを精査しました。

結果として、一番良さそうという感触が得られたのは「定番メニュー抽出ロジック」でした。定番メニューと言えども、10件以上提案されると自分の頭だけでは浮かんでこなかった食べ方に出会うことができる実感が得られました。また「定番」なので、突飛過ぎずにイメージしやすいといういい塩梅の提案が出せることがわかりました。

仮説をサービスに落とし込む

技術的な実現目処もたち、いよいよサービスの中でかたちにしていきます。

Figmaで画にしてレビューを繰り返す

UIデザインは基本的にFigmaを使って共有しながら作っていますが、初期はパターンをとにかくたくさん出し、良さそうな案を探っていきます。

f:id:yoshiko-nagano:20190627094756p:plain
まずは手書きのスケッチから

f:id:yoshiko-nagano:20190627094822p:plain
Figmaでたくさんのパターンをつくる

クックパッドマートチームは、日常的に料理をしているメンバーが多く、部内で簡易なユーザーテストをしてみるだけでも有益なフィードバックが多く得られます。Figmaのプロトタイピング機能を使って実機でデザインを見せるということを繰り返し、デザインの方向性を固めていきました。

また、画にする <-> フィードバック のサイクルを出来るだけ短くする方法として、最近はペアデザインも試しています。UIデザイナーとPMで数時間社内の空きスペースにこもり、画にすることと議論することを同時進行で進めることで、デザインの精度とアウトプットのスピードが高められることが実感できました。

f:id:yoshiko-nagano:20190627094857p:plain
オフィスの片隅でペアデザインをする様子

実データを見られるようにする

ダミーデータでデザインをしていると、どうしてもこちらの都合の良い見え方だけでデザインが進んでしまいがちです。なので、早い段階で実データを見られるようにすることも意識しました。

レシピサービス側に必要なAPIのエンドポイントを作成し、クックパッドマートの管理画面からアクセスしてデータを取得できるようにする機能を早めの段階で実装しました。実際にどんなデータが何件返ってくるのかを確認できることで、デザインと現実のギャップを埋めることができました。

段階的に実装・リリースする

今回は、変更全体を一度にリリースするのではなく、商品詳細に「食べ方」を表示するフェーズと、アプリのホームに「食べ方」を表示するフェーズの二段階に分けて、リリースを進めました。

リリースを分けた理由はいくつかありますが、実装範囲が絞られることで既存機能への予想外の影響を少なくできることと、QAの対象範囲を狭めてQA期間を最小限にできることが大きいと思っています。

出来るだけリリーススピードを落とさず、チームが常にサービスを改善し続けている実感を持てる状態が、健全なサービス開発を進めていく上でとても大切だと考えています。

まとめ

「何から手をつけようか…?」と完全に手探りなところから、様々な手法を使って仮説を定め、一つの新機能としてサービスに落とし込むまでのプロセスをご紹介しました。

クックパッドマートは、買い物を便利にするだけのサービスにとどまらず、食材を買うことの先にある「料理をして食べる」という体験全体をデザインしていくことが重要だと考えています。おいしい食材でおいしい料理を作って大切なひとと食べる時間を世の中にもっともっと増やしていくために、今後もサービスを進化させていきたいと思います。

この記事を読んでクックパッドマートの開発にご興味を持っていただけた方がいれば、ぜひ一緒にサービスを作りましょう!ご応募おまちしております。

www.wantedly.com www.wantedly.com

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/