Основные понятия
Проектирование API
Основы проектирования API для собеседований по System Design
На собеседованиях по System Design вам нужно определить, как клиенты взаимодействуют с вашей системой в рамках шага проектирования API в рекомендуемой структуре интервью.
Проектирование API довольно прямолинейно. Вы выбираете протокол, определяете ресурсы и задаете, как клиенты передают данные и получают ответы. Эта статья не сделает вас экспертом по проектированию API, но охватывает основы, необходимые чтобы впечатлить интервьюера за 5 минут, отведенные на этот шаг.
Стоит подчеркнуть: большинству интервьюеров не так важно, насколько идеально вы спроектировали API. Они хотят увидеть, что вы способны сделать разумный API в сжатые сроки и перейти к более сложным частям системы.
Тем не менее, если вы претендуете на продуктовую или фронтендерскую роль, API дизайн важнее, поскольку вы будете работать с API ежедневно. Также для Junior ролей ожидания от ваших навыков проектирования распределенных систем ниже, поэтому на API в интервью может уйти больше времени.
Типы API
В интервью вы обычно выбираете один из трех основных протоколов:
- REST (Representational State Transfer) - REST использует стандартные HTTP-методы (GET, POST, PUT, DELETE) для манипуляции ресурсами, идентифицируемыми по URL. Для стандартных CRUD операций в веб- и мобильных приложениях REST естественно соответствует операциям базы данных и семантике HTTP, поэтому это выбор по умолчанию.
- GraphQL - в отличие от REST с фиксированными эндпоинтами, GraphQL использует один эндпоинт с языком запросов, позволяющим клиентам запрашивать ровно те данные, которые им нужны. Представьте мобильное приложение, которому нужна только базовая информация о пользователе, и дашборд, который показывает подробную аналитику: в REST вам пришлось бы создавать множество эндпоинтов или заставлять клиентов получать лишние данные, а GraphQL позволяет каждому клиенту запросить ровно то, что ему нужно, одним запросом. Если интервьюер упоминает "гибкую выборку данных" или говорит о необходимости избежать избыточной выборки (over-fetching) и недостаточной выборки (under-fetching), это сигнал подумать о GraphQL.
- RPC (Remote Procedure Call) - RPC-протоколы вроде gRPC используют
бинарную сериализацию и HTTP/2 для эффективного общения между сервисами. В то
время как REST рассматривает все как ресурсы, RPC позволяет думать в терминах
действий и процедур - когда сервису пользователей нужно быстро проверить
права доступа в сервисе авторизации, вызов RPC вида
checkPermission(userId, resource)звучит естественнее, чем попытка представить это как REST-ресурс. Если интервьюер говорит о микросервисах или внутренних API, стоит рассмотреть RPC. Также используйте RPC, когда критична производительность (подробнее о протоколах - в статье Основы сетей).
По умолчанию выбирайте REST, если нет конкретной причины выбрать что-то другое. Он понятен, у него развитая экосистема и он подходит для 90% случаев. Если вы сомневаетесь, просто скажите: "Я буду использовать REST API" и двигайтесь дальше.
Для real-time функций, таких как уведомления, чаты или live-обновления, вам нужны другие протоколы - WebSockets или Server-Sent Events. Это не традиционные API, однако их важно знать, и вы можете изучить их в описании паттерна Обновления в реальном времени.
Давайте пройдемся по каждому варианту и выделим, что важно на собеседовании.
REST
Поскольку REST - ваш выбор по умолчанию, уделим ему больше внимания и разберем, как проектировать REST API на интервью по System Design.
Моделирование ресурсов
Основа хорошего REST API - правильная идентификация ресурсов. Если вы следовали структуре интервью, ресурсы - это ваши основные сущности.
Возьмем Ticketmaster в качестве примера. Вашими основными сущностями могут быть события, площадки, билеты и бронирования. Они естественно отображаются на REST ресурсы:
GET /events # Получить все события
GET /events/{id} # Получить конкретное событие
GET /venues/{id} # Получить конкретную площадку
GET /events/{id}/tickets # Получить доступные билеты для события
POST /events/{id}/bookings # Создать новое бронирование для события
GET /bookings/{id} # Получить конкретное бронирование
Важно, чтобы REST ресурсы представляли объекты в вашей системе, а не действия. Вместо того чтобы думать о том, что пользователи могут делать (например, "бронировать" или "покупать"), думайте о том, что существует в системе (события, площадки, билеты и бронирования).
Ресурсы обычно оформляют как множественные существительные (bookings, events, tickets и т. д.). Большинству интервьюеров это не критично, но некоторым важно, и это легко сделать правильно.
При работе со связями между ресурсами есть два основных подхода. Можно
вкладывать ресурсы в другие ресурсы при явной связи родительский-дочерний,
например /events/{id}/tickets для всех билетов конкретного события. Либо можно
держать ресурсы плоскими и использовать query-параметры для фильтрации, например
/tickets?event_id=123.
Ключевое различие - обязательна ли связь. Используйте path-параметры (или
вложенные ресурсы), когда значение обязательно - как /events/{id}/tickets, где
всегда нужно указать, билеты какого события вы хотите. Используйте
query-параметры, когда фильтр опционален - например,
/tickets?event_id=123§ion=VIP, где можно запросить все билеты, билеты по
событию или по событию и секции.
В интервью это различие помогает быстро выбрать правильный вариант. Если связь всегда обязательна, используйте path-параметр. Если это один из множества опциональных фильтров - query-параметр. Интервьюеру важнее, что вы правильно идентифицировали ресурсы, но демонстрация этого понимания показывает хорошую интуицию в проектировании API.
HTTP методы
После идентификации ресурсов нужно решить, как клиенты будут с ними взаимодействовать. HTTP предоставляет набор методов (глаголов), которые естественно отображаются на типовые операции, и понимание того, когда использовать каждый, важно для интервью.
GET используется для получения данных без изменений. Используйте GET для
/events/{id}, чтобы получить детали события, или /events, чтобы получить
список всех событий.
POST создает новые ресурсы. Когда пользователь бронирует билеты, вы
отправляете POST на /events/{id}/bookings с деталями бронирования в теле
запроса. Сервер назначает ID и возвращает созданное бронирование. POST не
является идемпотентным. То есть при повторении запрос создаст несколько
бронирований.
PUT полностью заменяет ресурс тем, что вы отправляете. Если вы полностью
обновляете профиль пользователя, сделайте PUT запрос на /users/{id} с полным
объектом пользователя. В отличие от POST, PUT идемпотентен - повторение приводит
к одному и тому же конечному состоянию.
PATCH обновляет часть ресурса. Когда пользователь меняет только email,
сделайте PATCH запрос на /users/{id} с полем email.
DELETE удаляет ресурс. DELETE запрос на /bookings/{id} отменяет
бронирование. Он идемпотентен, потому что повторные вызовы оставляют систему в
одном состоянии (ресурс остается удаленным), даже если коды ответа отличаются
(первый раз 204, затем 404).
Ключевой концепт здесь - идемпотентность, то есть оставляет ли повторный запрос систему в том же состоянии. GET, PUT и DELETE идемпотентны, потому что многократные вызовы не меняют конечный результат. POST - не идемпотентен, и это важно, когда сеть дает сбои и клиенты повторяют запросы. Обычно вам не нужны дублирующиеся бронирования из-за повторного запроса.
В общем случае PATCH не является идемпотентным. Хотя на практике многие API реализуют его так, чтобы он вел себя идемпотентно, стандарт RFC 5789 не гарантирует этого. Основная причина в том, что PATCH передает не новое состояние ресурса (как PUT), а набор инструкций по его изменению. Если эти инструкции зависят от текущего состояния, повторные вызовы изменят ресурс несколько раз. Если запрос говорит "прибавь 1 к счетчику", то после 5 вызовов значение увеличится на 5, а не на 1. Инструкция "добавить элемент в массив" при повторении создаст дубликаты в этом массиве.
Передача данных в API
API эндпоинтам нужны входные данные, чтобы понимать, что делать. Это может быть идентификатор ресурса для получения, данные для обновления или фильтры выдачи. Понимание того, куда помещать разные типы данных, важно для чистого и понятного API.
У вас есть три основных варианта передачи данных в REST API, и каждый выполняет свою задачу.
Path-параметры идентифицируют конкретный ресурс. Когда вы хотите получить
детали события, вы помещаете ID события в путь: /events/123. ID становится
частью структуры URL, и это ясно показывает, что вы запрашиваете конкретное
событие, а не коллекцию событий. Используйте path-параметры, когда значение
необходимо для идентификации ресурса - без него запрос не имеет смысла.
Query-параметры фильтруют, сортируют или модифицируют способ получения
ресурсов. Когда вы ищете события в конкретном городе или диапазоне дат, вы
используете query-параметры: /events?city=NYC&date=2026-01-01. Они опциональны
- вы можете запросить все события без фильтров или применить несколько фильтров
вместе. Query-параметры также хорошо подходят для разбиения на страницы:
/events?page=2&limit=20. Обратите внимание, что первый параметр отделяется символом ?, а последующие - символом &.
Тело запроса содержит фактические данные, которые вы отправляете для
создания или обновления ресурсов. Когда пользователь бронирует билеты, вы
отправляете POST на /events/{id}/bookings с деталями бронирования в теле
запроса. Например, сколько билетов и какие предпочтения по местам. Тело запроса
- место для сложных структур данных и всего, что слишком велико или чувствительно для URL.
Каждый тип входных данных выполняет свою роль в контракте API. Path-параметры - структурные, они определяют, на какой эндпоинт вы обращаетесь. Query-параметры - модификаторы, они меняют поведение эндпоинта. Тело запроса - это полезная нагрузка, реальные данные, с которыми вы работаете.
Вот практический пример. Если вы строите систему бронирования и пользователь хочет забронировать VIP билеты на конкретное событие, это может выглядеть так:
POST /events/123/bookings?notify=true
{
"tickets": [
{"section": "VIP", "quantity": 2},
{"section": "General", "quantity": 1}
],
"payment_method": "credit_card"
}ID события (123) находится в path, потому что нужно указать, какое событие вы бронируете. Опция уведомления - query-параметр, потому что это опциональное поведение. Детали бронирования находятся в теле запроса, потому что это основные данные, которые вы создаете.
Возврат данных
Ответ API состоит из двух частей:
- Код статуса, который указывает, успешен запрос или нет.
- Тело ответа, которое содержит данные, возвращаемые клиенту (обычно JSON).
Для кодов статуса придерживайтесь распространенных: 200 для успеха, 201 для созданных ресурсов, 400 для неверных запросов, 401 для необходимости аутентификации, 404 при отсутствии ресурса и 500 для ошибок сервера.
Не переусложняйте - интервьюерам важнее, что вы понимаете различие между ошибками клиента (4xx) и сервера (5xx), чем то, что вы помните каждый код статуса.
GraphQL
GraphQL появился в 2012 году, чтобы решить конкретную проблему: мобильному приложению компании нужны были одни данные, веб-приложению - другие, и они испытывали сложности на REST эндпоинтах с фиксированной структурой. Мобильная команда постоянно просила новые эндпоинты или изменения существующих, и это замедляло разработку обеих команд.
С REST у вас обычно два варианта, когда разные клиенты требуют разные данные - оба неприятные. Вы можете создавать множество эндпоинтов под разные сценарии, что приводит к разрастанию API и сложной поддержке. Или вы можете возвращать все данные, которые могут понадобиться любому клиенту, что ведет к избыточной выборке, когда мобильные клиенты скачивают мегабайты данных, которые им не нужны.
GraphQL объединяет ресурсные эндпоинты в один эндпоинт, принимающий запросы, описывающие, какие данные вы хотите. Клиент задает форму ответа, и сервер возвращает данные ровно в этой форме.
Как работает GraphQL
Вот простой пример для нашего сценария Ticketmaster. Вместо отдельных REST эндпоинтов для событий, площадок и билетов у вас есть один GraphQL эндпоинт, который может обрабатывать такие запросы:
query {
event(id: "123") {
name
date
venue {
name
address
}
tickets {
section
price
available
}
}
}
Сервер возвращает ровно то, что вы запросили - ни больше, ни меньше. Если мобильному приложению нужны только названия и даты событий, оно может запросить только эти поля. Если дашборду нужны подробные данные о событии с информацией о площадке и доступности билетов, он может запросить все это в одном запросе.
Когда использовать GraphQL в интервью
GraphQL подходит, когда у вас есть разные клиенты с разными потребностями в данных. Если интервьюер упоминает сценарии вроде "мобильному приложению нужны другие данные, чем веб-приложению", вероятно, он хочет, чтобы вы использовали GraphQL.
GraphQL также хорош, когда фронтенд-командам нужно быстро итеративно развивать продукт без изменений бэкенда. С REST добавление нового поля на экран мобильного приложения часто требует изменений на сервере и деплоя API. С GraphQL команда фронтенда может запрашивать дополнительные поля, если они уже описаны в схеме.
Однако GraphQL добавляет сложность. Нужно реализовать парсинг запросов, валидацию схемы и часто более сложные стратегии кэширования. Для большинства собеседований по System Design REST проще и прямолинейнее, если задача не требует гибкости GraphQL.
Проектирование схемы GraphQL
Если вы выбираете GraphQL, вам нужно мыслить иначе. Вместо REST эндпоинтов вы проектируете схему, определяющую типы данных и их отношения.
Для нашего примера Ticketmaster вы начали бы с моделирования ключевых сущностей как GraphQL типов:
type Event {
id: ID!
name: String!
date: DateTime!
venue: Venue!
tickets: [Ticket!]!
}
type Venue {
id: ID!
name: String!
address: String!
}
type Query {
event(id: ID!): Event
events(limit: Int, after: String): [Event!]!
}
Ключевое отличие от REST в том, что отношения определяются прямо в схеме. У Event есть Venue, и клиенты могут пройти по этому отношению в одном запросе.
Но эта гибкость создает проблему N+1, главный подводный камень GraphQL. Когда клиент запрашивает события с их площадками, вы можете выполнить один запрос за событиями и N отдельных запросов за площадками. При 100 событиях это 101 запрос к базе вместо 2. Решение - паттерны batching/dataloader, которые группируют связанные запросы, но это добавляет сложности, которой нет в REST.
GraphQL по-другому решает вопросы авторизации. Вместо защиты целых эндпоинтов, как в REST, вы защищаете отдельные поля. Пользователь может видеть имя и дату события, но не данные площадки.
В интервью упоминайте GraphQL, когда видите явные проблемы избыточной или недостаточной выборки, но не выбирайте его по умолчанию. Большинство интервьюеров оценят, что вы знаете о GraphQL, но предпочитают, чтобы основные архитектурные проблемы решались более простыми инструментами.
RPC
RPC (Remote Procedure Call) - это парадигма коммуникации, которая позволяет клиенту вызывать процедуру на сервере и ждать ответа, не вникая в детали сетевого взаимодействия. Протоколы вроде gRPC используют бинарную сериализацию и HTTP/2, что делает их быстрее традиционных JSON-over-HTTP REST API для межсервисного общения.
Как работает RPC
В отличие от ресурсного подхода REST, RPC ориентирован на действия. Вы фактически вызываете функции по сети, как если бы они были локальными в коде. Вот как могли бы выглядеть операции Ticketmaster в RPC:
// Вместо GET /events/123
getEvent(eventId: "123")
// Вместо POST /events/123/bookings
createBooking(eventId: "123", userId: "456", tickets: [...])
// Вместо GET /events/123/tickets
getAvailableTickets(eventId: "123", section: "VIP")Самый популярный RPC-протокол сегодня - gRPC, который использует Protocol Buffers для сериализации и HTTP/2 для транспорта. Эта комбинация быстрее, чем JSON REST, особенно для общения между сервисами. Другой известный RPC фреймворк, Apache Thrift, поддерживает множество языков и форматов сериализации.
Protocol Buffers и типобезопасность
gRPC использует Protocol Buffers (protobuf) для определения контрактов сервисов.
Вы пишете .proto файл, описывающий методы и структуры данных:
service TicketService {
rpc GetEvent(GetEventRequest) returns (Event);
rpc CreateBooking(CreateBookingRequest) returns (Booking);
rpc GetAvailableTickets(GetTicketsRequest) returns (TicketList);
}
message GetEventRequest {
string event_id = 1;
}
message Event {
string id = 1;
string name = 2;
int64 date = 3;
Venue venue = 4;
}Из этого определения gRPC генерирует клиентский и серверный код на разных языках. Это означает, что ваш Go сервис и Java сервис оплаты могут общаться с проверкой типов на этапе компиляции, выявляя несоответствия до развертывания.
Когда использовать RPC в интервью
RPC отлично подходит для микросервисных архитектур, где сервисам нужно часто и эффективно общаться. Строгая типизация помогает ловить ошибки на этапе компиляции, а бинарный протокол более эффективен, чем JSON. Рассматривайте RPC для внутреннего общения между сервисами, особенно когда производительность критична или когда задержки определяются сетью, а не работой сервера.
Рассмотрите RPC, когда:
- Производительность критична: бинарная сериализация и HTTP/2 делают RPC заметно быстрее
- Нужна типобезопасность: сгенерированный клиентский код предотвращает многие ошибки
- Сервис-сервис коммуникация: внутренние API между вашими сервисами не нуждаются в REST
- Нужна потоковая передача: gRPC поддерживает двунаправленный стриминг для функций реального времени
Для нашего примера Ticketmaster вы можете использовать REST API для публичных эндпоинтов, которыми пользуются мобильные и веб-клиенты, а gRPC - для внутреннего общения между сервисами бронирований, платежей и инвентаризации.
Если вас не попросили явно, вы обычно не будете расписывать внутренние API на шаге проектирования API в интервью. Вместо этого сосредоточьтесь на публичных API. При проектировании high-level дизайна можете упомянуть что внутренние сервисы общаются по RPC, если увидите в этом необходимость.
Общие паттерны API
Независимо от того, выбираете ли вы REST, GraphQL или RPC, есть несколько общих паттернов, которые применимы ко всем типам API. Их полезно знать, потому что они встречаются в большинстве реальных систем.
Разбиение на страницы (пагинация)
Когда у вас большие наборы данных, вы не можете вернуть все сразу. Представьте API, возвращающее все события за всю историю - это может быть миллионы строк и гигабайты данных.
Вместо этого нужна пагинация, чтобы разбивать большие результаты на небольшие порции. Есть два основных подхода к пагинации: по смещению и по курсору.
Пагинация по смещению
Пагинация, основанная на смещении (offset-based) - самый простой подход и
используется на большинстве сайтов. Вы указываете, сколько строк пропустить и
сколько вернуть: /events?offset=20&limit=10 возвращает строки 21-30. Этот
подход интуитивен и легок в реализации, но у него есть проблемы с большими
наборами данных. Если кто-то добавит новое событие, пока вы проходите по
страницам, вы можете увидеть дубликаты или пропустить строки из-за сдвига
данных.
Пагинация по курсору
Пагинация, основанная на курсоре (cursor-based) решает это, используя указатель на конкретную строку вместо подсчета с начала. Вот как это выглядит на практике:
Первый запрос выглядит так: /events?limit=10, а ответ включает события и
курсор, указывающий на последнюю строку:
{
"events": [...],
"next_cursor": "cmi2ss4lj06ac08adys8gxq62"
}Следующий запрос выглядит так:
/events?cursor=cmi2ss4lj06ac08adys8gxq62&limit=10
Курсор обычно представляет собой закодированную ссылку на конкретную строку
(например, ID или timestamp). Это стабильнее, потому что не зависит от
добавления новых строк, но сложнее реализовать функции вроде "перейти на
страницу 5". В примере cmi2ss4lj06ac08adys8gxq62 - это ID последнего события
на первой странице.
Для интервью пагинации по смещению обычно достаточно, если только вы не работаете с real-time данными или интервьюер явно говорит о высоких объемах. Большинству интервьюеров важнее, что вы вообще вспомнили про пагинацию, чем конкретный ее тип.
Стратегии версионирования
API со временем эволюционируют, и вам нужна стратегия, чтобы внедрять изменения без поломки существующих клиентов. Это особенно важно для публичных API, где вы не контролируете, когда клиенты обновляют код.
Самый распространенный подход - версионирование в URL (URL versioning), где
вы указываете версию в пути: /v1/events или /v2/events. Такое явное
версионирование позволяет клиентам сразу видеть, какую версию они используют.
Этот подход также прост в реализации, поскольку вы можете легко маршрутизировать
запросы основываясь на URL.
Версионирование через заголовок (Header versioning) помещает версию в HTTP
заголовок: Accept-Version: v2 или API-Version: 2. Это делает URL более
чистыми и лучше соответствует стандартам HTTP, но менее очевидно для
разработчиков и сложнее тестируется в браузерах.
Для интервью версионирование в URL обычно безопаснее, потому что оно более распространено и его легче объяснять. Если интервьюер специально не спрашивает о версионировании в заголовках, придерживайтесь URL подхода.
Вы заметите, что в наших разборах мы даже не включаем версионирование в дизайн API. Это не потому, что это не важно в реальной жизни (это важно), а потому что для большинства задач и интервьюеров это не ключевой момент.
Соображения безопасности
Безопасность часто рассматривается как второстепенная тема в интервью, но демонстрация осведомленности в области безопасности может выделить вас в глазах интервьюера. Вам не нужно проектировать идеальную систему безопасности, но показать, что вы понимаете базовые принципы, означает, что вы думаете о системе, подготовленной к реальной эксплуатации.
Аутентификация и авторизация
Первый вопрос, на который должен ответить API: "Кто делает этот запрос и имеет ли он право делать то, что просит?"
Аутентификация проверяет личность - доказывает, что пользователь тот, за кого себя выдает. Авторизация проверяет права - может ли этот аутентифицированный пользователь выполнить конкретное действие.
В нашем примере Ticketmaster аутентификация может подтверждать, что запрос приходит от пользователя "jon@snow.com", а авторизация проверяет, что Jon имеет право отменить конкретное бронирование (он должен иметь возможность отменять только свои бронирования, а не чужие).
API ключи vs JWT токены
Когда вы проектируете аутентификацию для API, обычно выбираете между двумя подходами в зависимости от того, кто будет использовать API и как к нему будут обращаться.
Для большинства (но не всех) интервью аутентификация и авторизация не в фокусе. Наш совет - обозначить, какие эндпоинты требуют аутентификации, и сказать, что вы будете использовать JWT или хранить сессии пользователей в базе данных.
API ключи
API ключи - это длинные случайно сгенерированные строки, которые выступают
паролями для приложений, а не для людей. Когда клиент делает запрос, он включает
API ключ в заголовок Authorization, и сервер ищет этот ключ, чтобы определить,
какое приложение делает запрос.
Вот как они работают: вы генерируете уникальный API ключ для каждого клиента
(например, sk_live_abc123...), храните его в базе вместе с правами или
лимитами, и затем проверяете входящие запросы по этому ключу. Они идеально
подходят для коммуникации между сервисами, где вы контролируете обе стороны.
Когда сервис бронирований вызывает сервис оплаты, API ключ - простой и
эффективный способ. Они также подходят, когда вы открываете API для сторонних
разработчиков, которым нужен программный доступ.
Если вы строите продукт с пользовательскими API, API ключи почти никогда не являются лучшим выбором. Пользователи не должны управлять длинными криптографическими строками, а API ключи часто не имеют срока действия и не несут контекст пользователя так, как это нужно для пользовательских сессий.
GET /events
Authorization: Bearer sk_live_abc123...
JWT (JSON Web Tokens)
JWT токены, напротив, кодируют информацию о пользователе прямо в токене вместо хранения состояния сессии на сервере. Когда пользователь успешно логинится, сервер создает JWT с ID пользователя, правами и временем истечения, и подписывает его секретным ключом.
Когда этот JWT приходит с последующими запросами, вы можете проверить подпись и прочитать информацию прямо из токена без запросов к базе. Сам токен несет весь контекст, необходимый для авторизации запроса.
JWT особенно полезны для распределенных систем, потому что любой сервис, который знает секретный ключ, может валидировать токен независимо. Если мобильное приложение отправляет JWT на API-шлюз, шлюз может проверить пользователя и уверенно перенаправить запрос в сервис бронирований.
// JWT payload
{
"user_id": "123",
"email": "jon@snow.com",
"role": "customer",
"exp": 1770882886
}Используйте API ключи для внутреннего общения сервисов и внешнего доступа разработчиков. Используйте JWT для пользовательских сессий в веб и мобильных приложениях. JWT не требуют запроса к базе и могут передавать пользовательский контекст, что делает их идеальными для пользовательских приложений.
Role-Based Access Control (RBAC)
В реальных системах есть разные типы пользователей с разными правами. В нашей системе Ticketmaster клиенты могут бронировать билеты и просматривать свои бронирования, менеджеры площадок могут создавать события и смотреть отчеты о продажах, а администраторы могут делать все.
RBAC назначает роли пользователям и права ролям:
Roles:
- customer: бронировать билеты, просматривать свои бронирования
- venue_manager: создавать события, просматривать продажи для своих площадок
- admin: доступ ко всему
User: jon@snow.com -> Role: customer
User: melisandre@asshai.com -> Role: venue_manager
User: george@martin.com -> Role: admin
В спроектированном API нам нужно проверять и аутентификацию, и авторизацию:
GET /bookings/{id}
1. Проверяем, аутентифицирован ли пользователь (валидный JWT токен)
2. Проверяем, авторизован ли пользователь (является владельцем этого бронирования или является администратором)
Ограничение скорости запросов (Rate Limiting)
Ограничение скорости запросов предотвращает злоупотребления, ограничивая количество запросов, которые клиент может делать за заданный период. Это защищает систему как от злонамеренных атак, так и от случайного чрезмерного использования.
Распространенные стратегии:
- Лимиты на пользователя: 1000 запросов в час для аутентифицированного пользователя
- Лимиты на IP: 100 запросов в час для неаутентифицированных запросов
- Лимиты на эндпоинт: 10 попыток бронирования в минуту для предотвращения спекуляций
Обычно rate limiting реализуется на уровне API-шлюза или через middleware в приложении. При превышении лимитов возвращается статус 429 Too Many Requests.
В интервью упоминание rate limiting показывает, что вы думаете о вопросах реальной эксплуатации системы, но не тратьте время на проектирование конкретных алгоритмов, если об этом не спросили. Простого "мы реализуем rate limiting, чтобы предотвратить злоупотребления" обычно достаточно.
Заключение
Проектирование API на собеседованиях по System Design - это демонстрация инженерного здравого смысла, а не создание идеальных спецификаций. Сфокусируйтесь на выборе правильного протокола для вашей задачи (обычно REST), на четком моделировании ресурсов и на демонстрации понимания базовой аутентификации и безопасности.
В интервью важен баланс. Потратьте достаточно времени, чтобы показать, что вы можете спроектировать разумный API, но не закапывайтесь в детали, когда есть более сложные архитектурные проблемы. Интервьюеры хотят видеть, что вы можете строить работающие системы, а не то, что вы выучили все HTTP коды. На практике кандидаты чаще ошибаются, потратив слишком много времени на API дизайн, чем недооценив этот этап. Старайтесь не тратить больше 5 минут на описание API на интервью.