snaqme Engineers Blog

おやつ体験メーカー snaq.me エンジニアによる開発プロダクトブログ

おやつの商品開発と「データ」による裏側

こんにちは、スナックミーでデータエンジニアをしている加藤です。

スナックミーでは多種多様なおやつの中からユーザー一人一人に合った8つのおやつをお届けするサービスをメインに展開しています。

そんなたくさんのおやつ達は、定期便の商品を主に担当する商品開発チームによって日夜、考案・試作され、より多くのユーザーさんに楽しんでもらえるように開発が行われています。

今回はこの商品開発をさらに良いものとするための、データを扱う立場からの関わりや取り組みについて紹介していこうと思います。

スナックミーの商品開発

スナックミーでは常時数百近いSKUを展開しており、さらには毎週月曜日に新商品を打ち出すというサイクルが回っています。 これらの美味しいおやつは非エンジニアである商品開発チームによって開発されており、ユーザーさんからいただいた評価やコメントなどを元に様々な商品が生み出されています。

ありがたいことに多くのユーザーさんから評価などをいただいており、その結果、データを確認するために度々エンジニアへとSQLによるデータ抽出依頼が飛んでくる状況が続いていました。

そこで開発の効率化・高度化のために、ここ数ヶ月商品開発チームと連携して様々なことに取り組んできました。

シンプルなデータの可視化・ダッシュボード化

取り組みのファーストステップとして、商品開発チーム用のダッシュボードを作成し、現状の見える化を進めました。 データをいつでも簡単に、かつ一元的に確認できる体制を作ることで、まずは現状がどうなっているのかを正しく把握しやすくすることから始めました。

例えば上の画像のようにユーザーさんからいただいた各おやつの評価などを可視化し、モニターできるようなダッシュボードを作成していきました。

最初のうちは非常に単純な数字の可視化ばかりでしたが、次第に商品開発チーム側からも「もっとこういう値が見たい!」や「こういう点から考えるためのデータが欲しいんだけど」などといった声をいただけるようになり、商品開発チームにとって意味のあるダッシュボードへと進化していきました。

商品を様々な視点で見るための指標の考案・提供

より良いおやつを開発していくためには、だんだんと単純な数字の可視化だけでは不十分に感じることが多くなっていきました。 なぜなら「より良い」ことを考える上での基準が複数あるからです。

例えば、おやつの評価について言えば、最も単純な指標としては平均点があるでしょう。 しかし、

  • 万人受けする全体的に評価の高いおやつ
  • 一部の人からはものすごく良い評価を得られるが、別のある人に取ってはあまり良くない

を同列に平均点だけで考えるのは果たして正しいでしょうか? おそらく平均点だけで言えば万人受けするおやつが高くなるはずですが、ユーザーさん一人一人にとってより良いおやつの開発を目指す上では、2つめの一部の人から高い評価を得るおやつも「違った視点では良いおやつだ」と判断をくだせる必要があります。

また評価だけでなく、

  • 生産のしやすさ・発注量の増加幅
    • とても評価が高く人気のおやつでも、ほんのわずかにしか作れずお届けできる量が限られていては困ります
  • 割れや欠けの発生しにくさ
    • どんなに美味しくても、おやつの強度に問題があり、お届けの途中で毎回粉々になってしまうのではがっかり感が大きくなってしまいます
  • 季節感やシーズンとのマッチ
    • 春夏秋冬やイベントごとなど、いつ食べるのかという点も無視できません

といった様々な要素が関与してきます。 しかし、これらを全て一つずつ見えるようにしたところで、今度は逆に商品開発チームが目を通すべきデータが多くなりすぎてしまい、いずれ確認することすら億劫になってしまうことは容易に想像がつきます。

そこで、いくつかの視点から複合的な指標を考案し、対象を絞ったデータを商品開発チームには注視してもらうようにしました。

上の画像は、ある「別の視点での良さ」を可視化した複合指標のダッシュボードです。 これにより、平均評価だけでは分からなかった点まで議論できるようになりました。

コミュニケーション密度を上げ、要望を吸い上げやすい体制を作る

上であげたような指標の整備と合わせて、定期ミーティングではどのおやつがどの指標に対してインパクトを与えると仮定しているのか?またその結果はどうだったのか?といった形で振り返りなどを進めるようにしています。

