JavaRush /Курси /Java Server /Зовнішнє HTTP API в ReadLater Starter

Зовнішнє HTTP API в ReadLater Starter

Java Server
Рівень 14 , Лекція 0
Відкрита

1. Бекенд як HTTP-клієнт

Якщо дотепер бекенд уявлявся вам як «сервер, який лише приймає запити», то сьогодні ми трохи змінимо цю картину. У реальності бекенд-сервіс майже завжди живе в оточенні інших сервісів і API: у когось запитує дані, комусь надсилає інформацію, у когось уточнює деталі. І це цілком нормально.

Уявіть типовий робочий день будь-якого «звичайного» сервісу: він прийняв запит від користувача, потім звернувся до зовнішнього платіжного провайдера, потім уточнив дані доставки в логістичному сервісі, потім надіслав лист через поштовий API — і лише після цього повернув відповідь користувачу. Іноді складається враження, що бекенд насправді не «обробляє бізнес-логіку», а акуратно диригує чужими API. Ну, а диригент — теж професія, між іншим.

Щоб одразу визначитися з термінами, корисно розрізняти два напрями:

Напрям Що відбувається Хто «клієнт», а хто «сервер»
Inbound HTTP (вхідний) Хтось стукає до нас клієнт = користувач/браузер/мобільний застосунок, сервер = наш бекенд
Outbound HTTP (вихідний) Ми стукаємо до когось клієнт = наш бекенд, сервер = зовнішнє API/сусідній сервіс

Сьогодні нас цікавить саме outbound-частина: наш Java-застосунок буде надсилати HTTP-запит до зовнішнього API і отримувати відповідь. Тобто поводитися… як дуже серйозний Postman, тільки з характером Java і звичкою до суворих типів. Поки що без типізації — але Java вже морально готується.

2. Каталог книг для ReadLater Starter

У ReadLater Starter домен простий і людяний: особистий список «прочитати потім». Але щойно ви спробуєте зробити цей список хоч трохи зручним, виникає питання: звідки брати дані про книгу? І тут зовнішній каталог виявляється не «надбудовою», а цілком логічним джерелом інформації.

Можна, звісно, змусити користувача вручну вводити все: назву, автора, рік, видавництво і ще, бажано, «хто перекладач». Але тоді наш сервіс швидко перетвориться на застосунок «Угадай, як правильно пишеться прізвище автора», а користувач почне копіювати дані з інтернету вручну. Це і незручно, і ризиковано.

Зовнішній каталог книг у нашому проєкті відіграє роль джерела довідкових даних. На практиці це виглядає так: користувач вводить пошуковий рядок, наприклад clean code, ми надсилаємо запит до каталогу, отримуємо список результатів, користувач обирає книгу за зовнішнім ідентифікатором, а ми можемо отримати детальнішу картку. Навіть якщо згодом у нас з’явиться локальний список читання, каталог і далі лишається корисним як «довідник», який уміє знаходити книги швидше й точніше, ніж людина, втомлена після роботи.

І є один критично важливий методичний момент: ми вже дослідили контракт цього каталогу через Postman. Тобто сьогодні ми не «вгадуємо правильний URL». Ми робимо інженерно чесну річ: переносимо вже перевірений контракт у код. Це як спершу виміряти рулеткою, а потім різати, а не навпаки. У програмуванні, звісно, іноді ріжуть і навпаки, але ми спробуємо краще.

3. Локальний vs зовнішній HTTP-виклик

Усередині Java-програми ми звикли до дуже затишної моделі: викликали метод — отримали результат. Усе в одному процесі, один heap, один дебагер, можна поставити брейкпоінт і відчути себе володарем світу. З HTTP-викликом мозок намагається застосувати ту саму модель — і саме тут зазвичай починаються питання: «А чому воно не працює?».

Для порівняння, ось «звичайний» локальний виклик — усередині одного процесу:

// Локальний виклик: усе відбувається в нашому процесі, без мережі та HTTP-статусів
String result = localCatalog.findById("OL12345M");

// Тут ми друкуємо вже готовий результат (наприклад, рядок із назвою книги)
System.out.println(result); // (наприклад) Clean Code by Robert C. Martin

У цьому коді немає ні адреси, ні мережі, ні статусу, ні заголовків. Якщо метод зламається, ви, найімовірніше, швидко зрозумієте де саме: дебагер, стек-трейс — усе поруч.

А тепер — натяк на зовнішній виклик. Навіть не виконуємо його, просто подивіться на форму:

import java.net.URI;
import java.net.http.HttpRequest;

// Адреса зовнішнього API: це вже не "виклик методу", а конкретний URL/URI
URI uri = URI.create("https://catalog.example/books/OL12345M");

HttpRequest request = HttpRequest.newBuilder(uri)
        // Заголовок: явно кажемо, що хочемо отримати JSON
        .header("Accept", "application/json")
        // HTTP-метод: читаємо дані (а не змінюємо)
        .GET()
        .build();

