snaqme Engineers Blog

おやつ体験BOX 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

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

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

エンジニアのバリュー

S: Scale Out
N: Next
A: Agile
Q: Quality

今年から弊社はVPoEを設け CTOである私が技術面にフォーカスし、VPoE が組織面にフォーカスする体制になりました。 この背景や行っていることは別途紹介させていただければと思っています。 VPoEを作る前からエンジニアのバリューを作成したいとずっと思っており、これを機にバリューを作成しました。
ちなみにスナックミー全社では以下がバリューになってます。

[おもしろい!]
難易度や工数じゃなく「それっておもしろい?」を軸に“ワクワクファースト”で行動します。
おもしろいほうがやる気が出るし、お客様に喜んでいただけるから。

[やってみよ!]
ごちゃごちゃ言ってないで、“オーナーシップ”を持ってまずやってみよう。
[つぎにいかそう!]
失敗したっていい。学びがあれば儲けもの。
失敗を恐れずどんどんチャレンジしていきましょう。みんな”やさしい”から失敗したって大丈夫だよ!

を頭文字にしたバリューです。 snaqme.com

バリューは覚えやすく自分的に気に入っています。 ですが、このバリューは全社に向けてバリューであり、エンジニアの目線で行くと少し昇華する必要があると感じていました。理由としてエンジニアにとって少しイメージしずらいものにどうしてもなっていたりします。例えば「おもしろい」これは人によって何が面白いのかバラバラです。バラバラというのが問題なのではなく、必ずしもユーザーさんと直接紐づいていないケースがあります。SREだと仲間のエンジニアのパフォーマンス最大化のために動くこと多く、製造や出荷などの社内システムは社員やパートさんが利用するために開発するケースが多いです。最終的にはユーザーさんに紐づきますが、少し感覚距離が遠いことろにいます。
ということから「」をベースにエンジニア向けに昇華させたバリューを作りました。
以下がバリューになりsnaq.meの「SNAQ」それぞれの頭文字にしたバリューにしました

Scale Out

エンジニアリングで事業成長を引っ張っていけるような開発をしていこう。その為にもスケールしやすい仕組みの開発を心がけよう。

Next

日々の挑戦の中で失敗をしてしまった時にでも、次への一歩が踏み出しやすい仕組みづくりを心がけよう。壊しやすい仕組みだったり、最速で開発・改善していけるような環境を持ち続けよう。

Agile

小さく早くスピード感を持って、継続的に改善を積み重ねていこう。

Quality

ユーザーにとって一番良いものを作っていこう。


  • スピードを重視したいが、短期的なスピードではなく中長期的なスピードも重視したい
  • 如何に小さく始められ
  • 失敗しても次に活かせるような仕組み
  • 速さだけでなくシステムの質も忘れない

このあたりをイメージしやすいようなバリューにしてます。

詳しいことは以下に

note.com

グロースハック

バリューを作ったので、バリューを意識した動きにしていきたいですが、もう一つテーマを決めたいなと思い共有しました。
それがグロースハックです。なぜ、グロースハックにしたのかというと、エンジニアは言われたものを開発することは一つの価値ですが、エンジニア自身で価値を社内・社外に提供できるような存在になっていきたいと思っているからです。相互の目線で一つの価値を2倍にも3倍にもすることが可能であり必要だと思っています。どっちが上ではなくどんな価値が最大化されるのかエンジニア自身が考えを共有し結果ユーザーさんの満足度が上がったり、インパクトが上がるもの作り上げていきたいと思っています。

まとめ

今回はエンジニアのバリュー(Scale Out Next Agile Quality )はVPoEが筆頭に考えを共有し一つ形にしましたので、どんどん浸透させていきたいと思います。
また、このバリューに共感でき方はぜひ一度カジュアルに話しませんか? 実際どう体現させているのか、どんなことをしているのかなどお話しします。 おやつと、世界を面白く。」 一緒に面白くしたい仲間をお待ちしてます!!

meety.net

engineers.snaq.me

他ブランド含めた在庫管理について

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
弊社ではsnaq.meというおやつの定期便を展開しているのですが、実はあまり知られていない「他ブランド含めた在庫管理」について今回紹介させてください。

当初のブランドの種類

他ブランド と聞いてそもそもsnaq.meに他ブランドがあることを知らない方でもいるのではないでしょうか?
サービス最初は下記のように1つで展開していました。(1つといいつつ常時100種類ほどの多ジャンルのおやつからユーザーさんごとにパーソナライズされお届けしています。)

f:id:snaqme-labs:20220301184910j:plain

ブランドの種類

そこから進化していき今はさまざまなBOXを選べるようになっています(お届け2回目から可能です)

  • 通常: 今まで通り常時100種類ほどの多ジャンルのおやつからお届け
  • オツマミー: お酒のおつまみにもぴったりなスナック
  • グラノーラ: スナックミーのパティシエが作るグラノーラ1袋(250g)、トッピングにぴったりのナッツ、ドライフルーツ、ミニクッキーなどを合計4種セレクトしてお届け
  • 焼き菓子: 定期便でお届けしている人気の焼菓子のみをセレクトしたBOX

f:id:snaqme-labs:20220301185432j:plain

それぞれのBOXが取り扱うおやつは一部共通になっています。
ざっくりイメージとして、それぞれのBOXとおやつの共通部分は以下のような感じです。少なからず何か一緒のおやつがある状況です。
それぞれに特化したおやつもあるものの適しているおやつもあるため以下のような管理になっています。 f:id:snaqme-labs:20220301191356j:plain

在庫の管理の仕方

では、今まではどのように管理していたのかというとシンプルに書くと以下通りです。単一エリア在庫管理 同じ在庫の数を見て判断 もしくは、同一商品を作成という形で行っていました。同一商品を別々なものとして扱う場合、ユーザーごとの嗜好データにブレが生じる可能性があります。なぜならデータ上別々のおやつと判断されるため、管理コストが格段に上がってしまいます。
また、ユーザー数が増加とともに出荷スペースの細分化の必要性が増してきます。なぜなら細分化していかないと物理的なスペース・1人あたりの出荷効率から1箇所で出荷できる数に限度があるため、出荷できる箇所を分ける必要になります。(最大出荷数の向上)

f:id:snaqme-labs:20220301191728j:plain

単一エリア在庫管理

単一エリア在庫管理の在庫数を見て判断する方針で問題になってくるのが、BOXとの関係が密結合な状態であるため並列処理ができず、直列の処理になってしまいます。つまりどういうことことかというと、1つの作業が終わらないと次の作業に移れない状況になるということです。例えば2つ在庫があり、2人で分ける場合、1人が終わらないと次の人に行けない状態。別々で管理できれば待つことなくそれぞれに1つずつ振り分けることが可能ですができないということです。また在庫管理したことがある方はイメージつくと思いますが、在庫の考え方として実在庫理論在庫の2種類用意して運用しているところが多いと思います。

  • 実在庫 : 実際に存在している在庫数
  • 理論在庫 : 購入などによる在庫を確保可能な数

この実在庫と理論在庫の関係上、在庫を確保後に次の作業を開始することは可能ですが在庫が同一の場所である時のみ運用成立できます。在庫が別々にある場合は、在庫管理を分ける必要があります。

複数エリア在庫管理

ということから、在庫を分担する仕組みを導入しました (複数エリア在庫管理)
在庫管理のSaasの拠点別在庫管理できるサービスを参考にし、拠点内で複数在庫管理させたい場合、拠点が複数ある場合両方に対応できるようにしたい思い、以下のような構成しました。決して、DBを分けマイクロサービスにすることはせず、1つのDB内で別々で管理できるようにしてます。

entity 拠点 {
id -- comment '拠点のid'
}

entity エリア {
id -- comment 'エリアのid'
}

entity 在庫拠点ーエリア {
----
拠点_id -- comment '拠点のid'
エリア_id -- comment 'エリアのid'
}

entity 在庫 {
----
在庫拠点ーエリア_id -- comment '在庫拠点ーエリアのid'
}

拠点||..|{在庫拠点ーエリア
エリア||..|{在庫拠点ーエリア
在庫||..|{在庫拠点ーエリア

f:id:snaqme-labs:20220301192615j:plain

  • 拠点 : 物理的に離れている (都道府県)
  • エリア : 同じ建物内に複数存在 (フロア別や部屋別)

ざっくり上記のようなER図で開発し、現在拠点は1つですが、エリアはすでに複数運用しています。

まとめ

複数エリア在庫管理を導入する前は毎日のようにエンジニアが何かしら修正をしたり、運用方法の注意点を説明したりなど、本質的じゃない動きをよく行なっていました。 オペレーション側も注意する項目が多く、神経を費やし心理的安全性は決して高い状態ではありませんでした。
一見同じおやつが別々のエリアに存在し複雑そうに見えていますが、実運用上あるおやつが全体で何個あるかは把握せず、そのエリアに何個あるのか把握できる大事なので、運用上の心理的安全性・ミス・出戻りなどはかなり減ってきました。
また、具体的に 単一エリア在庫管理から複数エリア在庫管理にどのように変更したのかは別の機会で共有させてもらえればと思います。
スナックミーではユーザーさんが利用する開発 (体験向上)以外に、今回の在庫管理を含めたWMSなども内製していますので、さまざまな観点から技術的チャレンジハードからソフトまでプロダクト開発が可能です。

より詳しいこと聞きたい方・一緒に働きたい方は 以下のmeety or twitterから連絡お待ちしてます。

meety.net

twitter.com

ユーザーさんにおやつを届けるまでのアーキテクチャ

こんにちは スナックミー CTO の三好 (@miyoshihayato) です
登壇などでユーザーさんにおやつを届けるまでのアーキテクチャを紹介させてもらったことはありましたが、ここではなかったので改めて紹介させていただきます。 過去紹介しているものからいくつかアップデートもしているので、その辺りも一緒にご紹介させていただければと思います。

snaq.meとは

おいしいマルシェおやつとワクワクを詰め込んでハッピーな「おやつ体験」をお届けします

農家さんが大切に育てたこだわりの素材が、生産者さんの手で美味しいおやつに。全国の生産者さんとお客さまを繋げ、体と心に優しく美味しいおやつを、ワクワクと共に届けます。おやつの時間を価値あるものに、人々の心と暮らしを豊かにします。

ユーザーさんにおやつを届ける概要

アサイン : ユーザーさんに届けるおやつを決めること

ユーザーさんのアクション(評価やリクエスト、好き嫌いなど)から弊社の独自のアルゴリズムを用いてアサインをしています

・評価: 4段階 (苦手~大好き)
・リクエスト: 必ず入るとは限らないが「このおやつ食べたい!」という意思表示
・好き嫌い: 苦手な原料や好きなジャンルなど

f:id:snaqme-labs:20220201194831j:plain

アサインはサービス当初全て手作業で行っていました。評価は当初からできるようにしていたので、各々の評価データを1件ずつ確認しながらアサインをしていました。さらにアサインするためにどの商品がアサイン可能なのか、どういう組み合わせがいいのかは担当者に依存するため属人性の高い業務でした。そのため当初からアサインの自動化はサービス当初を急務として開発を進めサービス開始3ヶ月でβ版の導入しました。
(ユーザーが増えれば増えるほどアサイン業務に時間が埋め尽くされるため。
ex) 1人1分で100人行うには100分、500人もやろうと思えば8時間強の業務)

アサインを支えるアーキテクチャ

f:id:snaqme-labs:20220207164054p:plain

① : アサインするおやつの順番決め
② : アサイン実行環境
③ : ユーザー情報の格納

① : アサインするおやつの順番決め

ユーザーごとに好き嫌いが異なるので、アサインした方がいい順番も異なってきます。そのため、ユーザーとおやつに点数をつけていくところになります。点数が高ければ、好きな可能性が高く、低いと好きな可能性が低いという感じです。

② : アサイン実行環境

アサインを実行環境であり、基本的にここは2パターンで実行されます。

  • 基幹システムからの実行
  • 定刻での自動実行

ここの実行環境ではアサイン登録はされず実行と結果のファイルだけ出力される仕組みになってます。また、弊社の独自のアルゴリズムが存在し、ユーザーごとに8品の商品をアサインを可能にしています。アサインできる条件はいくつかあり、実際にアサインを実行する前にさまざまな前処理を行なっています。例えば、在庫がなく棚卸しさせるおやつや製造しアサイン可能であればロケーションidを採番したりなどです。

ロケーションid: おやつがどの場所に存在させるか振る作業
在庫があれば全てアサイン可能ではなく、限られたSKUのもアサインをしています
常時100種類ほど

③ : ユーザー情報の格納

ここに関しては以下をご覧ください

labs.snaq.me

アサインの種類

  • snaq.me の定期
  • スイッチボックス
  • 追いシリーズ (翌日配送のsnaq.meやotuma.me)
  • otuma.me 定期
  • その他 (臨時対応)

当初はsnaq.meだけのアサインでしたが、さまざまなアサインのタイプにも対応できる仕組みになっています。(条件も異なっています。)

まとめ

アサインのシステムはver.1 -> ver.2 -> ver.3 -> ...とアップデートしてきました。このアップデートはユーザーさんの声やデータから導かれた課題に対して向き合い取り組んできたところになります。しかし、課題は山積みです。製造や出荷効率を上げつつ、ユーザーさんの満足度をあげるこの両輪をうまく回転させることで事業を推進していけると思っています。

より詳しいこと聞きたい方は 以下のmeety or twitterから連絡お待ちしてます。

meety.net

twitter.com

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

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

システムのシンプル化

非エンジニアにも向けて共有したので、シンプルと言う言葉にしてみました。
なぜシンプル化させたいかというと、サービス始めると様々な仕様が生まれては消えていき、相関してコードも増えていきます。言い換えるとプロダクト開発をメインで進めていくと様々な技術負債が生まれてきます。
技術負債とはコードやアーキテクチャだけの話ではありません。
例えば、「サービス当初は簡単でわかりやすかったが、様々な機能が増え、気づくとよくわからない」。
もしくは「以前はこの設定で動作していたのに、今はうまく機能してない・機能してなかったっけ?という、一つの機能の影響度が不透明」。このような状態はよくあると思います。
前者はいい意味で捉えるとサービスを使う用途は人によってそれぞれなので、必要な機能と不要な機能というのは存在してくることはあると思います。(100%いいとは限らない)
一方で、後者はチームで混乱を招く可能性があります。新しい仲間が入った時の学習コストが増加したり、本質的ではないコミュニケーションが発生したり(多方面から○○は□□すれば良いですか?と言うのな同じような質問など)、小さいミスが多発したりなどです。
非エンジニアにとってシステムはいくら内製化しても
仕様 = 守るもの
と捉えがちです。あながち間違ってないですが、足りないものに付け足す依頼はできても、マストな項目が多い場合、多いというのとに関しては気づきにくいものです。なぜなら、一つの機能に対して引き算していくことはその機能の周りにある関係性が見えにくいためだと考えています(必要だと思ってしまう)。また、人はペインに改善を求めますが、重要ではなく、複雑すぎるシステムは思考停止になりかねません。
そこで無意味な制限を極力なくすことで、システムのつながりがクリアにしたい。
このような技術的負債を定期的に見直す必要があると考え、今年のテーマとして取り掛かっていきたいと思っています

システムが原因で各々のチームをチェーンのようなもので身動きしずらい状態にせず、動きやすいゴムのようなものに変えていきたいとイメージです

シンプル化にすることでのメリット、デメリットは
メリット

おもしろいことがしやすくなる => できることの幅が広がる
チャレンジがしやすくなる => ユーザーさんの満足度 =>会社・個の成長
チーム内の意思決定が早くなる => スピード感が生まれる / 次に活かしやすい

デメリット

個々が好き勝手でき、カオス状態になる可能性がある

会社を成長させていくためにはこの状態が必要だと考えているので、今年のエンジニアチームのテーマとしています。

データドリブン

「完璧なデータ分析基盤を作り、KPIから事業を促進していくぞ」みたいなことをしたいのではなく、取れてないデータを把握・残し、施策を打つときに何を確認し、トレードオフが起きていることろは何かなどしっかり考察できる状態にしていきたいと考えています。
改めて現状のKPIを一つ一つ見直し、インパクトがあるところ、トレードオフになっているところを把握できる状態にしていきたいと考えています。
というのも今年の3月でサービス開始丸6年たちあらゆるデータから施策など打ってきました。
その中でできていない施策、施策の因果関係、見ているデータの粒度など、整っていない箇所も多くあり、データを扱えるレベルを1段階2段階高めていきたいと考えています。

相手を理解していこうとする意識

物事を進めていくには必要な観点だと思っています。
弊社は、製造・出荷、商品開発 (定期便、ストア)、パティシエ、マーケ、CS、デザイン (オフライン、オンライン)など多岐に渡るメンバーが多く存在しています。
さまざまなメンバーがいるので、少しでも理解していくことがプロダクトを1段階、2段階前に進めることができると考えてます
一次情報が大事な話と一緒で、会社内でも自分たちの領域外の情報・思考をしっかり自分の目・体で感じる必要性があると思います。

例えば「Aができないので、Aが欲しいから作って欲しい」 この観点だけ見るとAが必要だから、Aを作る。理由もAのように聞こえる。しかし、必ずしもAが必要なのではなく、BCを行うことで解決できることは多々ある。
しかし、最初からBCの案が出るわけではなく、事業のドメイン知識やそのポジションの考え方を把握する必要があります。
エンジニア同士でもAを作ることできても、コード安全性、汎用性、データの冪等性などは別の話と近いかもしれない

まとめ

今年は昨年の動きや振り返りから個・チーム・会社全体として1段階、2段階スケールアップしていけるような取り組みをして、スナックミー全体をさらに促進していきたいと思います。

上記の3つの中で「システムのシンプル化」をマイクロサービス化と書かなかったのはマイクロサービスを目的としないようにしたいという意味を込めています。たまに手法を目的・目標としてプロダクト開発する時がありますが、目的はユーザーさんの満足度を上げることだと考えているので、チームとしての目標に掲げませんでした。
しかし、エンジニアは新古知新の姿勢が大事なので、現状に満足するとすぐついていけなくなるのは事実です。そのためにチームで補いながら開発していきたいと思います。

今年もよろしくお願いします。新しい仲間もお待ちしております。

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

meety.net

engineers.snaq.me