そうすることで、おやつごとに求める良さを整理し、より解像度を上げた振り返りと今後の開発への活用ができるようになってきていると感じています。

このように、整備したデータを提供する側から積極的にコミュニケーションを取るようにし、データ利用の促進や商品開発チームが求めているデータがどういうものなのかを確認しやすい体制を作っています。

今後に向けて

ここまでいくつか紹介してきましたが、こうしたアクションは大量のデータが扱いやすく整備されていることを前提にしています。 データ基盤の構築によって、ここ最近で様々なアクションが取りやすくなってきましたが、まだまだ進化の余地のある領域だと思っています。

個人的には、(知識的に比較的明るい) 統計解析や数理モデリングによる分析の高度化や、機械学習による商品開発の補助についても今後進めて行けたらと思っています。

最後に

今回はスナックミーの商品開発について、データエンジニアの視点から書いてみました。 まだまだできること、やれていないことも多く今後の伸び代は非常に大きいです!

もしデータドリブンな商品開発やおやつに興味のある方がいらしたら、ぜひお話しを聞きにきてください!お待ちしています!

meety.net

engineers.snaq.me

おいしいおやつの定期便snaq.meマイページの変遷

snaq.meでプロダクトマネージャーをしている渡邉(@nabepiyoo)です。

私からは、技術的な詳細というよりはマイページがどのように変わってきて、それがユーザの皆さんのおやつ体験をどうお手伝いできるのかという話をさせていただければと思います。普段はマイページの課題が何なのか、どんなことをすれば喜んでいただけるのか、データを見つつ優先順位をつけながらチーム一丸となって開発を進めています。

マイページというとどういうものを思い浮かべるでしょうか?

住所情報等の変更手続きができたり、月々の支払い情報が確認できたり、お問合せフォームがあるとか、そのようなことを思い浮かべられたのではないかと思います。お菓子はスーパーやコンビニ、百貨店、街の洋菓子屋さん、和菓子屋さんなどなど、いろんなところで買うことができます。おやつを定期便でお届けするビジネスモデルでユーザの皆さんにご満足いただけるよう、2016年の開始当初からマイページは大きく変化してきました。

2016年に公開されたsnaq.meマイページの歴史

2016年3月にスナックミーは初めてsnaq.meというサービスを公開しました。 初回発送に合わせて評価機能を追加

当時、評価機能を初回発送に併せてリリースしています。今と比べるとだいぶ粗いかもしれませんが、当初からユーザの皆さんにご満足いただけているかどうかを測る重要な仕組みがありました。

2017年当時の評価機能
2017年当時の評価機能

そこから何度もバージョンアップを繰り返し、6年経った今、私たちはおやつを評価するだけでなく、お届けしたおやつを見れたり、特に大好きだったおやつを記録することができるおやつ手帳、高く評価したおやつは大袋をリクエストする機能などをご提供しています。これがこの6年で起きた進化です。

2022年に公開したおやつ手帳
2022年に公開したおやつ手帳

届けてほしいに応えるためのリクエスト機能

snaq.meのマイページでたくさんのユーザさんにご利用いただいている機能の1つにリクエスト機能があります。

2016年のリクエスト機能

こちらも2016年からラインナップ※ にあるおやつが並んでいて、届けてほしいおやつに対してリクエストをすることができるようになっています。

2017年のリクエスト機能
2017年のリクエスト機能

こんなによくご利用いただいているのであれば、きちんとしたUIを整備してお使いいただけるようにしようということで何度もバージョンアップしています。

※ snaq.meにはラインナップという概念があります。お届けできる期間が限られるおやつがあるということです。常時70種類ほどのおやつがラインナップに存在しています。

現在のリクエスト機能

そして2022年には、バイヤーイチオシのおやつという特定のおやつをピックアップしたコーナーも出来ました。従来から多くのおやつが並んでいたのですが、それならイチオシのおやつはどれなの?という疑問にお応えするような形になっています。

好き嫌いを考慮してお届けする

ユーザさんとインタビューをしていると、snaq.meを始める前に苦手なものが届いたらどうしようと思っていたというお声をいただくことがあります。

しかし、snaq.meでは2016年からマイページで好き嫌いをご登録いただくことができます。

好き嫌いは人それぞれで、同じようなおやつをお好みの方でも苦手なものは異なっていたりします。

シンプルに嫌いな原料や成分などで登録することができたり、好きなジャンルを設定することができます。

ですからsnaq.meでは毎回8個のおやつを好き嫌いを考慮して、苦手なものが入らないように、なおかつ毎回同じようなものにならないような仕組みでおやつを選んでお届けしています。

ただ、好き嫌いをご登録いただいていない状態だと苦手なものが入ってしまう可能性はあるので、ユーザの皆さんはこの機会に設定されているかどうかご確認いただけると幸いです。

よりおやつに触れたくなるホーム画面へ

2017年のマイページ
2017年当時のマイページ

私たちはこうしてマイページを変化させてきたわけですが、改めて気づいたことがあります。

おやつはデジタルには存在しないということです。お手元に届くまではおやつについて頭の中で変換する必要があります。

ですからホーム画面に来ると何か分かりやすく新しいおやつの情報を知ることができるとか、クリアしていくことでお届けするおやつがより好みになっていくチャレンジができたり、こんなおやつがあったな、また食べたいなと思い出していただけるように表現し始めたのが2022年です。

先ほどお伝えしたような評価やリクエスト、好き嫌い登録といったものはチャレンジに含まれており、これらを行なっていただくことで、自然と好きなものがお届けされるような仕組みになっています。

8月には夏まつりをデジタルでも直営店でも行いまして、期間中にはホームの背景が夏まつり仕様になっていました。

2022年夏まつり期間のマイページ
2022年夏まつり期間のマイページ

2016年当時のマイページから、現在ユーザの皆さんがご覧いただいているマイページはほんの少し綺麗になりました。なるべく直感的に理解でき、子供の頃に待ち遠しかったおやつの時間のように楽しい体験をしてもらえるようなマイページを届けるということを今snaq.meでは実は行っています。

今後も引き続き、おやつに触れたくなるようなマイページを整えていきます。

最後に

そんなスナックミーではもりもりコードを改善し、開発していきたいエンジニアを募集中です。 採用の最新情報はこちらにありますので、ご興味ある方はご確認ください!

engineers.snaq.me

何でも良いので話したいよという方はカジュアル面談でお話ししましょう😄 meety.net

ユーザーに最適なおやつを届ける「アサイン」の歴史

こんにちは、スナックミーでデータ周りを担当するエンジニアをしている加藤です。 スナックミーでは、ユーザーのみなさんにお届けするおやつを様々な情報をもとに選定することをアサインと呼んでいます。

過去にもこのアサインに関する記事がいくつか書かれていますが、今回は少し俯瞰した目線で、その遍歴とこれからについて書いていこうかなと思います。

おやつを選定する「アサイン」

スナックミーでは、ユーザーごとに適したいくつかのおやつを選定してお届けするサービスを展開していますが、この「適したおやつ」を見つけるためには非常に多くの情報を扱う必要があります。

  • 好きなおやつは入れてほしいし、嫌いなおやつは入れないでほしい
  • できれば避けたい成分は入っていてほしく無い
  • リクエストしたおやつは入れてほしい
  • 過去に食べて苦手だったものは避けたい

などなど。 こういった情報から適したおやつを選定するためのアルゴリズムを弊社では独自に開発しており、これをアサイアルゴリズムと呼んでいます。

現在進行形でこのアルゴリズムは進化し続けており、より適したおやつ選定となるように開発が続けられています。

手作業によるアサイ

概要のところで多くの情報を扱う必要があることを書きましたが、サービス開始当初はなんと手作業にてアサインが行われていました。ユーザーのデータを1件ずつ確認し、どういう商品が良いか、どういう組み合わせが良いかを決めていくという非常に属人性が高くかつ時間のかかる作業だったわけです。

仮に1人1分で終えたとしても、ユーザーが500人もいれば8時間以上もかかる作業になります。 現在はありがたいことにさらにユーザー規模が拡大しているため、手作業で行おうとすればとてもじゃないですが1日では終わらないですね・・・。

アサイアルゴリズムのシステム化