Тут одразу видно «інший всесвіт». З’явився адрес URI, з’явився HTTP-метод, з’явився заголовок Accept. І головне: навіть якщо запит виглядає правильним, це ще не означає успіх. Сервер може повернути 404, може повернути 500, може повернути неочікуваний формат відповіді, а може взагалі… не відповісти. І це не «екзотика». Це нормальне життя мережі.

Якщо хочете зафіксувати відмінності в одному місці, ось дуже практична таблиця:

Характеристика Локальний виклик методу HTTP-виклик до зовнішнього API
Де виконується код У вашому процесі На чужому сервері
Вхідні дані Параметри методу URI + method + headers (+ іноді body)
Результат Повернене значення Status + headers + body
Помилка Виняток у вашому коді Може бути і виняток (мережа), і коректна HTTP-відповідь із помилковим статусом
«Чи можна дебажити всередину?» Зазвичай так Ні (це не ваш код)

І звідси народжується ключова звичка backend-розробника: HTTP-виклик завжди потрібно сприймати як взаємодію за контрактом, а не як «метод, який десь там виконується».

4. HTTP-контракт зовнішнього виклику

Коли ви починаєте працювати із зовнішнім API, дуже хочеться думати: «Ну там же JSON, зараз якось прочитаємо». Але правильніше мислити інакше: «Я укладаю договір із чужим сервісом». У цьому договорі є чіткі пункти: куди і як я стукаю, і що мені обіцяють повернути у відповідь. Інакше ви швидко опинитеся в ситуації «вчора працювало, а сьогодні ні».

В HTTP-контракті є кілька обов’язкових частин. Ми вже розбирали їх раніше, але сьогодні це важливо саме з позиції вихідного виклику, тобто коли клієнт — це ми:

Частина контракту Де це проявляється в коді Питання, яке варто собі поставити
URI URI.create(...) Куди я йду? Це точна адреса?
Method .GET(), .POST(), … Що я роблю: читаю чи змінюю?
Headers .header("Accept", "...") У якому форматі я хочу отримати відповідь?
Status code response.statusCode() Це успіх чи помилка? Який клас проблеми?
Body response.body() Що саме повернулося? Чи схоже це на те, що ми очікували?

І ось важлива думка: контракт — це не лише body. Новачок часто дивиться тільки на JSON і забуває, що 200 і 404 — це принципово різні історії. Навіть якщо в обох випадках тіло відповіді схоже на JSON, зміст зовсім різний.

Якщо зовсім просто: зовнішнє API — це як чек у магазині. Вам важливо не лише те, що всередині пакета (body), а й те, що написано зверху: чи успішно пройшла покупка (status), які умови (headers) і чи правильна адреса магазину (URI). Інакше ви ризикуєте принести додому не продукти, а відчуття сорому й набір помилок.

5. Postman: чернетка коду

Гарна новина: сьогодні нам не потрібно грати в «вгадай правильний endpoint». Ми вже ходили до каталогу через Postman, уже бачили приклади запитів і відповідей, уже перевіряли варіанти. Тобто ми робимо те, що в реальній розробці називається: спочатку дослідження контракту, потім реалізація.

Зручно подумки уявити, що Postman-запит — це чернетка майбутнього коду. Приблизно так, умовний формат, але зміст упізнаваний:

# Пошуковий запит до каталогу (чернетка з Postman)
GET {{baseUrl}}/search?q=clean%20code
# Кажемо серверу, що очікуємо JSON у відповіді
Accept: application/json

Це не просто «красиві рядки». Це зафіксована домовленість: метод GET, конкретний шлях, query-параметр q і очікування JSON-відповіді.

І ось чому цей крок важливий методично. Коли ви пишете перший HTTP-клієнт у Java, дуже легко скотитися в магію: «Ну, воно якось надсилає запит». Postman повертає вас на землю: там усе видно. Метод видно. URL видно. Заголовки видно. Відповідь видно. І завдання сьогоднішнього дня — перенести цю прозорість у код, а не сховати її під килим «універсального клієнта на майбутнє».

6. Мінімальний успіх: статус і raw body

На першому кроці дуже корисно знизити очікування до правильного мінімуму. Ми не будуємо сьогодні ідеальну архітектуру, не «проєктуємо клієнтський SDK», не перетворюємо JSON на красиві Java-об’єкти. Наш критерій успіху простіший і чесніший: застосунок надіслав запит, отримав відповідь і зміг показати нам її зміст.

Тут з’являється термін, який ми будемо використовувати весь рівень: raw body. Це тіло відповіді, прочитане «як є», без мапінгу в DTO. Тобто, по суті, JSON-рядок. І так, для Java-розробника це звучить як «фу, рядок», але в навчальному кроці це саме те, що потрібно: спочатку переконатися, що транспорт працює і контракт збігається, а вже потім думати про типи.

Схематично наш сьогоднішній мінімальний потік виглядає так:

