Синхронізація міжнародної доставки: API, webhooks та стійкість даних

Для користувача відстеження посилки — це просто: ввів номер — побачив, де вона. Але для розробника за лаштунками ховається значно складніший процес. За одним простим статусом “In transit” можуть стояти десятки перевізників, різні API, webhooks, черги подій, повторні запити, зіставлення статусів і дані, що надходять із запізненням.

Міжнародна посилка – чудовий приклад розподіленої системи. Вона фізично переміщується між складами та країнами, а її цифровий слід збирається з багатьох незалежних джерел. Ці два процеси майже ніколи не бувають ідеально синхронізовані.

Розберімося, що насправді відбувається між сканером на складі та повідомленням у вашому застосунку.

Tracking number — це ключ, а не GPS

Найпоширеніша помилка — сприймати номер відстеження як координати в реальному часі. Насправді це радше ключ до набору подій. Посилку відсканували під час приймання — з’явилася подія. Вона пройшла сортувальний центр — ще одна. Її передали іншому оператору — наступна.

Між цими точками коробка може проїхати сотні кілометрів без жодної нової цифрової події. Тому “статус не змінювався 12 годин” не означає, що вантаж стоїть на місці.

У найпростішому варіанті система зберігає tracking ID, перевізника, тип події, час, локацію, сирий і нормалізований статус. Справжні складнощі починаються, коли перевізник не один.

Один маршрут — кілька API

У США посилку може перевозити один оператор. Після прибуття на форвардинговий склад вона стає частиною міжнародного відправлення, а в Україні останню милю може виконувати зовсім інша служба.

Кожен учасник ланцюга має власну модель даних. Один повертає `delivered`, інший — `DEL`, третій — текстове `Package delivered to recipient`. Якщо показувати користувачеві сирі значення, інтерфейс швидко перетвориться на словник кодів.

Тому між зовнішніми API та frontend потрібен шар нормалізації. Десятки зовнішніх подій можна звести до кількох основних станів: `Created`, `Accepted`, `In transit`, `At warehouse`, `Customs`, `Out for delivery`, `Delivered`, `Exception`.

Саме якість цього зіставлення (мапінгу) часто визначає, чи зрозуміє користувач, що насправді відбувається з його посилкою.

Webhook чи polling

Ідеальний сценарій — перевізник надсилає webhook одразу після нової події. Система приймає payload, перевіряє його, записує подію та оновлює загальний статус.

Але не всі інтеграції однаково зручні. Десь є webhooks, десь тільки REST API, а партнерський інтерфейс може мати обмеження за кількістю запитів (rate limits). Тоді доводиться використовувати polling — періодичне опитування.

Наївний варіант — кожні п’ять хвилин перевіряти всі tracking numbers. На сотнях відправлень це ще працює. Але на великих обсягах більшість запитів не приноситимуть нових даних.

Краще частоту опитування прив’язувати до стану. Новостворений лейбл можна перевіряти рідше. Якщо посилка “Out for delivery” — частіше. А доставлені відправлення взагалі можна прибрати з активного опитування.

Події приходять не по порядку

Уявімо, що на сортувальному центрі створено подію о 10:02, а наступне сканування відбулося о 10:17. Через мережеву затримку друга подія потрапила у вашу систему першою. Якщо просто оновлювати поле `status` у порядку надходження HTTP-запитів, можна “відкотити” посилку назад у часі.

Тому важливо розділяти щонайменше два timestamp: `event_time` — коли подія сталася фактично, і `received_at` — коли система її отримала. Так само треба бути готовим до дублювання. Webhook може прийти двічі, партнер повторить запит після timeout, а polling витягне подію, яку система вже отримала іншим каналом.

Без механізму idempotency (здатності виконати операцію кілька разів без зміни результату) одна фізична операція легко перетворюється на декілька push-повідомлень.

Idempotency рятує не тільки платежі

Для подій відстеження корисно мати зовнішній `event ID` або власний `deduplication key` — наприклад, комбінацію номера посилки, типу події, часу та локації. Це особливо важливо, якщо на зміну статусу підписані інші процеси.