当初から急務としてアサインのシステム化が進められ、およそサービス開始3ヶ月にしてシステムのβ版が導入されました。

その後も改良が続けられ、最終的にはAWS Batch上で動くシステムによっておよそ2000人のユーザーへのアサインが5~10分程度で完了するようになりました。

フルリプレイスとアーキテクチャの更新

しかしユーザー数のさらなる増加への対処や、度重なる改修による開発困難性の増加もあり、アサイアルゴリズムのフルリプレイスが行われました。

アーキテクチャからコードの構成まで議論を重ねて考え直し、1月ほどかけてリプレイスを実施しました。

結果的にはアルゴリズムの速度も開発容易性も増加し、現在もアサイン周りの改良を続けていますが、これまでよりも少ない工数で実装できていると感じています。

このアサインの改善については以下の記事で詳しく紹介しています。 labs.snaq.me

また、現在のアサインのアーキテクチャについてはこちらの記事も見てみてください。 labs.snaq.me

「おやつ得点」の導入

上記のリプレイスと合わせてもう一つ大きな動きがあり、それが「おやつ得点」という考え方の導入です。 詳細なアルゴリズムを紹介することはできませんが、ユーザーにとってより最適なおやつをを探すために、データベース上に大量に蓄積されているユーザーの過去のアクション (評価、リクエストなど) データから、ユーザーごとに各おやつの好みの度合いをスコア化し、これをアサインに導入しました。

こうしたML的なアルゴリズムとの融合によっても、ユーザーによりよいおやつをお届けすることを目指しています。

今後に向けて

ここまでざっくりとアサインの遍歴をたどってきましたが、アサインを大まかに表すと

  • 上限の決まった箱 (snaq.meであれば8つのおやつを選定します)
  • いくつかの情報を持ったユーザー
  • いくつかの情報を持ったおやつ
  • 好みなど満たすべき制約条件

が登場する最適化問題を解くことに他なりません。

ただし、その満たすべき条件は非常に多岐に渡ります。

  • 嫌いなもの・苦手なものが入らないこと
  • 好きを優先しつつ、バランスよく入ること
    • いくらクッキーが好きでも、クッキーばかり8つは流石にがっかりしますよね。
  • ユーザー1人1人の最適化と全ユーザーの最適化を同時に満たすこと
    • ある人は好きなものばかり、でも別のある人は嫌いなものばかりではいけません。
  • 好みの優先と在庫の消化を調和させること
    • 好きなおやつを入れることが最優先ですが、現実問題、在庫の多少を無視することはできません。

などなど・・・

今あげたものの他にもいくつかの条件が存在します。これをどんな方法でどれくらいの時間で解くかがアサインなわけです。

その解法は画期的なアルゴリズムかもしれませんし、MLをメインに据えた方法かもしれません。いずれにしろアサインはまだまだ完成形ではなく、日々改良を続けて行く必要があります。

近い将来、さらなる発展を遂げたアサインはどんな形になっているでしょうか? もしかすると人間には理解できない無数のパラメータによって管理されている未来もあるかもしれません。 とにかくワクワクが止まりませんね!

最後に

今回はスナックミーにおけるアサインの遍歴について紹介しました。 過去幾度もユーザーの声やデータをもとに改善が続けられ、色々な課題を解決してきたアサインですが、まだまだ課題は山積みです。

ユーザーにどんなおやつをお届けするかは弊社のサービスの根幹にあたる部分なので、今後も常にどんな形が良いかを考え改善していきたいと思います。

また、そんなアルゴリズムを一緒に開発してくれるとエンジニアをスナックミーでは募集中です!ぜひお待ちしています!

meety.net

engineers.snaq.me

FastAPI schemaの考え方

こんにちは
今回はFastAPIのschemaの考え方を書いていきたいと思います。

FastAPIではpydantic を採用しており、pydanticを利用してschema設定していきます。 今回でいうschemaはDBのschemaではなく、I/Oのリクエスト・レスポンスのschemaの考え方になります。

schemaを考えていくときに押さえていきたいこと

型の設定

