sequenceDiagram
autonumber
actor User as Пользователь (Экран Книги)
participant APP as Мобильное приложение (Flutter)
participant RS as Сервис Рецептов (FastAPI)
participant DB as База Данных (PostgreSQL)
participant KAFKA as Брокер событий (Kafka)
participant CK as Контур Готовки (Cook)
User->>APP: Корректирует описание рецепта в интерфейсе
APP->>RS: HTTP PATCH /api/v1/recipes/custom/412 (JSON с новым описанием)
activate RS
note over RS: Шаг 3: Декодирование JWT (Извлечение home_group_id)
Note over RS, DB: Старт атомарной SQL-транзакции
RS->>DB: SELECT * FROM user_custom_recipes WHERE id = 412 FOR UPDATE
activate DB
DB-->>RS: Возвращает текущее состояние записи
deactivate DB
note over RS: Шаг 6: Проверка принадлежности к home_group_id группы
note over RS: Шаг 7: Pydantic-слияние (Экстраполяция только переданных полей)
RS->>DB: UPDATE user_custom_recipes SET description = \$1 WHERE id = 412
Note over RS, DB: Коммит транзакции (COMMIT)
RS->>KAFKA: send_to_kafka_async("masterdata.recipes.mutated.v1")
RS-->>APP: HTTP 200 OK (status: "UPDATED")
deactivate RS
note over KAFKA: Шаг 11: Асинхронная инвалидация кэша ИИ-аналитики
%% ДЕМОНСТРАЦИЯ ИНТЕГРАЦИОННОЙ СВЯЗИ
Note over CK, RS: Вызов эталона при запуске готовки пользователем
CK->>RS: gRPC GetRecipeIngredients(recipe_id=412)
activate RS
RS-->>CK: gRPC RecipeIngredientsResponse (Массив свежих данных)
deactivate RS
Метод PATCH /api/v1/recipes/custom/{id}
Документация API: Точечное обновление свойств авторского рецепта (PATCH) с частичной Pydantic-экстраполяцией
- INVENTORY-2XX Backlog — Краткое Описание задачи 1.
- INVENTORY-1XX Refinement (Уточнение) — Краткое Описание задачи 2.
1 Функциональное назначение
Метод предназначен для частичного (точечного) изменения структуры кастомного рецепта пользователем в рамках своей группы (home_group_id).
В отличие от тяжелого метода PUT, который требует полной перезаписи объекта, PATCH решает следующие задачи: 1. Экономия сетевого трафика (Low Overheads): Позволяет изменить только отдельные свойства рецепта (например, исправить опечатку в описании или заменить один текстовый шаг приготовления), не гоня по сети массив ингредиентов технологической карты. 2. Асинхронная инвалидация кэша: Автоматически уведомляет ИИ-контур аналитики КБЖУ об изменении метаданных рецепта через шину данных Apache Kafka. 3. Изоляция структуры: Сохраняет неизменными дочерние строки таблицы ингредиентов, если в JSON-пакете не передано поле ingredients, разгружая дисковую подсистему СУБД от лишних операций удаления и записи.
2 Протокол взаимодействия (HTTP Контракт)
- Метод:
PATCH - Маршрут:
/api/v1/recipes/custom/{id} - Формат данных:
application/json
2.1 Спецификация параметров пути (Path Parameters)
| Параметр | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
id |
Integer | Да | Уникальный ID изменяемого кастомного рецепта в базе группы | 412 |
2.2 Спецификация заголовков (HTTP Headers)
| Заголовок | Обязательный | Описание | Пример значения |
|---|---|---|---|
Content-Type |
Да | Формат передаваемых данных | application/json |
Authorization |
Да | Токен авторизации (Access Token) для проверки прав владения кулинарной книгой |
Bearer eyJhbGciOiJIUzI1Ni... |
X-Request-ID |
Да | Сквозной ID запроса для распределенного логирования | req-rec-patch-55bb |
2.3 Спецификация тела запроса (Request Body)
Все поля являются опциональными. Валидатор Pydantic на FastAPI модифицирует строго те ключи, которые физически присутствуют в переданном JSON-пакете.
| Поле | Тип | Обязательный | Описание | Пример значения |
|---|---|---|---|---|
title |
String | Нет | Новое текстовое название рецепта | "Бабушкин яблочный пирог v3" |
description |
String | Нет | Модифицированные кулинарные заметки к блюду | "Проверенный временем рецепт" |
cooking_steps |
Array | Нет | Обновленный массив текстовых инструкций | ["Новый Шаг 1...", "Шаг 2..."] |
2.3.1 Пример сырого JSON-запроса (Payload — Сценарий: пользователь исправляет только текстовое описание, не затрагивая состав):
{
"description": "Проверенный временем рецепт"
}3 Схема обработки запроса пользователя (Mermaid)
На диаграмме представлена логика частичного обновления (PATCH) кастомного рецепта. Сервис рецептов изолированно фиксирует изменения в PostgreSQL, после чего асинхронно публикует событие в брокер Apache Kafka. Смежные сервисы (ИИ-аналитика и Уведомления) подхватывают этот сигнал, а основной контур Готовки (Cook) при следующем вызове заберет обновленный эталон через gRPC.
4 Расшифровка шагов
- Шаг 1 (
User -> APP): Пользователь в кулинарной книге изменяет одно или несколько текстовых полей рецепта (например, переписывает поле описания"description", оставляя шаги и состав ингредиентов неизменными) и нажимает кнопку «Сохранить изменения». - Шаг 2 (
APP -> RS): Мобильное приложение Flutter через клиентDioотправляет запросHTTP PATCH /api/v1/recipes/custom/{id}. В теле JSON передается исключительно измененный ключ"description". В заголовках прокидывается Access Token авторизации и сквознойX-Request-ID. - Шаг 3 (
RS -> RS): Микросервис Рецептов (FastAPI) перехватывает запрос, валидирует и декодирует JWT. Из токена извлекается параметрhome_group_idдля проверки прав доступа к приватной кулинарной книге. - Шаг 4 (
RS -> DB): Сервис открывает атомарную SQL-транзакцию вPostgreSQLи блокирует целевую строку рецепта:SELECT id, home_group_id, description FROM user_custom_recipes WHERE id = $1 FOR UPDATE. - Шаг 5 (
DB -> RS): База данных возвращает текущую физическую структуру записи из таблицы кастомных рецептов. - Шаг 6 (
RS -> RS): Проверка контекста безопасности (Security Scope): Сервис сверяет полеhome_group_idиз базы данных сhome_group_id, зашитым в токене пользователя. Если идентификаторы не совпадают (попытка изменить рецепт чужой семьи), транзакция откатывается (ROLLBACK) с ошибкойHTTP 403 Forbidden. - Шаг 7 (
RS -> RS): Pydantic-экстраполяция: FastAPI берет структуру из базы данных и накладывает на неё входящий JSON-патч с помощью встроенного механизма частичного обновленияresponse_model.model_validate(existing_db_model, update=patch_data). Изменяются только переданные поля, остальные (включая массив шагов) сохраняют свои исходные значения. - Шаг 8 (
RS -> DB): Обновленный объект записывается обратно в базу данных:UPDATE user_custom_recipes SET description = $1, updated_at = NOW() WHERE id = $2. Транзакция успешно закрывается командойCOMMIT. - Шаг 9 (
RS -> KAFKA): Сервис Рецептов выступает в роли продюсера (Producer) и отправляет асинхронное системное уведомление в топик Kafkamasterdata.recipes.mutated.v1с типом события"RECIPE_UPDATED". Сообщение содержитrecipe_idиhome_group_id. - Шаг 10 (
RS -> APP): Сервер возвращает мобильному приложению статусHTTP 200 OKс подтверждением частичной модификации. Экран во Flutter разблокируется. - Шаг 11 (
KAFKA -> KAFKA): Асинхронное следствие: шина сообщений Kafka доставляет событие до ИИ-сервиса аналитики КБЖУ. ИИ сбрасывает (инвалидирует) старый кэш структуры рецепта для этой группы и перестраивает NLP-эмбеддинги. - Шаг 12 (
CK -> RS): Интеграционное взаимодействие (gRPC): Когда пользователь этой группы запускает метод фиксации готовки по рецепту (из микросервисаCook, Метод 4), транзакционный шлюз готовки обращается к сервису Рецептов через внутренний gRPC-канал:gRPC GetRecipeIngredients(recipe_id=412). Сервис Рецептов мгновенно возвращает актуальный эталон технологической карты по протоколу HTTP/2, изолируя основную базу холодильника от структуры хранения кулинарных книг.
5 Спецификация ответов сервера (Response Body) и ошибок
5.1 1. Успешный ответ (Success Response)
5.1.1 HTTP 200 OK (Ответ на Шаге 10)
Возвращается мобильному приложению после успешного коммита транзакции точечного обновления. В теле ответа фиксируются только измененные свойства и метаданные сессии.
- Заголовки ответа (Response Headers):
Content-Type: application/json
- Тело ответа (Response Body):
{
"status": "RECIPE_PARTIALLY_UPDATED",
"data": {
"recipe_id": 412,
"home_group_id": "group-772-alpha",
"updated_fields": {
"description": "Проверенный временем рецепт"
},
"timestamp": "2026-07-02T01:07:02Z"
}
}5.2 2. Спецификация интеграционного gRPC-контракта (Шаг 12)
Описание структуры Protocol Buffers (recipes_integration.proto) для внутреннего синхронного взаимодействия между бэкенд-шлюзом Готовки (Cook) и сервисом Рецептов.
syntax = "proto3";
package masterdata.recipes.v1;
service RecipesIntegrationService {
// Вызывается контуром Cook для получения эталона перед списанием продуктов
rpc GetRecipeIngredients (RecipeIngredientsRequest) returns (RecipeIngredientsResponse);
}
message RecipeIngredientsRequest {
int32 recipe_id = 1;
string home_group_id = 2;
}
message RecipeIngredientItem {
string ingredient_name = 1; // Каноническое имя продукта
double required_amount = 2; // Вес или штуки
string unit = 3; // Единица измерения ("кг", "шт")
}
message RecipeIngredientsResponse {
int32 recipe_id = 1;
string title = 2;
repeated RecipeIngredientItem ingredients = 3; // Массив для каскадного автосписания
}5.3 3. Вилки исключений и обработка ошибок
5.3.1 Ошибка: Попытка изменения глобального системного рецепта (HTTP 403 Forbidden)
Выбрасывается на Шаге 6, если переданный id принадлежит глобальной неизменяемой базе рецептов экосистемы (master_recipes), которые защищены от записи.
{
"error_code": "ERR-SYSTEM-RECIPE-IMMUTABLE",
"message": "Операция отклонена: базовые рецепты экосистемы защищены от записи и доступны только для чтения.",
"details": {
"requested_id": 12,
"table_scope": "master_recipes"
}
}5.3.2 Ошибка: Нарушение прав доступа к чужой кулинарной книге (HTTP 403 Forbidden — Шаг 6)
Выбрасывается, если home_group_id текущей сессии пользователя не совпадает с home_group_id владельца рецепта в базе данных.
{
"error_code": "ERR-RECIPE-ACCESS-DENIED",
"message": "Доступ заблокирован: у вас нет прав на редактирование рецептов чужой группы.",
"details": {
"reason": "Security scope verification failed for target recipe entity."
}
}