sequenceDiagram
    participant U as "Користувач"
    participant A as "ReadLater Starter (Java)"
    participant C as "Зовнішній каталог книг"

    Note over U,A: "Користувач запускає команду й очікує спостережуваний результат"
    U->>A: "Запуск команди 'catalog search ...'"
    Note over A,C: "Важливо бачити контракт: URI, метод, заголовки, статус і body"
    A->>C: "HTTP GET (URI + Accept)"
    C-->>A: "HTTP response (status + headers + JSON body)"
    A-->>U: "Виведення статусу й raw JSON (попередній перегляд)"

Зверніть увагу: «відповідь» тут — це не лише JSON. Навіть на рівні «просто вивести в консоль» ми хочемо бачити щонайменше три речі: статус, заголовки (хоча б один раз подивитися) і тіло. Бо саме так бекенд-розробник вчиться не втрачати важливі частини контракту.

7. Відтворюваний крок

Дуже важливо, щоб перший зовнішній виклик не залишився «кодом в окремому методі, який ніхто ніколи не викликає». Наприкінці дня ви маєте вміти запустити застосунок і отримати спостережуваний результат: зовнішній запит справді пішов, відповідь справді прийшла, і ви справді бачите JSON. Це перетворює тему з «теорії про HTTP» на звичку.

І при цьому ми свідомо не ускладнюємо. Зараз буде достатньо навіть такого «скелета взаємодії» — поки без деталей реалізації, просто як ідея:

// Виконуємо запит до каталогу і забираємо "сирий" JSON як рядок
String json = catalogClient.searchRaw("clean code");

// Для першого кроку зручно друкувати прев’ю, щоб не завалити консоль мегабайтами JSON
int previewLength = Math.min(json.length(), 200);
System.out.println(json.substring(0, previewLength) + "...");
// { "docs": [ ... ] }...

Цей фрагмент важливий не тим, що він «красивий», а тим, що він робить зовнішню інтеграцію частиною застосунку. Тобто ми можемо повторювати запуск, порівнювати відповіді, розуміти, що змінюється, і поступово наводити код до ладу. Це рівно той самий принцип, за яким ми раніше фіксували Gradle Wrapper і команди запуску: якщо крок можна повторити, отже він справді існує, а не «випадково спрацював один раз на моєму комп’ютері».

І так, у цей момент може з’явитися бажання негайно загорнути все в десять шарів абстракцій. Це нормальне бажання, особливо якщо ви бачили красиві архітектурні картинки. Але сьогодні наше завдання навпаки: залишити механіку видимою, щоб мозок звик до реальності HTTP-виклику. Красу і «дорослі шари» ми нарощуватимемо пізніше — коли буде що нарощувати.

8. Типові помилки під час першого outbound HTTP-виклику

Коли ви тільки починаєте робити вихідні HTTP-виклики, ви неминуче порівнюєте їх зі звичними викликами методів і очікуєте такого самого поводження. Звідси народжуються помилки, які на практиці трапляються навіть у розумних людей — просто тому, що мозок ще не прийняв правила гри. Краще зловити їх на старті.

Помилка №1: думати, що бекенд «лише приймає запити», а виходити назовні — це щось рідкісне.
Через таке уявлення зовнішній виклик сприймається як дивна разова фіча, а не як нормальна частина системи. У результаті код ліпиться «куди доведеться», без дисципліни. Корисно одразу прийняти: outbound HTTP — це така сама звична рутина, як робота з рядками. На жаль.

Помилка №2: подумки прирівняти HTTP-виклик до someService.method() і проігнорувати контракт.
Щойно ви забуваєте, що в HTTP є адреса, метод, заголовки й коди статусу, ви починаєте дивитися тільки на body. Це небезпечно: ви можете отримати помилковий статус і все одно намагатися читати тіло як «успішну відповідь». Правильна звичка: спершу статус, потім решта.

Помилка №3: сховати перший виклик за «універсальним клієнтом на майбутнє».
Це той випадок, коли «архітектура» вбиває навчання. Якщо на першому кроці ви створите п’ять класів і три інтерфейси, ви втратите найцінніше — розуміння того, як реально виглядають запит і відповідь. На стартовому етапі краще один маленький зрозумілий виклик, ніж «вічний двигун» з абстракцій.

Помилка №4: писати код без опори на вже досліджений Postman-контракт.
Іноді хочеться: «Ну, зараз швидко накину URL просто рядком». У результаті ви витрачаєте час на вгадування, а не на навчання. Якщо контракт уже досліджено, використовуйте його як карту. Postman-колекція — це не архів «на пам’ять», а робоча документація для коду.

Помилка №5: вважати raw JSON у консолі «фіналом завдання».
Raw JSON — чудовий проміжний результат, але лише проміжний. Якщо ви зупинитеся тут, застосунок залишиться на рівні «друкуємо незрозумілий текст». Сьогодні raw — це інструмент перевірки контракту і транспорту. Далі, не сьогодні, ми будемо робити відповідь придатною для реального використання в коді.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