昨今、型設定できない開発は開発スピード、不具合の原因、心理的安全性など多方面で良いことがないです。(型に縛られすぎるのも本末転倒なのでほどほどに) そのため、型設定できる方法があるのであれば、導入を考えたい。pythonでは mypy がありますので、mypyを用いて型のチェックをしていきます。

mypyは、プログラムの実行前に型チェックを行います。この型チェックはPythonの型ヒントという記法で書かれた情報をもとにしているので、型ヒントがない場合は、チェックがほぼ行われないので注意が必要です。

$ pip install mypy
box: int = 1
snaqme_price: dict[str, int] = {"box_fee": 1880, "delivery_fee": 330, "discount": 1000}

def total_delivery_fee(box: int) -> int:
    return snaqme_price["delivery_fee"] * box

このような int型やdict型などはさまざまなパターンで肩を決めることが可能です。 mypyに関しては別途詳しく書いていきます

ドメイン

schema管理する上でこのドメインで考えていくことが他の2つより大事だと考えています。 例えば、ユーザー情報を考えていく場合

・ 名前
・ メールアドレス
・ 住所
・ 電話番号
・ 決済情報
・ 生年月日
・ 好きなこと
・ 嫌いなこと
など

上記のような属性があった場合

ユーザーbase
・ 名前

ユーザー基本情報
・ メールアドレス
・ 住所
・ 電話番号
・ 決済情報
・ 生年月日

ユーザーの嗜好
・ 好きなこと
・ 嫌いなこと
など

のように分けられるかと思います。 この分け方が絶対と言いたいわけではなく、ユーザー情報という何も考えず一つのスキーマとして管理し続けるとどんな情報が入っているのかぱっと見わからなくなってしまいます。ユーザー情報 としている部分も将来的には分割が必要になり、肥大化されていく予想がつきます。 そのために細かく分けすぎず、大きく分けすぎずが良いと考えています。データベースと違い、FastAPIのレスポンスなどのスキーマは変更しやすいので、気軽にスタートできるのも特徴の一つになります。

レスポンスとリクエストの区別

スキーマとして管理できているがレスポンスとリクエストを根本から分けていくことで開発を進めやすくなると思います。ここで疑問になってくるのが、レスポンスとリクエストに関して同様なスキーマが存在しする場合どうするか?というところです。

私の考えでは無理に共通化させない、レスポンスはレスポンス、リクエストはリクエストと分ける。同様なものを共通化することは大事になってくる部分は多いと思いますが、レスポンスとリクエストに関しては共通化することで動作に不具合が生じさせる一つの原因になりかねない可能性があると考えています。
理由としては、レスポンスとリクエストは思いのほかコンテキストスイッチが発生しうるためです。コンテキストスイッチが発生する時にスキーマを考えていく場合どうしても片方のスキーマの存在を忘れがちになると思います。

なので、極力共通化しなくても良いと考えています。

まとめ

ざっくりではありますが、FastAPIでのschemaの考え方を共有させていただきました。 schemaの考え方に正解は存在しませんが、アンチパターンは存在しています。アンチパータンは開発スピードや心理的安全性を高められないため、アンチパターンが存在していると思っています。 いろんなschemaの考え方があると思いますので、ぜひ、いろんな方のスキーマの考え方を共有してもらえるとありがたく思います。

弊社では 「おやつと、世界を面白く。」 一緒に面白くしたい仲間をお待ちしてます!!

meety.net

engineers.snaq.me

FastAPI 独自でエラーハンドリングを設定する方法

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
FastAPIを開発するときに独自でエラーハンドリングを設定する方法を書いていきたいと思います。

FastAPIの採用背景は以下をご覧ください

labs.snaq.me

リプレイスの場合、エラーハンドリングはリプレイス前の仕様と同じにする必要があり、少なからずレスポンスはコントロールできる状態が必要でした。リプレイスではなく初めからFastAPIを採用したという方でも、日本語に対応できるのだろうか?エラーコードってどういう仕様なのだろうか?などカスタムしたくなるシーンは出てくると思います。

FastAPIのデフォルトのエラーハンドリング

パスがない場合

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}

Request URL http://127.0.0.1/hello_world

Response body

{
    "detail": "Not Found"
}

パラメータ不足時のエラー

from fastapi import FastAPI, Form
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str = Form(..., title="名前", description="名前")
    price: float = Form(..., title="金額", description="金額")
    is_snaqme: bool = None