Подія `ArrivedAtWarehouse` може оновити кабінет клієнта, створити push-сповіщення, поставити задачу на склад та змінити список посилок, доступних для консолідації. Якщо виконати весь цей ланцюг двічі, наслідки вже не будуть просто косметичними.

Eventual consistency — це нормально

Користувачеві хочеться, щоб фізичний світ і застосунок були синхронізовані до секунди. У логістиці це майже недосяжно. Сканер може працювати офлайн. API партнера може віддавати дані із запізненням. Webhook може потрапити в чергу повторних спроб (retry queue). Застосунок може кешувати попередній стан.

Тому система зазвичай є eventually consistent: через певний час усі компоненти приходять до правильного стану, але в конкретну секунду можуть бачити різні версії реальності. Проблема не в самій eventual consistency. Проблема — коли продукт її не пояснює.

Замість категоричного “Посилка знаходиться на складі X” іноді чесніше показати “Останнє оновлення: 14:32”. Це одразу задає правильне очікування щодо точності даних.

Черга між інтеграцією та бізнес-логікою

Якщо webhook запускає всю бізнес-логіку синхронно, зовнішній партнер фактично керує часом відповіді вашого endpoint. Надійніша схема виглядає приблизно так: carrier → webhook endpoint → validation → queue → event processor → database → notifications.

Endpoint швидко приймає валідну подію. Нормалізація, дедуплікація, перерахунок стану та сповіщення виконуються асинхронно. Це забезпечує можливість повторних спроб (retry), захист від надлишкового навантаження (backpressure) та простіший контроль помилок. Для проблемних payload’ів корисний dead-letter queue: вони не зникають і не блокують основний потік обробки.

Чому «створено етикетку» може висіти кілька днів

Класичний кейс: магазин створив shipping label і передав tracking number покупцю. API перевізника вже знає номер, тому система показує `Label created`. Але самої коробки у перевізника ще фізично немає.

Магазин може передати її ввечері, наступного дня або навіть після вихідних. Перше фізичне сканування відбудеться лише під час приймання посилки перевізником.

З технічного погляду все працює правильно. З продуктового — користувач бачить номер і думає, що доставка вже почалася. Тому статуси краще називати зрозуміліше: “Магазин створив етикетку, очікуємо передачу перевізнику” точніше, ніж абстрактне “Відправлено”.

Спостережуваність потрібна не менше, ніж трекінг

Якщо система агрегує дані з кількох carrier API, простого 500 error у логах недостатньо. Корисно відстежувати затримку між `event_time` і `received_at`, кількість номерів без оновлень довше встановленого порогу, частоту помилок 429 (Too Many Requests) та 5xx (Server Error) від партнерів, розмір черги повторних спроб і кількість статусів, для яких ще немає відповідного мапінгу.

Особливо показовий лаг (затримка). Якщо затримка подій від одного партнера раптом зросла з кількох хвилин до сорока, користувачі ще можуть нічого не помітити, але інцидент уже почався.

Синхронізація міжнародної доставки: API, webhooks та стійкість даних 2

UX — останній шар архітектури

Можна ідеально побудувати процес прийому даних (ingestion pipeline) й усе одно отримати сотні звернень “де моя посилка?”. Причина проста: користувачеві не потрібні ваші event types. Йому потрібна відповідь на три питання: що вже сталося, де ми зараз і що буде далі.

У міжнародній доставці шлях покупки зі США може включати магазин, американського перевізника, склад, авіа- або морський транспорт, митний етап і локальну доставку. Завдання продукту — перетворити цю складність на один зрозумілий маршрут.

Як приклад користувацького сценарію можна подивитися, як такий маршрут організований у Dnipro LLC: американська адреса, особистий кабінет, міжнародне відправлення та трекінг об’єднуються в один безперервний процес.

Для розробника відстеження посилки — це хороший навчальний приклад роботи з розподіленими системами. Тут є зовнішні API, невпорядковані події, дублікати, повторні спроби, eventual consistency, зіставлення станів і, врешті-решт, людина, яка все одно очікує одну просту відповідь.

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

Джерело

No votes yet.
Please wait...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *