Rails for backend of fresh EC platform "Cookpad Mart"

Rails for backend of fresh EC platform "Cookpad Mart"

2020.05.13に開催された「オンライン開催!【シューマイ】Tech Lead Engineerから最新技術を学べ!Rails編」で話した「生鮮ECプラットフォームの
バックエンドを支えるRails」についてのスライドです。

2da0f25da3bb542a8724475589ac79a3?s=128

Ryo Katsuma

May 13, 2020
Tweet

Transcript

  1. 生鮮ECプラットフォームの バックエンドを支えるRails クックパッド株式会社 買物事業部部長 勝間 亮

  2. •勝間 亮 (かつま りょう) @ryo_katsuma •2009~ クックパッド入社 ‣レシピ領域バックエンドエンジニア ‣レシピ領域マネージャー •2018~

    買物事業領域立ち上げメンバー ‣副部長 兼テックリード ‣2020~ 部長
  3. None
  4. クックパッドマート •2018年〜買物領域の新規事業 •生鮮食材のEC ‣ 半分正しい、半分正しくない

  5. クックパッドマート •インターネットで生鮮食材を扱う上での課題 1. 生鮮食材の流通の課題 2. 生鮮ECの課題

  6. 生鮮食材の流通の課題

  7. これまでの流通網 生産・出荷組合 ¥120 市場・直売所 ¥130 仲卸・問屋 ¥150 小売店 ¥150〜198 生産者

    ¥50~80 • お届けまで数日〜数週間 • 中間業者が通るたびに利益が乗る構造 • 生産者の手取りは小売価格に比べて少ない
  8. 解決アプローチ クックパッド マート ¥120〜150 生産者 ¥80~100 •当日集荷、当日配送 • 生産者直売で地域で一番安く買える(= 生産者の手取りを増やせる)

    • 生産者直売でお届けまでのリードタイムが短い(= 新鮮なままお届け)
  9. 生鮮ECの課題

  10. 現状の生鮮EC •個配の配送コスト問題 ‣ 多くのECサービスは最低注文金額3,000~5,000円 ‣ まとめ買いをせざるをえない •再配達問題 ‣ 肉・魚などナマモノは宅配Boxや置き配を適用しづらい ‣

    お届け時間に必ず家にいないといけない制約
  11. 解決アプローチ •敢えて個配をしないピックアップ型EC ‣ 生活動線上に受取り場所を作って自分で好きな時間で取りに行く ‣ 再配達問題を解決 •集荷配送コストの圧縮 ‣ 複数人の注文をまとめて配送することで配送コストを1/N ‣

    生産者の集荷場所も一元化することで集荷コストも1/M ‣ 最低注文金額を0円に
  12. None
  13. None
  14. クックパッドマート •生鮮ECサービス •生鮮ECプラットフォーム

  15. クックパッドマート •生鮮ECサービス •生鮮ECプラットフォーム

  16. クックパッドマートの バックエンド

  17. バックエンド •Rails 6.0.2 •Ruby 2.6

  18. 可能なかぎり素朴なRailsに •MVC以上のレイヤーは極力増やさない ‣ app/services • 複数Modelをまたがる処理 ‣ app/resources • RESTful

    APIレスポンスのClassを定義するModelのラッパー
  19. モノリシックなサービス ‣ ユーザー向けモバイルアプリAPI ‣ 販売者向けWebアプリ ‣ ドライバー向けモバイルアプリAPI ‣ スタッフ向け管理Webアプリ ‣

    ハードウェアキッティング会社向け管理Webアプリ
  20. モノリシックなサービス •新規事業なので変化がかなり大きい ‣ サービスをまたいだ実装を単純化 ‣ アプリ分割によるModel層の管理の煩雑化を避けたい (DBは共通) •必要になればサービス分割を検討 •必要な外部サービスは適宜利用 ‣

    決済: Stripe / Push通知: Firebase
  21. 実装も素朴 •の、ように見える

  22. 実装も素朴? •の、ように見えるかもしれないが。。。

  23. 現実世界を反映した 設計と開発

  24. 物体と概念のmodel設計

  25. 受け取り場所(マートステーション)

  26. モデル設計 • ステーションは複数の冷蔵庫を • 冷蔵庫は複数のコンテナを • コンテナは複数の注文商品を • 注文商品は商品にひも付き

  27. モデル設計 • Location has_many LocationFridges • LocationFridge has_many LocationFridgeContainers •

    LocationFridgeContainer has_many OrderItems • OrderItem belongs_to Item
  28. モデル設計 • Location has_many LocationFridges • LocationFridge has_many LocationFridgeContainers •

    LocationFridgeContainer has_many OrderItems • OrderItem belongs_to Item
  29. コンテナの内容は日毎に変わる 「ある日のコンテナ状況」を再現するときにSQLだけで解決したい

  30. モデル設計 • Location has_many LocationFridges • LocationFridge has_many LocationFridgeContainers •

    LocationFridgeContainer has_many DeliveryLocationFridgeContainers • DeliveryLocationFridgeContainer has_many OrderItems • OrderItem belongs_to Item 5/10付
  31. モデル設計のポイント •現実世界のものをそのままモデリングしても駄目 •現実世界と結びつく「概念」をどう表現するか ‣ 例) モノは同一でも時系列で状況が変わるもの ‣ 表現をミスるとデータ取得が困難に

  32. モデル設計のポイント •とはいえ何度も設計はミスってる •DB設計ミスに気づいたら積極的に リファクタリング ‣ 作り直しは許容 ‣ 2度目はさすがにうまくいく

  33. 物理制約の設計

  34. 一般的ECの購入時チェック •在庫が存在するかどうか •ユーザー情報が正しいかどうか ‣ 決済情報、住所など

  35. 集荷からお届けまで

  36. 集荷冷蔵庫 集荷からお届けまで

  37. 配送車 集荷からお届けまで

  38. 受け取り用冷蔵庫 集荷からお届けまで

  39. 注文処理ではこれらのチェックが必要

  40. 受け取り冷蔵庫のキャパシティ •注文商品はコンテナに入る必要 ‣ order_items.sum(&:volume) 
 ɹ<= delivery_location_fridge_container.available_capacityɹ ‣ を満たすコンテナがあるかを確認

  41. 商品毎の重さや体積測定

  42. 配送車のキャパシティ •ステーションのコンテナが全て車に入る必要 •配送資材(シッパー)にコンテナを入れて配送 ‣ delivery_location_fridge_containers.size 
 <= distribution_routing_shipper.available_capacity ‣ を満たす車内のシッパーがあるかを確認

  43. 集荷用冷蔵庫のキャパシティ •集荷用冷蔵庫にコンテナが入る必要 •配送コンテナを設置する空き棚があるか ‣ CollectionSpotFridgeAddress
 .where.not(id: already_assigned_address_ids)
 .merge(collection_spot_fridge: collection_spot_fridges) ‣

    を満たす空き棚があるかを確認
  44. 採番処理 •注文された商品に ‣ どの配送先冷蔵庫のどのコンテナに入るか ‣ どの配送車のどの資材に入るか ‣ どの集荷元冷蔵庫のどの棚に入るか •の情報(= nanika_id)を付加する採番処理を行う

  45. 物理制約の設計のポイント •制約の実装は複雑になりがち ‣ 現実世界を表現すると仕方なし(と思ってる) •制約を採番処理と見立ててServiceClassにまとめる ‣ DeliveryLocationFridgeContainerAssignerService ‣ DistributionRoutingShipperContainerAssignerService ‣

    CollectionSpotFridgeAddressAssignerService •採番ができない場合は物理制約にひっかかったと見なしてraise
  46. 物理制約のValidation

  47. 一見、採番成功していても注意 •タイムセール施策 ‣ 継続率向上 •特定の商品への注文が短期間に集中 ‣ 同一データに対してread/writeが集中

  48. 実際にあった話 •注文時の採番処理は完了 •早朝にドライバーへの集荷指示データ作成時にraise •

  49. 実際にあった話 •注文時の採番処理は完了 •早朝にドライバーへの集荷指示データ作成時にraise •ドライバー稼働開始までにデバッグのタイムアタック

  50. None
  51. ここから9:00までにデータ不整合を直して ドライバーに指示データを作成しないと集荷が間に合わない

  52. 起きたこと •決済サービスへの通信も挟まり、transaction処理を 行いづらい •受け取り場所Aの注文データに対して 受け取り場所Aには巡らない配送車の資材IDが採番 ‣ データとしてはIDは採番されている ‣ 採番されるべきでないデータでコンテナが奪われる

  53. 要するにこうなっていた •DBのデータ的にはValidationはOK •物理世界を考慮したビジネスロジック的にはNG

  54. 対応方針 •定期データチェックバッチ •DBのデータが ビジネスロジック的に正しいか ‣ 未来の全注文データを定期チェック ‣ 最悪raiseしても落ち着いて対応

  55. まとめ

  56. まとめ •流通を愚直に表現すると ‣ 物理と概念を的確に紐付けた設計する必要 ‣ 物理世界の制約を表現する必要 ‣ ビジネスロジックを満たすデータかチェックする必要

  57. まとめ •愚直に実現することは(ご覧の通り)複雑 •が、それはそれで面白い ‣ 普通のRailsアプリではない技術的な楽しさ

  58. None
  59. https://www.wantedly.com/projects/300736

  60. ご清聴ありがとうございました