@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.post("/items/{item_id}")
def update_item(item_id: int, item: Item):
    return {"item_name": item.name, "item_id": item_id}

Request URL http://127.0.0.1/items/1

Request body

{
    "name": "パイナップル",
    "is_snaqme": true
}

price の値が必要だがrequest bodyに入れない場合

Response body

{
    "detail": [{
        "loc": [
            "body",
            "price"
        ],
        "msg": "field required",
        "type": "value_error.missing"
    }]
}

バリデーションエラー

from typing import Union

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
def read_items(q: Union[str, None] = Query(default=None, max_length=5)):
    results = {"items": [{"item_id": "snaqme"}, {"item_id": "otumame"}]}
    if q:
        results.update({"q": q})
    return results

q は最大文字数5文字のところ6文字で入れた場合

Request URL http://127.0.0.1/items?q=123456

Response body

{
    "detail": [{
        "loc": [
            "query",
            "q"
        ],
        "msg": "ensure this value has at most 5 characters",
        "type": "value_error.any_str.max_length",
        "ctx": {
            "limit_value": 5
        }
    }]
}

このような Response body になっています。detail 直下に loc (エラー箇所), msg (メッセージ), type (エラータイプ) で構成されています。バリデーション設定によりその他のエラーメッセージも表示されています。

一方で以下のような

{
    "error": {
        "code": 401,
        "message": "ログイン失敗しました"
    }
}

のようにエラーコードとメッセージだけ出したい場合どのように行うとよいのでしょうか?

独自定義したExceptionを利用

import traceback
from fastapi import Request, Response, status
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware

class CustomException(Exception):
    """カスタム例外"""

    # デフォルトを401エラーとする
    default_status_code = status.HTTP_401_UNAUTHORIZED

    def __init__(
        self,
        msg: str,
        status_code: int = default_status_code,
    ) -> None:
        self.status_code = status_code
        self.detail = {"error": {"code": status_code, "message": msg}}


class SystemException(Exception):
    """システム例外"""

    def __init__(self, e: Exception) -> None:
        self.exc = e
        self.stack_trace = traceback.format_exc()
        self.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
        self.detail = {
            "error": {
                "code": status.HTTP_500_INTERNAL_SERVER_ERROR,
                "message": "システムエラーが発生しました。",
            }
        }


class HttpRequestMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        try:
            response: Response = await call_next(request)
        except CustomException as ce:
            # カスタム例外
            response = JSONResponse(ce.detail, status_code=ce.status_code)
        except Exception as e:
            se = SystemException(e)  # カスタムシステム例外に変換
            response = JSONResponse(se.detail, status_code=se.status_code)

        return response

CustomException のようにさまざまなパターンで例外処理を作成することが可能です。
呼び出し方は以下のようになります

from config.middleware import CustomException, HttpRequestMiddleware
from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: Union[str, None] = Query(default=None, max_length=5)):
    if q != "snaq.me":
        rails CustomException(msg=f"not {q} but snaq.me")
    return {"Hello": "World"}

app.add_middleware(HttpRequestMiddleware)

まとめ

このようにエラーハンドリングを自由にカスタムできチームとして思い通りのエラーハンドリングの参考になれば幸いです。 またエラーハンドリングはレスポンスに特化しましたが、バリデーションもFastAPI既存の仕様に従うかどうかでやり方が変わってきます。 次回以降もFastAPIで培ってきたこと発信します。

弊社では 「おやつと、世界を面白く。」 一緒に面白くしたい仲間をお待ちしてます!!

meety.net

engineers.snaq.me

スナックミーのエンジニアチームで直近動きたいこと (2022年7月)

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
四半期ごとにチームに共有している直近動いていきたいことを一部変えてこちらのエンジニアブログに載せたいと思います。

DBに残らないデータの活用

