Mealance ~コンビニでも健康に生きる~

https://github.com/TadokoroYuki/konbini-navi/tree/main

Go

TypeScript

Kubernetes

PostgreSQL

コンビニ食品の栄養バランスを管理するモバイルアプリ

warisuno

zatu

u8cae6sn7q8

1ie2qtmlmn8

推しアイデア

自分の食べたものを記録することでQOLが超向上する!?!?!?!? 詳しくはTopa'zを読みましょう

作った背景

コンビニを美化したかったから

推し技術

EKS + ALBでマイクロサービスアーキテクチャ gRPC! ECSと サーバーレスは !!!甘え!!!

プロジェクト詳細

概要

本アプリは、コンビニ利用が多い若者を対象に、食事の栄養バランスを簡単に管理できるモバイルアプリです。 ユーザーが購入した商品を記録することで、タンパク質・脂質・炭水化物のバランスを可視化し、不足している栄養素を補う商品を提案します。 忙しい日常の中でも、健康と利便性を両立した食生活を実現することを目的としています。

技術

アーキテクチャ図

image

インフラ

技術概要

・ GitOps ・ 簡単なCIをGitHub Actionsで実行 (PRなどで確認) ・ CodePipeline → CodeBuildでcdk deployとECR更新 ・ CodeConnectionsでAWSとGitHubを接続 ・ Image UpdaterでECRの変更を検知して、ArgoCDでEKSを更新 ・ ECRの認証情報取得のためのcronジョブも立てている

・ EKS ・ マイクロサービス(詳細はbackendで) ・ PostgreSQL

・ ALB ・ API Gateway ・ Amplifyからhttpsでしか通信ができず、ALBにリクエスト時Mixed Contentsエラーが発生したため、手前にAPI Gatewayを配置している

・ フロントエンド ・ AmplifyにExpo Webデプロイ ・ コンソール上で自動デプロイ(簡単!だけどビルドは時間かかる) ・ Cognito認証

設計ミスでめちゃめちゃめちゃめちゃ困った

今回のハッカソンでは、フロントエンドとバックエンドの並行開発を止めないために、**「API仕様書(OpenAPI)を絶対の契約(Single Source of Truth)とする」**ルールを徹底しました。

しかし、この「仕様書通りに作る」という方針が、インフラ構築において思わぬ壁となりました。

仕様書で定義された /v1/users/{userId}/records のような動的パスを、K8sのマイクロサービスにルーティングしようとした際、Kubernetes標準の Ingress (pathType: Prefix) では、パスの中間にワイルドカード(*)を使用できないという厳格な制約に衝突したのです。

K8sの制約を「AWS ALBの仕様」で突破 インフラの都合でAPI仕様書を書き換えてチームに混乱を生むわけにはいきません。 そこで私たちは、K8sの制約を回避するため、AWS Load Balancer Controller の pathType: ImplementationSpecific という設定を採用しました。

バックエンド

概要

今回、Golangでマイクロサービスアーキテクチャで実装を行いました。 サービスとしては、

・ Products ・ Records ・ Nutrition ・ Recommendations

となっています。 各サービスはgRPCを用いて互いに通信をしています。

レコメンデーション機能

レコメンデーション機能では16次元のベクトルを作成して、コサイン類似度を用いて現在のユーザーが食べるべきものを推定できるようにしました。

1サービスにつき1テーブルを基本として持たせています。

Recommendations機能では、Productsに栄養に関する数値6個、好きな食べ物の種類に関する真偽値(0と1で表現している)10個を一つの配列に持たせて、すべての項目が均等に評価されるようにしました。 さらに、ユーザーのニーズも同じように評価することにしました。

これをどちらもベクトルとして認識し、コサイン類似度で現時点でのユーザーに必要なProductsを数値で推定しています。

現状の比較だと栄養観点より好きな食べ物の種類のほうに引っ張られてしまうため、栄養と好きな食べ物が 1:1で評価されるように重みづけをしています。

各カラムについては以下に記述しています https://lapis-room-606.notion.site/32a6431e4386808fb769d1753642752a?source=copy_link

gRPCの苦労等

バックエンドは今回一人でやっています。 gRPCを実装したことが初めてでめちゃくちゃ難しかったです。 コーディングの部分はどうにかなった感はあるのですが、設計を最初にしとけばよかったなと感じました。

今回設計を頭の中で軽く考えて即実装を開始したのですが、途中から ある日のざつのひとくん「RecommendationsサービスはProductsテーブルの全件を取得しなきゃじゃん!!!もーーーーーー!!」 「Productsはuser_idでフィルタをかけて取得するものしか実装してないよ!!!!!」 というようなことがありました。 設計をイベントストーミングなどでやっておけばこんなことは起こんなかったのに、、と後悔しています。

フロントエンド

フロントエンドでは、ユーザーが簡単に食事を記録し、栄養バランスを直感的に把握できる体験を設計しています。

ホーム画面ではその日の栄養状態を表示し、次に選ぶべき商品を提案します。

記録画面ではコンビニ商品を入力することで食事データを蓄積します。

履歴画面では過去の食事を振り返ることができます。

おすすめ画面ではその日の栄養バランスに寄り添ったおすすめ用品を提案します。

シンプルで見やすいUIにより、忙しいユーザーでも継続的に利用できる設計としています。

今回の開発では、健康管理アプリの継続利用率を高めるための UI/UX改善に取り組みました。具体的には、健康スコア表示、カレンダーによる可視化、レシート撮影導線などを対象に、既存機能との整合を保ちながら試作・実装を進めることにチャレンジしました。苦労したことは recommendations 周りで、OpenAPIと実装レスポンスの不整合があり、互換対応を行ったことです。

チーム開発

Notionを使ってチームの情報ハブを集約しました。 MTG議事録や開発前のToDoやアイデアを集約することで、開発前部分の情報がまとまり、同じコンテキストの状態からチームで開発することができました。

奇跡的に、今回のチーム全員、得意な領域が違いました。 チーム一人ひとりが自分の担当する部分を広く持っています。

今回、docs(.claudeや、設計書)やopenapi.ymlをリポジトリに配置することで、これを絶対ルールとして掘り下げるような開発の仕方を進めました。

例. ・ frontend: UI/UX改善と機能追加 ・ backend: API内部ロジックの拡充 ・ infra: ALBのIngress

各領域(front/back/infra)で3人 + PdM(1人)を立てて、チームのタスク管理をしていただき、基本的にはIssue駆動開発をしていました。 ・田所: PdM, PjM ・川村: フロント ・わりすの: インフラ ・ざつ: バックエンド

warisuno

@MountWario