Backend-платформа FoodSea для iOS-приложения: каталог продуктов, сравнение цен по магазинам, оптимизация корзины и оформление заказа.
Проект организован как монорепо с несколькими сервисами и общими protobuf-контрактами.
| Сервис | Технология | HTTP | gRPC | Роль |
|---|---|---|---|---|
core-service |
Go | :8081 |
:9091 |
Пользователи, каталог, офферы, корзина, поиск, barcode, изображения |
optimization-service |
Go | :8082 |
:9092* |
Оптимизация корзины, работа с аналогами, хранение optimization snapshots |
ordering-service |
Go | :8083 |
:9093 |
Заказы и Saga-оркестрация |
ml-service |
Python | — | :50051 |
Поиск аналогов по векторной близости |
* В make dev-all локально optimization gRPC запускается на :9094 (чтобы не конфликтовать с Kafka на :9092).
- iOS клиент работает с backend по HTTP (через
core/optimization/ordering). - Внутри backend сервисы общаются по gRPC (
core↔optimization↔ordering↔ml). - Kafka используется для событий и оркестрации long-running сценариев:
cart.events(инвалидация результатов оптимизации при изменении корзины),optimization.events,order.events,saga.commands,saga.replies.
- Каждый Go-сервис владеет своей БД (Database-per-Service):
core_db,optimization_db,ordering_db.
- Clean Architecture внутри Go-модулей.
- Domain-слой не зависит от Gin/Ent/Kafka/gRPC.
- Деньги только в
int64(копейки), безfloat. - Sentinel errors +
errors.Isдля межслойной классификации ошибок. - Cache-aside: Redis не источник истины.
- At-least-once обработка Kafka сообщений, идемпотентные обработчики.
ml-service строит и обслуживает in-memory KNN-индекс аналогов товаров.
- Данные берутся из
core-serviceпо gRPC (CatalogService.ListProductsForML). - Прямого доступа к SQL у
ml-serviceнет.
Для каждого товара строится объединённый вектор из:
- текстового эмбеддинга (
sentence-transformers, модельall-MiniLM-L6-v2) поname + description + composition; - нормализованных nutrition-признаков (
calories/protein/fat/carbohydrates); - one-hot категории;
- нормализованного веса/объёма (парсинг строк вроде
500 мл,1 кг); - нормализованного минимального price-признака.
Все компоненты взвешиваются через env-параметры (TEXT_WEIGHT, CATEGORY_WEIGHT, NUTRITION_WEIGHT, PRICE_WEIGHT).
- Базовый поиск:
NearestNeighbors(metric="cosine", algorithm="brute"). - Режим
price_aware=trueкорректирует score относительно цены исходного товара:- более дешёвые аналоги получают бонус,
- более дорогие — штраф.
filter_store_idsограничивает кандидатов товарами, доступными в выбранных магазинах.- Результаты ниже
MIN_SCORE_THRESHOLDотбрасываются.
- При старте
ml-serviceпытается загрузить сериализованный индекс (INDEX_PATH). - Если файла нет, сервис загружает каталог из
core, строит индекс и сохраняет его на диск. - Если
coreвременно недоступен и индекса нет, сервис стартует с пустым индексом (возвращает пустые результаты, не падает).
optimization-service реализует алгоритм в optimizer/algorithm.
- Подготовка входа:
- цены по магазинам,
- условия доставки,
- кандидаты аналогов.
- Сокращение пространства поиска:
- используются только магазины, где есть товары корзины;
- если магазинов слишком много, выбираются top-15 по покрытию корзины.
- Полный перебор подмножеств магазинов:
- для каждого subset выполняется жадное назначение товара в минимальную цену внутри subset;
- неподходящие subset (не покрывают всю корзину) отбрасываются.
- Multi-move consolidation:
- система пробует переносить позиции между магазинами,
- цель: достичь
free deliveryтам, где это выгоднее, и уменьшить итоговую суммутовары + доставка.
- Этап аналогов:
- поверх оптимального распределения предлагаются замены (
substitutions), - учитывается и ценовая дельта, и изменение доставки (включая cross-store сценарии).
- Timeout fallback:
- при
context deadlineвозвращается лучший найденный результат сis_approximate=true.
В snapshot сохраняются:
assignments(что и в каком магазине брать),delivery_kopecks,total_kopecks,savings_kopecks,substitutions(выгодные аналоги),- статус (
active,locked,expired) для сценариев ordering Saga.
| Инструмент | Почему выбран |
|---|---|
gin |
Быстрый HTTP-слой с простым middleware pipeline и удобной интеграцией со Swagger |
ent |
Type-safe ORM + строгие схемы + предсказуемая генерация кода |
atlas |
Версионируемые SQL-миграции, удобная связка с Ent |
grpc + protobuf |
Строгие межсервисные контракты и компактный транспорт |
kafka-go |
Простой и стабильный Kafka-клиент в Go без лишней инфраструктуры |
go-redis/v9 |
Стандартный де-факто клиент Redis для cache-aside сценариев |
golang-jwt/jwt |
Прозрачная и контролируемая JWT-аутентификация |
swaggo |
Генерация OpenAPI/Swagger из кода handler-ов |
| Инструмент | Почему выбран |
|---|---|
sentence-transformers |
Качественные готовые эмбеддинги для семантической близости товаров |
scikit-learn (NearestNeighbors) |
Надёжный и понятный KNN для небольшой/средней размерности данных |
numpy |
Быстрая векторная математика для сборки и нормализации признаков |
grpcio |
Нативная и стабильная интеграция Python-сервиса в gRPC-контур Go-сервисов |
pytest |
Быстрые модульные проверки feature-builder/index/service логики |
| Компонент | Почему выбран |
|---|---|
| PostgreSQL 16 | Надёжная транзакционная БД для core/ordering/optimization |
| Redis 7 | Быстрый кэш и хранение TTL-данных |
| Kafka (Confluent) | Событийная шина и поддержка Saga-аудита |
| MinIO (S3-compatible) | Локальный и предсказуемый storage для изображений |
| Docker Compose | Быстрый локальный запуск зависимостей без k8s-overhead |
| Kubernetes | Deployment-манифесты, HPA, Ingress и stateful-компоненты для кластерного запуска |
| Testcontainers | Интеграционные/e2e тесты в условиях, близких к production runtime |
services/
core/ # API каталога/корзины/партнёров + gRPC Cart/Offer/Catalog
optimization/ # API оптимизации + gRPC OptimizationService + cart.events consumer
ordering/ # API заказов + Saga orchestration
ml/ # Python gRPC analog service
proto/ # protobuf контракты (общий Go-модуль)
deploy/ # docker-compose, init topics, k8s manifests
docs/api/ # экспортированные API-артефакты
scripts/ # вспомогательные скрипты (в т.ч. swagger regression)
make toolsmake dev-infra-upmake migrate-core
make migrate-optimization
make migrate-orderingmake proto
make swaggerВариант A (всё сразу, в фоне, включая ml-service):
make dev-allВариант B (по отдельности):
cd services/core && go run ./cmd/api
cd services/optimization && go run ./cmd/api
cd services/ordering && go run ./cmd/api
cd services/ml && make run- Core health:
http://localhost:8081/health - Optimization health:
http://localhost:8082/health - Ordering health:
http://localhost:8083/health
Swagger UI:
http://localhost:8081/swagger/index.htmlhttp://localhost:8082/swagger/index.htmlhttp://localhost:8083/swagger/index.html
make test-unit
make test-integration
make test-e2e-core
make test-e2e-ordering
make test-e2e-optimization
make test-mlПолный прогон:
make test-allОбщие:
ENV,SERVER_PORT,GRPC_PORTDB_URL,REDIS_URL,KAFKA_BROKERSJWT_SECRET
Optimization-specific:
CORE_GRPC_ADDR,ML_GRPC_ADDROPTIMIZATION_TIMEOUT,RESULT_TTL
Ordering-specific:
CORE_GRPC_ADDR,OPTIMIZATION_GRPC_ADDRSAGA_STEP_TIMEOUT,SAGA_MAX_COMPENSATION_ATTEMPTS
ML-specific:
CORE_GRPC_ADDR,GRPC_PORT,INDEX_PATHTEXT_MODEL,TEXT_WEIGHT,CATEGORY_WEIGHT,NUTRITION_WEIGHT,PRICE_WEIGHT,PRICE_PENALTY,MIN_SCORE_THRESHOLD
make build
make lint
make seed-core
make stop-all
make dev-infra-downВ репозитории есть готовые манифесты в deploy/k8s:
- namespace
foodsea, - Deployments/Services для
core,optimization,ordering,ml, - StatefulSets для Postgres, Redis, Kafka/Zookeeper,
- HPA для Go-сервисов,
- Ingress (
deploy/k8s/ingress.yaml).
- настроенный Kubernetes-контекст (
kubectl config current-context); - доступный Ingress Controller (манифест использует
ingressClassName: nginx); - локальные образы
foodsea/*:latestдолжны быть доступны кластеру.
make k8s-deployТаргет выполняет:
docker buildдля всех сервисов;kubectl apply -f deploy/k8s/namespace.yaml;kubectl apply -f deploy/k8s/ -R.
make k8s-status
kubectl get pods,svc,ingress -n foodseaЛоги конкретного сервиса:
make k8s-logs SVC=core-service
make k8s-logs SVC=optimization-service
make k8s-logs SVC=ordering-service
make k8s-logs SVC=ml-service- Через Ingress host:
api.foodsea.app(смотри правила вdeploy/k8s/ingress.yaml). - Для локальной проверки без DNS можно использовать port-forward:
kubectl port-forward -n foodsea svc/core-service 8081:8081
kubectl port-forward -n foodsea svc/optimization-service 8082:8082
kubectl port-forward -n foodsea svc/ordering-service 8083:8083make k8s-downЕсли меняется API-контракт (proto/*.proto), обязательно запускай make proto и проверяй совместимость вызовов между сервисами.