今回チームにとってしっかり取り組んでいきたいテーマがこちらになります。
弊社は商品開発・ユーザーさんに届ける商品の選定・製造/出荷効率 (リパック以外に自社でおやつも作ってます)など大事な役割が多く存在しています。これらの質の向上や施策の成功確度を高めるためにデータの活用は必須です。
今までも上記に関係するデータを活用し商品開発などは行なっていますが、活用しきれない・できないデータがいくつもありました。昨年から少しずつデータ基盤を構築していき今まで気づけなかったファクトに気づけ、捉えることができなった事象の把握・施策の実施をできるようになってきています。 (メンバーに感謝です)。
このデータ基盤は既存のDBをベースに整えてきてきました。次なるステップとして、DBに保存されないデータをしっかり活用していける基盤を作ることになります。アクセスログ・滞在時間・ヒートマップなど様々ありますが、スナックミーだからできることを考えて構築していきたいと考えています。

例えば、CRMについて。CRMは施策に対して効果があったかどうかというのはメールなどの open rateやclick rate が一般的になるかと思います。
確かに、open rateやclick rateは大事で悪いというわけではありません。ここで伝えたいことはその先で存在する意図したアクションをしてくれているのかどうなのかというのは実は分かりません。
施策を打ったが実は解約に繋がっていた、クリックしたのに何もせず直帰した、評価率向上に寄与したかったがリクエストの数も一緒に増えたなどネガティブ面もあれば、ポジティブな面の発見を総合的に判断できるようになります。

スナックミーだからできるアプローチを考え実行していきたいと考えています。

内製の強化 (+ 内製必要ないところの脱却)

2022年7月現在 社員 7名 + 業務委託 5名 計12名で開発をしているのですが、いくつか内製して開発しています。

  • 商品管理 (原料の発注〜出庫、製造、パックの数)
  • 商品の自動選定・出荷作業 (音声ピッキング)
  • ユーザーさんが利用するマイページ (サブスク、ECのような仕組み)
  • メールやLINEをセグメント配信するCRM
  • データ基盤

など. 必要に応じて開発をし、どの機能もスナックミーをワークさせるために大きな役割を果たしています。
ここでいう内製の強化というのは、上記システムの更なるアップデートになります
0 -> 1で作ったシステムや 1 -> 10 としてきたシステム様々ありますが、1 -> 10, 100 にするべきものはスケールアップしていき、 0 -> 1  で開発したがSaasなどで代用し他の開発に注力できる場合は置き換える。
また、1 -> 10 にもできるが、100 にするにはいつもよりパワーが必要で他が疎かになる可能性があるものを見極める
今いるメンバーの開発を最大限に活かし、会社, 個としてインパクトを最大化できる観点を持ち続け開発に取り組みたいと考えています。

他チームとスクラム

スナックミーはいろんなチームが存在するためいい意味でそれぞれ独立して仕事に取り組んでいます。ですが、他のチームとエンジニアが組むことで新しいシナジーを生み出せています。(マイページによるユーザー体験からオペレーションの製造・出荷まで)
数年前はできなかった(うまくワークしなかった)取り組みを今はできるようになってきたりしているので、より一層力を入れていきたいと考えています。
おやつを製造してユーザーさんにお届けするサービスを展開しているスナックミーは エンジニアだけではいいシステムは作れず、他のチームが存在しスクラムを組むことで1 + 1 が2以上の力を発揮できるようになります。
そのため、他チームとスクラムすることは今までやってこなかったわけではなく、より一層大事にしたい & 以前までのチーム目標の表現方法を少し変えているだけで、「他チームとスクラム」は根幹であると考えています。

まとめ

今年も半分が過ぎ、個として、チームとして、会社としてできる幅は日を重ねるごとに大きくなっています。その中で今回3つ大事にしたいことを書きました。いつもと変わらない姿勢もあれば、今まで培ってきたことをさらに飛躍させるための取り組みもあります。
一つ壁を乗り越えると新しい景色 + 新しい壁が現れます。常にこの繰り返しかもしれません。しかしチームと乗り越えた壁をチームと一緒に見る新しい景色を見続けたいので、少しでも興味持った方はぜひお話ししたいです。

おやつと、世界を面白く。」 一緒に面白くしたい仲間をお待ちしてます!!

meety.net

engineers.snaq.me

FastAPI 採用経緯

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
スナックミーは昨年からPHP -> FastAPIにReplaceをすすめ、すでに多くのAPIがFastAPIで本番稼働しています。

ここではなぜReplaceしようと考えたのか、FastAPIを採用したのかなどを記載させていただければと思います。

