1. Карта папок — это скелет, который не двигается
После inventory и dependency map кажется, что проект понятен. Это иллюзия: вы видите скелет, а скелет не двигается. Статическая карта отвечает «что есть в проекте», runtime flow — «что происходит, когда система работает» (например при оформлении заказа в Commerce OS). Спутаете оптики — поправите не тот сервис и сломаете половину сценария.
| Вопрос | Статическая карта | Runtime flow |
|---|---|---|
| Где живёт логика заказов? | В модуле orders/ | попадает в контроллер, потом в сервис, потом в репозиторий и события |
| Какие модули связаны с оплатой? | orders/ зависит от payments/ | Оплата вызывается после резерва товара и до сохранения заказа |
| Что происходит, если товара не хватает? | Обычно карта не отвечает | Сервис прерывается, возвращается ошибка, запись в БД не происходит |
| Какой конфиг влияет на поведение? | Можно найти файл настроек | Можно понять, в какой момент настройки реально меняют путь выполнения |
Прочитав flow, вы перестаёте видеть «набор папок» и начинаете видеть систему.
2. Точка входа — не всегда main()
Сначала найдите, где сценарий начинается, — это entry point. Частая ошибка: считать, что точка входа всегда main(). В бизнес-приложении их много, каждая запускает свой тип поведения: заказ — HTTP-маршрут, пересчёт метрик по расписанию — фоновая задача, инициализация кэша — стартовая логика.
| Тип точки входа | Пример в Commerce OS | Где искать |
|---|---|---|
| HTTP-запрос | |
контроллеры, роуты, интеграционные тесты |
| Действие в интерфейсе | клик по кнопке «Оформить заказ» | React-компонент, клиент API, обработчик формы |
| Фоновая задача | пересчёт метрик dashboard | jobs/, планировщики, cron-настройки |
| Старт приложения | инициализация модулей | , конфигурация, startup listeners |
| Командный сценарий | импорт данных из файла | CLI-команда, runner, shell-скрипт |
Для оформления заказа серверная точка входа выглядит примерно так:
@PostMapping("/api/orders")
public OrderResponse create(@RequestBody OrderRequest request) {
return orderService.placeOrder(request); // первая серверная точка входа
}
Аннотации Spring наизусть знать не нужно — нужен принцип: кнопка → HTTP-запрос → метод контроллера, отсюда начинается путь данных внутри backend.
Прося Claude найти точку входа, формулируйте через конкретную пользовательскую историю:
Найди точку входа для сценария «покупатель оформляет заказ».
Покажи:
1. где начинается путь в интерфейсе или API,
2. какой маршрут принимает запрос,
3. какой метод вызывается первым на сервере,
4. какими файлами это подтверждается.
Если что-то не проверено, пометь это явно.
Правило: один runtime flow — одна история. Тяните в один запрос заказ, возврат и уведомления сразу — получите лапшу, а Claude начнёт обобщать там, где нужны следы.
3. Читайте цепочку вызовов, а не список файлов
После точки входа идёт call chain — упорядоченный путь от обработчика до результата. Без порядка это список файлов; с порядком видно, кто кого вызывает, где меняются данные, где возникают побочные эффекты.
Для сценария «оформить заказ» в Commerce OS:
flowchart TD
A[POST /api/orders] --> B[OrderController#create]
B --> C[OrderService#placeOrder]
C --> D[InventoryService#reserve]
D -->|товар есть| E[PaymentService#authorize]
D -->|товара нет| X[Ответ с ошибкой]
E -->|оплата успешна| F[OrderRepository#save]
E -->|ошибка оплаты| Y[Ответ с ошибкой]
F --> G[(PostgreSQL)]
F --> H[OrderPlacedEvent]
H --> I[notifications / jobs]
Схема даёт порядок: резерв раньше оплаты, оплата раньше сохранения, событие — только после записи в базу. И ветвления: нет товара — не идём в PaymentService; не прошла оплата — заказ не сохранить как успешный.
Центр цепочки часто живёт в одном сервисном методе:
public OrderResponse placeOrder(OrderRequest request) {
inventoryService.reserve(request.items()); // резервируем товар
paymentService.authorize(request.payment()); // пробуем оплату
Order saved = orderRepository.save(mapper.toOrder(request));
eventPublisher.publish(new OrderPlacedEvent(saved.id())); // побочный эффект
return mapper.toResponse(saved);
}
Побочный эффект — не то, что метод возвращает, а то, что меняет в системе: запись в базу, отправка события, уведомление, вызов внешнего платёжного провайдера. Именно они ломают систему неожиданнее всего.
Отмечайте и путь данных: OrderRequest (HTTP) → валидация → доменная модель Order → PostgreSQL → наружу OrderResponse. Не зафиксируете — запутаетесь, где входной JSON, а где внутренняя модель.
И не забывайте про конфиги — они незаметно меняют flow. Сервис оплаты выбирает реализацию по настройке:
app:
payments:
provider: mock-pay # какой провайдер оплаты будет вызван
orders:
max-items: 20 # ограничение на размер заказа
Цепочка без конфигов и переменных окружения — слишком гладкая. Реальная система всегда подчиняется настройкам, профилям и флагам.
4. Ветка ошибки важна не меньше основной
Типичная ошибка — проследить только красивый сценарий и закрыть задачу. Но код чаще ломается на ветках ошибок. Runtime flow без error path — половина картины, иногда меньше.
В оформлении заказа очевидны минимум две ветки: нет товара на складе и не прошёл платёж. Не понимаете, где они отрезают выполнение, — не понимаете систему. Перенесёте запись в базу раньше проверки оплаты — получите «успешные» заказы без оплаты. Такой баг приходит ночью.
Error path удобно проверять по тестам. Интеграционный тест подтверждает, что при нехватке товара контроллер возвращает конфликт:
@Test
void createOrder_returnsConflict_when_item_is_unavailable() throws Exception {
mockMvc.perform(post("/api/orders").content(невалидныйЗапрос()))
.andExpect(status().isConflict()); // 409, если товара не хватает
}
Смысл читается даже без знания тестового API: это доказательство для ветки ошибки — система завершает сценарий конкретно, а не «как-то ругается».
Здесь работает дисциплина из прошлой лекции: нет подтверждения error path в коде, тестах или конфигах — не дорисовывайте, пишите не проверено. Например: «не проверено, есть ли retry при частичном падении платёжного провайдера». Это хорошая инженерная запись. Слабость — когда модель придумала, а вы молча унесли это в inventory как факт.
При разборе flow держите четыре вопроса: где начинается основная ветка, где она может оборваться, какие побочные эффекты успевают произойти до ошибки и чем это подтверждается.
5. Запрашивайте flow с evidence
Спросите «как работает оформление заказа?» — Claude ответит уверенно и обобщённо. Читать приятно, работать неудобно. Нужен не пересказ архитектуры, а разбор потока с доказательствами. Помогает контраст слабого и сильного вопроса:
| Слабый запрос | Сильный запрос |
|---|---|
| Как работает заказ? | Проследи сценарий «покупатель оформляет заказ» от HTTP-маршрута до записи в БД и публикации события |
| Объясни модуль orders | Найди точку входа, цепочку вызовов, ветки ошибок, влияющие конфиги и тесты |
| Где тут логика оплаты? | Покажи, в какой момент flow вызывает оплату и что происходит, если она не прошла |
Шаблон запроса, который обычно даёт полезный результат:
Проследи поток выполнения сценария «покупатель оформляет заказ».
Покажи в ответе:
1. точку входа;
2. цепочку вызовов по файлам, методам и классам;
3. как меняются данные по пути;
4. какие побочные эффекты происходят;
5. какие конфиги или env-переменные влияют на поведение;
6. какие тесты подтверждают этот сценарий;
7. какие есть ветки ошибки;
8. что ты не проверил и какие предположения сделал.
Для каждого важного шага укажи файл или метод, на который ты опираешься.
Если уверенность средняя или низкая, пометь это явно.
Магии нет: вы заранее задаёте формат и запрещаете модели прятать неопределённость за красивыми формулировками.
После ответа — ручная проверка. Не перечитывайте все файлы, откройте две-три опоры: точку входа, центральный сервисный метод, тест на ошибку. Этот выборочный проход резко снижает риск унести в заметки красивую, но неверную историю.
И не просите разом «объяснить flow» и «предложить улучшения». Сначала установите, как система работает сейчас. Иначе Claude смешает факты с улучшениями, а вам потом разбирать, где описание, а где совет.
6. Перенесите flow в CODEBASE_INVENTORY.md
Разбор не должен остаться в чате. Динамический срез добавляем в тот же CODEBASE_INVENTORY.md. Отдельный RUNTIME_FLOW.md на первом проходе не нужен: карта проекта должна жить в одном месте. Хороший формат:
## Runtime flow — оформление заказа
Точка входа:
`POST /api/orders` → `orders/OrderController#create`
Цепочка вызовов:
`OrderController#create` → `OrderService#placeOrder`
→ `InventoryService#reserve` → `PaymentService#authorize`
→ `OrderRepository#save` → `OrderPlacedEvent`
Побочные эффекты:
- резерв товара
- запись заказа в PostgreSQL
- публикация события для уведомлений
Ветка ошибки:
- нет товара → ответ 409, заказ не сохраняется
- ошибка оплаты → заказ не подтверждается
Конфиги:
- `app.payments.provider`
- `app.orders.max-items`
Тесты:
- `OrderServiceIntegrationTest#placeOrder_happy_path`
- `OrderControllerTest#create_returnsConflict_when_item_is_unavailable`
Непроверено:
- retry при частичном падении платёжного провайдера
Формат короткий: inventory — не роман о судьбе заказа. Он отвечает на правильные вопросы: откуда flow начинается, через что проходит, где может оборваться, чем подтверждается — и честно хранит неизвестное.
Правило прежнее: одна секция — одна история. Несколько историй — несколько коротких секций, иначе inventory превращается в стену текста, которую все игнорируют.
Раньше вы видели папки orders/, payments/, jobs/. Теперь — маршрут запроса: где входит, через какие сервисы проходит, где останавливается, какие настройки влияют, какие тесты держат поведение. Перед вами уже не репозиторий, а система, в которой можно работать по следам реального выполнения кода.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