1. Карта папок — це скелет, який не рухається
Після інвентаризації та мапи залежностей здається, що проєкт зрозумілий. Це ілюзія: ви бачите скелет, а скелет не рухається. Статична карта відповідає на запитання «що є в проєкті», 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/. Тепер — маршрут запиту: де входить, через які сервіси проходить, де зупиняється, які налаштування впливають, які тести утримують поведінку. Перед вами вже не репозиторій, а система, у якій можна працювати за слідами реального виконання коду.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