Replaceしようと考えた理由

  • 高学習コスト (メジャーではないPHPフレームワークの利用のため)
  • コードの質 (スピードを意識しすぎて コードの質を担保できなくなった 当時は 速 ≠ 質 と勘違い )
  • 心理的安全性 (テスト含めリファクタの難易度が高い)

などの理由からReplaceを決断し様々なフレームワークを調査検討し始めました

高学習コスト

メジャーではないPHPフレームワークの利用のため、新しいメンバーが入る時に

のため学習コストが高くなる可能性があったため

では、なぜPHPフレームワークを最初に導入したのかというと、立ち上げ当初は慣れたアセットで行う方がプロダクトが検証しやすいなどの理由から上記のフレームワークを導入しました。

コードの質

立ち上げ当初は1週間単位で方向性・仕様が変わる(市場検証など)ため、ひとまず動くものを作り続けていました。そのため、設計に大きな時間を使わず1に開発、2に開発、3に開発するという状況でした。

またスピードを重視するあまり、質のことをほぼ考えておらず、中長期にスピード低下する危険性などは意識していませんでした。

心理的安全性

実際のコードはスパゲッティコード状態、テストの記入なし、変更の影響範囲が分かりにくいなどから改修・追加開発をするときに難易度が高く、何か不具合があったときに問題解決に時間がかかることが予想してました。 というのもこのPHPのコードは私だけ把握するようにし、理由はPHPのReplaceは決まっている & このコードを理解するより他で稼働しているシステムを理解し追加・改修等に集中するためです。Replace促進するためあえての属人化にしてました。

Bule/Greenデプロイを導入してあった程度で、それ以外はこの開発をする際の心理的安全性は決して高いものではありませんでした。

FastAPIを採用した理由

  • 他のメンバーが学習コスト低くスタートできる
  • APIに特化したい (Railsのような多機能なFWはいらない)
  • パフォーマンスに優れている (レスポンス速度や処理)

などの理由からFastAPIにしました

他のメンバーが学習コスト低くスタートできる

昨今Rust, Go などを使った開発が主流になりつつありますが、

  • 誰も(しっかり)経験したことない言語
  • APIをReplaceするというプロジェクトであること

などの場合、学習コストをあまり当てたくないという理由でRust, Goは対象からなくしました。 その中で候補になった言語は、弊社で本番稼働しているPython, Ruby, JS, (PHP) となり、その中でもFastAPIの自由度の高さがありました。(シンプルでソースコード量が少なく、規制を弊社仕様にカスタムしながら開発できる) 自由度が高い = 複雑さが増す可能性がある ということですが、API開発という観点 & 開発する前に指針を概ね定めるという観点でカバーすると決めました。

※ 新規PJなどではしっかり用途にあった技術選定を行いますので、Rust, Goなど関係なく現時点の最善な選定をします。

APIに特化したい (view開発は求めてない)

今回はAPIのReplaceなので、API以外の機能は特にいくら優れていても加点はほぼないと割り切りました。

パフォーマンスに優れている

APIに関してはパフォーマンスの高さが大事だと考えており、ここでいうパフォーマンスとは

  • レスポンス速度
  • 高リクエストも捌ける

他の後押しポイント

  • FastAPIは2018年にスタートしとしたが github の stars 46.7k
    • (flask (2010年〜) -> 59.4k, django (2005年〜) -> 64.9k) ということで近年の中で勢いがある
  • mypy と Pydanticを組み合わせることで型対応も可能
  • Swagger が標準装備してあり、APIのドキュメント管理が自動化 (実はここほぼで決定打)

など

まとめ

いろんな角度から考察した上で、FastAPIを採用し 現在カバレッジは90%以上確保していたり、かなり安定した運用・開発ができReplaceに関しては今の所うまく行っていると考えています。 今後はFastAPIのドキュメントがまだまだ少ないというのが現状なので、弊社で培ってきた考え方(エラーハンドリング・Pydanticでのバリデーション・schemaなど)を定期的に共有していきたいと思っております。

弊社では 「おやつと、世界を面白く。」 一緒に面白くしたい仲間をお待ちしてます!!

meety.net

engineers.snaq.me