📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

System Design Explained: APIs, Databases, Caching, CDNs, Load Balancing & Production Infra

Hayk Simonyan1:49:49

Transcription

Большинство разработчиков не могут проектировать системы или функции с нуля. Они могут дополнять чужую архитектуру задачами с четкими требованиями и уже на зрелых системах. Но если попросить их спроектировать что-то с нуля, большинство из них обычно замирают. И на самом деле, именно этот навык отличает разработчиков среднего уровня от старших, потому что старшие также способны принимать решения, идти на компромиссы в дизайне, проектировать архитектуру с нуля и принимать решения при нечетких требованиях. Поэтому компании платят шестизначные суммы не за людей, которые просто умеют кодировать или следовать инструкциям, а за архитектурные решения, за производительность системы, за оптимизацию хранения данных и за принятие решений, которые также влияют на клиентов и программное обеспечение, которое они создают. Поэтому в этом видео я научу вас именно тем концепциям, которые я освоил, чтобы иметь возможность проектировать такие системы с нуля и достигать старших должностей. Именно так я без проблем проходил собеседования по проектированию систем. И это те навыки, которые я приобрел, чтобы достичь старшего уровня в течение второго года своей карьеры. Поэтому я не учу вас какой-то теории из книг или рассылок. Это то, что реально работает на реальных работах и на реальных собеседованиях. Итак, давайте перейдем к моему компьютеру, чтобы увидеть, что мы будем охватывать в этом курсе. Прежде всего, мы начнем с основ, с ключевых концепций, которые вам нужно понять перед всем остальным в проектировании систем. Затем мы перейдем к проектированию API, что является большой частью проектирования систем, как на самом деле проектировать API, которые масштабируются, а также имеют смысл для других разработчиков, которые будут их использовать. Затем мы перейдем к базам данных, как выбрать правильную базу данных для различных сценариев и правильно спроектировать свой слой данных. Далее мы перейдем к кэшированию. Как использовать кэширование? Как использовать CDN, балансировку нагрузки, чтобы сделать ваши системы быстрыми и надежными. Затем мы перейдем к обработке больших данных, потому что это большая тема сама по себе. Как правильно обрабатывать большие объемы данных. Затем мы перейдем к проектированию для продакшена, как создавать системы, которые реально работают в реальном мире, а не только на вашем ноутбуке или на одной машине. И, наконец, вы увидите, как я проектирую системы для собеседований и справляюсь с этим шагом, чтобы вы могли блестяще пройти эти собеседования и получить предложения, которые вам нужны для достижения старших должностей. Проектирование системы для поддержки миллионов пользователей — сложная задача, но каждая сложная система начинается с чего-то простого. Поэтому в этом уроке мы создадим базовую настройку, которая поддерживает всего одного пользователя, а затем постепенно будем расширять ее по мере продвижения. Потому что начинать с малого позволяет нам понять каждый основной компонент, прежде чем добавлять сложность. Итак, давайте начнем с первого шага и создадим настройку с одним сервером. Представьте, что мы настраиваем систему для небольшой базы пользователей. Это означает, что все работает на одном сервере: веб-приложение, база данных, кэш и другие компоненты. И эта настройка позволяет нам визуализировать основные рабочие процессы без дополнительной сложности. Теперь давайте разберем, как эта настройка с одним сервером обрабатывает запросы пользователей. У нас есть пользователи, которые пытаются получить доступ к нашему веб-сайту или API на сервере. Они могут использовать веб-браузер или мобильное приложение для доступа к нашему серверу. И с другой стороны, у нас есть наш сервер, который имеет необходимые файлы для обслуживания веб-браузеров, а также необходимые конечные точки API для обслуживания мобильного приложения, и он размещен по этому примерному IP-адресу. Изначально у наших пользователей нет этого IP-адреса. У них есть домен, к которому они пытаются получить доступ. Допустим, это app.demo.com. Поэтому, если они просто введут это доменное имя и нажмут Enter, их веб-браузер, например, свяжется с DNS, что означает систему доменных имен. Это провайдер, который сопоставляет домены с IP-адресами. И в нашем случае, скажем, наше доменное имя сопоставлено с IP-адресом, который является IP-адресом сервера, который у нас есть. Итак, этот DNS-провайдер отправит IP-адрес обратно в веб-браузер или в мобильное приложение нашим клиентам. И этот IP-адрес является IP-адресом нашего сервера. Теперь у них есть местоположение, куда они пытаются отправить запросы. Имея этот IP-адрес, устройство пользователя отправляет HTTP-запрос на наш сервер с просьбой о конкретных данных. Затем наш сервер обрабатывает этот запрос и отправляет обратно запрошенные данные. Это может быть HTML-страница для браузера или JSON-ответ для приложения, в зависимости от типа запроса. В этой настройке трафик обычно исходит из двух основных источников. Первый — это веб-приложения, а второй — мобильные приложения, которые пытаются получить доступ к нашему серверу. Для наших веб-пользователей сервер обрабатывает бизнес-логику, хранение данных, а также представление с использованием HTML, CSS и JavaScript. А для мобильных пользователей связь обычно осуществляется через HTTP. Эти мобильные приложения запрашивают данные с сервера с помощью вызовов API. И JSON часто используется для ответов, потому что он легкий и легко интерпретируется мобильными устройствами. Вот пример запроса API, который мы можем получить для нашего сервера. Это может быть GET-запрос к нашему домену/продукту/ ID этого продукта. И для этой конечной точки нам нужно получить детали продукта. И вот пример ответа, который мы можем отправить обратно клиенту. Это JSON-ответ, который содержит ID продукта. Он содержит название этого продукта, некоторое описание, цену продукта и некоторые другие метаданные, которые полезны для клиента. И затем это будет использоваться мобильным приложением или веб-браузером для отображения этого продукта на экране. И по мере нашего продвижения наша цель будет состоять в том, чтобы выявить области, где одного сервера может быть недостаточно для удовлетворения спроса пользователей. Пока что эта настройка идеально подходит для небольших баз пользователей, но она может испытывать трудности при большом трафике. Итак, далее мы рассмотрим способы масштабирования каждой части системы для эффективной поддержки большего числа пользователей. Некоторые ключевые выводы, которые мы можем сделать из этого, заключаются в том, что нам нужно начинать с малого. Нам нужно начать с простой настройки с одним сервером, чтобы понять основные компоненты системной архитектуры. Теперь мы также понимаем, как эти запросы проходят через вашу систему, что является основой для создания более масштабируемых систем, и мы также признаем уникальные требования к веб- и мобильным приложениям и то, как они взаимодействуют с вашим сервером. И в следующих уроках мы начнем рассматривать стратегии оптимизации и масштабирования этой настройки. По мере роста нашей базы пользователей один сервер становится недостаточным для обработки возросшего спроса и обслуживания большего числа пользователей. Мы можем разделить наш веб-уровень, который обрабатывает веб- и мобильный трафик, и уровень данных, который управляет базой данных. Эта настройка позволяет нам масштабировать каждый сервер в зависимости от его конкретной нагрузки. Но когда дело доходит до выбора правильной базы данных, откуда мы знаем, какая конкретная база данных лучше всего подходит для нашего конкретного приложения? Когда дело доходит до выбора базы данных, есть два основных варианта. Первый вариант — реляционные базы данных или RDBMS, которые структурированы в виде таблиц и строк. Некоторые популярные примеры: PostgrSQL, MySQL, Oracle database или SQL light. С другой стороны, у нас есть нереляционные или NoSQL базы данных. Они подходят для приложений, которые требуют гибкости и быстрого доступа к большим объемам неструктурированных данных. Некоторые примеры: Cassandra, MongoDB, Radius или Neo4G. Давайте начнем с изучения реляционных баз данных. Эти базы данных используют язык структурированных запросов или SQL для поиска и манипулирования данными. Данные здесь структурированы в виде таблиц, которые являются фундаментальными строительными блоками SQL-баз данных. И они похожи на электронные таблицы. Каждая таблица состоит из столбцов, которые можно рассматривать как поля или атрибуты таблицы, а также из строк, которые являются отдельными записями в этой таблице. Например, если представить таблицу клиентов, то в этой таблице у нас могут быть столбцы, такие как ID, имя, возраст и электронная почта. И для каждой строки у нас могут быть конкретные клиенты, например, ID 1 2 3, имя будет Джон, возраст 30 и так далее. Но каковы преимущества использования SQL-базы данных? Прежде всего, они поддерживают сложные операции соединения между несколькими таблицами. Например, если представить, что у нас есть таблица клиентов и таблица продуктов. И теперь мы хотим создать отдельную таблицу, которая будет связывать клиентов и продукты, которые они заказали. С помощью SQL вы можете объединить эти две таблицы в таблицу заказов. И она будет содержать информацию об ID клиентов, которые имеют этот заказ, а также ID продуктов, которые этот клиент заказал. И этот процесс объединения двух или более таблиц в одну таблицу называется операциями соединения в SQL. И другим большим преимуществом является то, что они обеспечивают надежную согласованность и целостность данных, особенно для транзакций. Транзакции в SQL — это последовательность одной или нескольких SQL-операций, которые выполняются как единое атомарное целое, и каждая транзакция в SQL следует принципу ACID. Вы можете думать о примере транзакции, как о банковском переводе. Поэтому, прежде всего, все транзакции атомарны, что означает, что вся транзакция рассматривается как единое целое, которое либо полностью успешно, либо полностью терпит неудачу. Каждая транзакция также является согласованной, что означает, что она преобразует базу данных из одного допустимого состояния в другое допустимое состояние, и они также обладают изоляцией, что означает, что изменения, сделанные параллельными транзакциями, изолированы друг от друга и не мешают друг другу. И, наконец, они обладают долговечностью, что означает, что даже если система выйдет из строя или сервер базы данных выйдет из строя, данные все равно останутся там. А теперь давайте посмотрим на нереляционные базы данных. Нереляционные базы данных могут иметь различные формы. Например, у нас есть документо-ориентированные базы данных, такие как MongoDB, или вы можете использовать ширококолоночные базы данных, такие как Cassandra, базы данных ключ-значение, такие как Radius, и графовые базы данных, такие как Neo4G. Давайте рассмотрим каждый из этих типов отдельно и начнем с документо-ориентированных баз данных. MongoDB является наиболее популярным примером документо-ориентированной базы данных, и данные здесь хранятся в JSON-подобных документах, что позволяет нам иметь сложные структуры данных в одной записи. Далее у нас есть ширококолоночные базы данных, где данные хранятся в таблицах, строках и динамических столбцах. Некоторые примеры здесь: Cassandra или Cosmos DB. Основное преимущество этих баз данных заключается в том, что они могут обрабатывать огромные объемы данных и очень хорошо подходят для многих операций записи. Другой вариант — графовые базы данных, которые фокусируются на хранении сущностей и их отношений в виде графов. Примером графовой базы данных является Neo 4G. Например, в Amazon они используют графовую базу данных Neptune, которая помогает им рекомендовать вам продукты на основе ваших предыдущих заказов. И другой популярный тип — это базы данных ключ-значение. Здесь данные хранятся в парах ключ-значение. Самое большое преимущество баз данных ключ-значение — это их простота и скорость, поскольку они в основном хранятся в оперативной памяти. Чтение и запись в эти базы данных чрезвычайно быстры по сравнению с другими базами данных. Некоторые примеры баз данных ключ-значение: memcache или radius. Итак, это основные четыре типа NoSQL баз данных. Теперь давайте посмотрим на преимущества этих NoSQL баз данных. Если посмотреть на тот же пример, который у нас был для SQL-баз данных, где у нас есть клиенты и продукты, и мы хотим объединить их в заказы. Например, в MongoDB это может быть один документ. Таким образом, вы можете хранить все данные пользователя, а также заказы и продукты в одном документе. И благодаря этой структуре NoSQL базы данных могут обрабатывать высокодинамичные и большие наборы данных без структуры, налагаемой реляционными базами данных, а также они оптимизированы для низкой задержки и масштабируемости. Итак, когда следует использовать реляционные или нереляционные базы данных? Вот краткое сравнение обоих. Если данные вашего приложения хорошо структурированы с четкими отношениями, то следует использовать SQL-базы данных. Например, если у вас есть приложение для электронной коммерции, отслеживающее клиентов и заказы, это хороший пример использования SQL-базы данных. Далее, если вам нужна строгая согласованность и целостность транзакций, например, если у вас есть финансовое приложение или банковская система, то следует использовать SQL-базы данных. Однако, если ваше приложение требует сверхнизкой задержки для быстрых ответов, то следует выбрать нереляционные базы данных. или если данные неструктурированы или полуструктурированы, такие как JSON-объекты, и отношения не так важны, то следует также выбрать NoSQL базы данных. И, наконец, если ваше приложение требует гибкого и масштабируемого хранения для огромных объемов данных, например, рекомендательный движок, хранящий данные о действиях пользователей в формате ключ-значение, то следует также выбрать NoSQL базы данных. Знание этого в теории — уже шаг вперед. Таким образом, вы уже знаете, как проектировать такие системы на высоком уровне. Но этого недостаточно для достижения старших должностей и прохождения собеседований. Чтобы по-настоящему освоить проектирование систем и стать уверенным старшим разработчиком, который получает шестизначные зарплаты, вам также нужен практический опыт создания этих систем с нуля у облачных провайдеров, таких как AWS, и объяснения ваших архитектурных решений на реальных собеседованиях. В течение следующих семи дней только вы можете присоединиться к менторству Dev Mastery с 7-дневной бесплатной пробной версией. Вы получите полный курс по проектированию систем, реальные проекты и мое менторство, чтобы стать уверенным старшим инженером, который не беспокоится об увольнениях или о том, что ИИ займет его работу, потому что у вас будут архитектурные навыки, которые компании отчаянно нуждаются и за которые всегда готовы платить шестизначные суммы. Нажмите на ссылку в описании, чтобы начать свою бесплатную пробную версию сегодня. Давайте рассмотрим два основных подхода к масштабированию: вертикальный и горизонтальный. И мы также увидим, почему горизонтальное масштабирование обычно более подходит для высоконагруженных приложений. Сначала у нас есть вертикальное масштабирование, или иногда его называют масштабированием вверх. Это просто означает, что мы добавляем больше ресурсов к нашему существующему серверу, то есть ОЗУ, ЦП или любые другие ресурсы, которые могут помочь нам справиться с большим трафиком. И этот подход прост и хорошо работает для приложений с низким или умеренным трафиком. Однако у него есть свои ограничения: во-первых, ограничения ресурсов. Существует жесткий предел тому, сколько вы можете добавить к одному серверу, и в конечном итоге вы достигнете предела тому, сколько вы можете обновить свой новый сервер. И вторая причина — отсутствие избыточности. Это означает, что если этот сервер выйдет из строя, у вас не будет других серверов для обслуживания ваших пользователей. Это означает, что ваше все приложение выйдет из строя вместе с вашим единственным сервером. С другой стороны, у нас есть горизонтальное масштабирование, которое иногда также называют масштабированием вширь. В случае горизонтального масштабирования мы просто добавляем больше серверов для распределения нагрузки. Таким образом, вместо одного сервера мы можем реплицировать и иметь три таких же сервера. И теперь мы можем распределить эту нагрузку между этими серверами, вместо того чтобы обрабатывать все на одном сервере. Как правило, это более подходит для крупномасштабных приложений, поскольку оно обеспечивает более высокую отказоустойчивость. И более высокая отказоустойчивость означает, что если один из наших серверов выйдет из строя, у нас все равно останется два доступных сервера. Таким образом, эти два сервера могут продолжать обслуживать наших пользователей, пока второй сервер восстанавливается после сбоя. И это также обеспечивает лучшую масштабируемость, потому что вы можете просто добавлять больше серверов по мере необходимости. Вместо трех вы можете добавить четвертый, который будет обрабатывать новый входящий трафик. Но как реализовать горизонтальное масштабирование в случае одного сервера? Мы знаем, что все наши клиентские запросы шли на один сервер, будь то из мобильного приложения или с настольного компьютера. Но что, если теперь у нас есть три сервера для обработки всей нагрузки? Как распределить клиентские запросы? Допустим, наше мобильное приложение делает запрос. Откуда мы знаем, куда должен идти этот запрос? Должен ли он идти на сервер один или сервер два или на сервер три, и кажется, что нам нужно что-то посередине, что будет направлять трафик на соответствующие серверы, и эта часть посередине называется балансировщиком нагрузки. Мы используем балансировщики нагрузки для распределения трафика между несколькими серверами. Например, здесь у нас три сервера. Сервер один, два и три. Всякий раз, когда у нас есть новый запрос от клиентов, балансировщик нагрузки решает, где у нас наименьшая нагрузка, а затем перенаправляет трафик на этот сервер. И он также контролирует отказоустойчивость, что означает, что если один из наших серверов выйдет из строя, например, сервер три, он перестанет отправлять трафик на первый сервер, поскольку он больше не доступен. И он будет отправлять весь трафик на сервер 2 и 1, пока сервер 3 снова не станет доступен. И это также может сделать наше приложение более масштабируемым, потому что мы можем добавить новый четвертый сервер и любые другие серверы, которые мы хотим. И этот балансировщик нагрузки обеспечит равномерное распределение всего трафика. Итак, это два основных подхода к масштабированию: вертикальное и горизонтальное. В случае вертикального масштабирования мы просто добавляем больше ресурсов к нашему тому же серверу. Но в случае горизонтального масштабирования мы добавляем больше пользователей к нашей базе серверов. А затем мы используем балансировщик нагрузки, который распределяет трафик между несколькими серверами. Но сейчас этот балансировщик нагрузки для нас своего рода черный ящик, потому что мы не понимаем, как он работает. Как он принимает запросы и как он распределяет трафик. Поэтому давайте рассмотрим это в следующем уроке и посмотрим, как это работает на самом деле и какие стратегии мы используем в балансировке нагрузки. Балансировщики нагрузки распределяют входящий трафик между несколькими серверами, одновременно гарантируя, что ни один сервер не несет слишком большой нагрузки. Но как это на самом деле происходит и как работает логика распределения входящего трафика? Чтобы лучше понять балансировщики нагрузки, давайте рассмотрим семь стратегий и алгоритмов, которые обычно используются в балансировке нагрузки. Начнем с round robin, который является одним из самых популярных алгоритмов. Это в основном потому, что это самая простая форма балансировки нагрузки, где каждый сервер в пуле получает запрос в последовательном вращающемся порядке, что, по сути, означает, что первый запрос, который он получает, он направляет его на первый сервер, а следующий запрос пойдет на второй сервер, а третий — на первый сервер, и когда последний сервер достигнут, в данном случае это сервер три, он перенаправляет его обратно на первый сервер, а затем снова на второй сервер и так далее. Это хорошо работает для серверов с одинаковыми характеристиками. То есть, если все наши три сервера имеют одинаковые возможности, то round robin будет хорошим выбором здесь. Следующий вариант — алгоритм наименьшего количества соединений. Он направляет трафик на сервер с наименьшим количеством активных соединений. Например, если у нас 10 активных соединений на сервере один, у нас девять активных соединений на сервере два, и у нас 30 активных соединений на сервере три. Если он получает новый запрос от клиента, он направит его на сервер два, потому что в данный момент у него наименьшее количество активных соединений. Итак, теперь у него будет еще одно соединение. И это особенно полезно для приложений, где у вас есть сеансы переменной длительности. То есть один из ваших сеансов может длиться 10 минут, другой — 1 минуту и так далее. И в этом случае балансировщик нагрузки учтет это и направит трафик на сервер с наименьшим количеством соединений. Третий вариант — наименьшее время отклика. Этот алгоритм больше ориентирован на отзывчивость серверов. Допустим, ваш первый сервер очень отзывчив. Второй — низкоотзывчивый, а третий — среднеотзывчивый. В этом случае балансировщик нагрузки выбирает наименьшее время отклика и наименьшее количество активных соединений. То есть сначала он попытается направить как можно больше соединений на более отзывчивый сервер. Но он также учитывает активные соединения. Допустим, этот сервер достиг 30 активных соединений. Затем он переключится на третий сервер, потому что это сервер со средней отзывчивостью. и он отправит некоторый трафик, скажем, еще 20 запросов на сервер со средней отзывчивостью, а после этого переключится на второй сервер и может отправить еще 10 запросов на этот первый сервер, пока не перенаправит их обратно на первый сервер. Это эффективно, когда цель — обеспечить самое быстрое время отклика на запросы, и у вас также есть разные серверы с разными возможностями. Четвертый вариант — алгоритм IP-хеширования, который определяет, какой сервер получает запрос на основе хеша IP-адреса клиента. Это полезно, когда вы хотите, чтобы ваши клиенты постоянно подключались к одному и тому же серверу. Допустим, клиент один делает запрос к вашему балансировщику нагрузки. Балансировщик нагрузки будет использовать IP-адрес клиента и на основе этого хеширует его и отправит на соответствующий сервер. Допустим, сервер два, и все последующие запросы клиента один будут идти к балансировщику нагрузки, и он будет использовать тот же алгоритм IP-хеширования, и на основе этого IP-адреса он снова перенаправит запросы пользователя один на сервер два. Это полезно, если для клиента важно постоянно подключаться к одному и тому же приложению. Если каждый из ваших серверов имеет некоторую информацию о подключенных к нему клиентах, в этом случае IP-хеширование — хороший выбор. Затем есть также взвешенные алгоритмы. Это варианты вышеуказанных методов, которые также могут быть взвешенными. Например, у вас может быть взвешенный round robin или взвешенное наименьшее количество соединений. В этом случае серверам назначаются веса, обычно основанные на их мощности и метриках производительности. Например, если первый сервер имеет 16 ГБ ОЗУ, второй — 32, а третий — 64. На основе ОЗУ сервера и других метрик им назначаются веса, и балансировщик нагрузки учитывает это при перенаправлении трафика. Сначала он попытается направить как можно больше соединений на третий сервер, потому что он имеет больший вес, то есть более высокую производительность, а затем попытается направить другой трафик на сервер два, а затем последняя и небольшая часть пойдет на сервер один. Существуют также географические алгоритмы, которые являются алгоритмами, основанными на местоположении, которые направляют запросы на сервер, географически ближайший к пользователю. Допустим, это приложение для пользователей из США. Поэтому большинство пользователей подключаются к этому приложению из США. Но у нас также есть часть пользователей, которые подключаются из Европы. И в нашем пуле серверов у нас может быть один сервер, расположенный в Восточной части США, другой сервер, расположенный в Западной части США. И последний сервер может быть расположен где-то в Европе для небольшой базы пользователей, расположенных в Европе. Поэтому, если пользователь приходит из Европы и делает запрос к этому балансировщику нагрузки, он перенаправит этого пользователя на сервер в Европе. Или, если пользователь приходит из США и делает запрос к этому балансировщику нагрузки, он проверит местоположение этого пользователя из США на основе его IP-адреса, а затем перенаправит либо на Восточную, либо на Западную часть США. Этот тип балансировки нагрузки полезен для глобальных сервисов, где важна минимизация задержки. И последний наиболее популярный тип — это согласованное хеширование. В этом случае мы используем хеш-функцию для распределения данных по различным узлам. У нас есть хеш-функция внутри балансировщика нагрузки, и мы обычно представляем пространство хеширования вместе с этим, которое образует кольцо хеширования, похожее на круг. Эта хеш-функция образует круг, где у нас есть серверы, например, сервер 1, 2 и 3, которые расположены перед этим балансировщиком нагрузки. Поэтому всякий раз, когда приходит новый запрос от пользователя, эта хеш-функция берет IP-адрес этого пользователя, а затем на основе этого находит этого пользователя на этом кольце хеширования. Допустим, он находит его где-то здесь, а затем, в зависимости от того, к какому серверу эта точка ближе всего, например, в данном случае она ближе к серверу 2, он перенаправляет трафик на этот сервер. Это несколько более сложный способ балансировки нагрузки, но он также гарантирует, что один и тот же клиент постоянно подключается к одному и тому же серверу, как и в случае с IP-хешированием. Мы также говорили о том, что всякий раз, когда сервер выходит из строя, этот балансировщик нагрузки гарантирует, что трафик не будет перенаправлен на этот сервер. Но как он вообще узнает, что этот сервер недоступен? Для этого большинство балансировщиков нагрузки поставляются с функциями проверки работоспособности, что означает, что они постоянно отслеживают серверы, отправляя запросы на проверку работоспособности на все эти серверы. И у них есть информация о том, какие серверы онлайн. Допустим, первые три сервера доступны, а какие офлайн, что означает, что четвертый сервер офлайн. Поэтому, когда он обнаруживает сбой при проверке работоспособности, он знает, что четвертый сервер больше не доступен. И на основе этой информации, если следующий запрос приходит от клиента, он не будет перенаправлять их на четвертый сервер до тех пор, пока проверка работоспособности снова не станет успешной, и он не узнает, что четвертый сервер снова онлайн. А теперь давайте посмотрим на некоторые примеры балансировщиков нагрузки. И что это такое на самом деле? Как мы их реализуем? Во-первых, у нас есть программные балансировщики нагрузки. Например, Nginx, вероятно, самый распространенный тип программного балансировщика нагрузки. Он имеет другие функции и также используется как веб-сервер, но он также предлагает функциональность балансировщика нагрузки. Обычно вы устанавливаете этот Nginx на свой сервер, а затем настраиваете серверы, которые должны быть сбалансированы по нагрузке, а также алгоритм. И, как вы можете видеть, он также поставляется с проверками работоспособности, которые я упомянул. Таким образом, вы можете настроить проверки работоспособности среди ваших серверов, и тогда он будет постоянно отслеживать ваши серверы, и всякий раз, когда один из ваших серверов выходит из строя, он не будет перенаправлять трафик на этот сервер. Другой пример программного балансировщика нагрузки — HAProxy, который является программным обеспечением с открытым исходным кодом, которое вы снова можете установить на свой сервер и настроить по своему усмотрению. Но помимо программных балансировщиков нагрузки, у нас также есть аппаратные балансировщики нагрузки. Например, у нас есть балансировщик нагрузки F5, который является широко используемым аппаратным балансировщиком нагрузки, известным своей высокой производительностью и набором функций. Далее у нас есть Citrix, который также предлагает функциональность балансировки нагрузки. И опять же, это аппаратный тип балансировщика нагрузки. Но если вы не хотите настраивать все это самостоятельно на своем сервере или в виде оборудования, то более простыми решениями являются облачные балансировщики нагрузки. Например, AWS предлагает эластичную балансировку нагрузки. И если у вас также настроены серверы в AWS, то довольно легко настроить это с вашими серверами. И вы также можете увидеть в преимуществах, что он автоматически поставляется с безопасностью, автоматическим масштабированием, что означает, что он автоматически добавит новые серверы в пул, если спрос на ваше приложение увеличится. И он также поставляется с мониторингом, который такой же, как и проверки работоспособности. Так что вам не нужно настраивать это самостоятельно. И другие примеры, аналогичные AWS, — это балансировщик нагрузки Azure и балансировка нагрузки Google Cloud. Теперь давайте поговорим о концепции, называемой единой точкой отказа в проектировании систем. Это одна часть вашей всей системы, которая при сбое приведет к отказу всей системы. Проще говоря, это любой компонент, который может привести к сбою всей системы, когда он перестает работать. Например, если представить эту настройку, когда клиенты подключаются к нашему балансировщику нагрузки, а затем балансировщик нагрузки распределяет их по API, а затем у нас есть одна база данных, которая используется для всех API-серверов. База данных здесь — один из примеров единой точки отказа. Всякий раз, когда эта база данных выходит из строя, все эти API не смогут подключиться к базе данных, и из-за этого все они также не будут функционировать должным образом, и наши клиенты не смогут получать никаких ответов от серверов. Таким образом, наличие единых точек отказа в вашей системе проблематично, потому что они могут создавать уязвимости. Первый очевидный недостаток — это надежность, потому что один сбой, такой как сбой этой базы данных, может привести к отказу всей системы, что может означать бизнес-потери, поскольку пользователи не могут получить доступ к нашей платформе. Возможно, они также не могут получить доступ к странице оформления заказа или любым другим частям системы, что может привести к убыткам в бизнесе. Это также проблема масштабируемости, потому что системы, имеющие единые точки отказа, подобные этой, часто испытывают трудности с масштабированием, поскольку каждый компонент добавляет риск отказа этой единой части. И последняя часть — это также проблема безопасности, потому что если у вас есть единая точка отказа в вашей системе, такая как балансировщик нагрузки, злоумышленники могут скомпрометировать эту точку, отправляя на нее огромный трафик, и если она выйдет из строя, вся система рухнет. Мы поговорим о том, как избежать единых точек отказа базы данных в разделе баз данных. Но в этом разделе мы можем рассмотреть, как избежать того, чтобы балансировщики нагрузки становились единой точкой отказа. Потому что сейчас у нас настроен только один балансировщик нагрузки. И если этот балансировщик нагрузки выйдет из строя, то все наши пользователи не смогут получить доступ к этой точке, и они также не смогут получить доступ к нашим API. Первая стратегия — добавить избыточность в нашу систему. Это означает, что мы можем использовать более одного балансировщика нагрузки. И, например, если второй балансировщик нагрузки выйдет из строя, пользователи не смогут подключиться к этому балансировщику нагрузки. Но в этом случае мы можем перенаправить весь трафик на первый. И тогда этот первый балансировщик нагрузки будет балансировать нагрузку между этими серверами. И мы будем отслеживать работоспособность этого второго балансировщика нагрузки. И всякий раз, когда он снова заработает и снова станет доступен, мы также перенаправим 50% трафика на второй балансировщик нагрузки. Другая стратегия — использовать проверки работоспособности и мониторинг самих балансировщиков нагрузки. Как мы видели, балансировщик нагрузки может выполнять проверки работоспособности серверов и проверять, онлайн или офлайн наши серверы. Мы можем использовать ту же стратегию для балансировщиков нагрузки, и мы можем постоянно проверять их работоспособность, и всякий раз, когда один из наших балансировщиков нагрузки выходит из строя, мы будем знать, что не следует перенаправлять на него трафик до тех пор, пока он снова не заработает. И третий распространенный тип — это самовосстанавливающиеся системы, что означает, что мы снова отслеживаем работоспособность нашего балансировщика нагрузки, и если в какой-то момент мы обнаружим, что он вышел из строя, мы заменим его новым балансировщиком нагрузки, который, по сути, является экземпляром этого же балансировщика нагрузки, и таким образом мы не вызовем никаких перебоев, и наши клиенты смогут подключиться к этому новому балансировщику нагрузки. Добро пожаловать в этот раздел, где вы узнаете фундаментальные принципы проектирования API, которые позволят вам создавать эффективные, масштабируемые и поддерживаемые интерфейсы между программными системами. Вот что мы будем охватывать в этом уроке. Мы начнем с того, что такое API и какова их роль в системной архитектуре. Затем мы рассмотрим три наиболее часто используемых стиля API: REST, GraphQL и gRPC. Мы обсудим четыре основных принципа проектирования, которые делают API отличными, а также как протоколы приложений влияют на решения по проектированию API. Мы также рассмотрим процесс проектирования API. Таким образом, начиная с этапа проектирования и заканчивая этапом разработки и развертывания. Итак, мы увидим, как выглядит этот процесс. Итак, давайте начнем с понимания того, что такое API. API расшифровывается как Application Programming Interface (интерфейс программирования приложений), который определяет, как программные компоненты должны взаимодействовать друг с другом. Допустим, с одной стороны у вас есть клиент, который является либо мобильным телефоном, либо браузером этого пользователя, а с другой стороны — сервер, который будет отвечать на запросы. Таким образом, API здесь — это просто контракт, который определяет эти условия: какие запросы могут быть сделаны. Таким образом, он предоставляет нам интерфейс о том, как делать эти запросы, то есть какие конечные точки у нас есть, какие методы мы можем использовать и так далее. Также, какие ответы мы можем ожидать от этого сервера для конкретной конечной точки. Итак, прежде всего, это механизм абстракции, потому что он скрывает детали реализации, раскрывая функциональность. Например, мы можем сделать запрос на сохранение данных пользователя на этом сервере. Но нас совершенно не волнует, как работает логика за кулисами внутри этого сервера. Поэтому нас интересует только интерфейс, предоставляемый через этот API, и мы используем только эту конечную точку и сохраняем пользователя, даже не зная деталей реализации, и он также устанавливает границы обслуживания, потому что он определяет четкие интерфейсы между системами и компонентами. Таким образом, это позволяет нам иметь несколько серверов. У нас может быть один сервер, ответственный за управление пользователями. У нас может быть другой, ответственный за некоторые другие записи, скажем, за управление постами и так далее. Таким образом, это позволяет различным системам взаимодействовать независимо от их базовой реализации, такой как клиентские браузеры с серверами или серверы с другими серверами и так далее. Теперь давайте сосредоточимся на наиболее важных стилях API, с которыми вы столкнетесь на этапе проектирования. Это RESTful, GraphQL и gRPC. Наиболее распространенным из них является REST, что означает Representational State Transfer (передача состояния представления). Эти типы API используют ресурсный подход, используя методы HTTP в качестве протокола. Одним из преимуществ REST API является то, что они без состояния, что означает, что каждый запрос содержит всю информацию, необходимую для его обработки, и нам не нужны никакие предыдущие запросы для обработки текущего запроса. И он использует стандартные методы протокола HTTP. которые: GET для получения данных, POST для сохранения данных, PUT или PATCH для обновления данных и DELETE для удаления данных. Таким образом, исходя из своих характеристик, REST наиболее часто используется в веб- и мобильных приложениях. Далее у нас есть GraphQL, который является вторым по распространенности стилем API после REST API. GraphQL — это язык запросов, который позволяет клиентам запрашивать именно то, что им нужно. Это означает, что он поставляется с одной конечной точкой для всех операций, и мы можем выбирать, что мы ожидаем получить от этого API, предоставляя полезную нагрузку в запросе, а операции здесь называются запросами (query), когда мы извлекаем данные, или мутациями (mutation), когда мы обновляем данные. Таким образом, это эквивалент PUT, PATCH или POST в RESTful API. И также есть подписка (subscription) в операциях, которая предназначена для связи в реальном времени. Преимущество GraphQL API заключается в том, что он позволяет нам минимизировать количество круговых путей. Допустим, нам нужны некоторые данные, для которых в RESTful API нам пришлось бы сделать три запроса, чтобы получить все эти данные. В случае GraphQL мы можем сделать один запрос и получить все эти данные, избегая ненужных двух запросов, которые нам пришлось бы сделать в RESTful. И благодаря этому это рекомендуемый вариант для сложных пользовательских интерфейсов. Поэтому, где бы у вас ни были сложные пользовательские интерфейсы, где на одной странице вам могут понадобиться разные данные, на другой странице — другие сложные вложенные данные. В этих случаях GraphQL — лучший выбор, чем RESTful API. И последний вариант — gRPC. Я бы сказал, что это наименее распространенный из этих трех. gRPC — это высокопроизводительный RPC-фреймворк, который использует Protocol Buffers для связи. Методы в gRPC определяются как RPC в proto-файлах, и он поддерживает потоковую и двунаправленную связь. Это отличный подход для микросервисов, особенно для внутренней связи между системами, поскольку он более эффективен при работе между серверами по сравнению с GraphQL или RESTful API. Таким образом, разница между REST, GraphQL и gRPC API довольно ясна, но давайте также проясним реальную разницу между REST и GraphQL API на примерах. Таким образом, как вы видели, REST поставляется с ресурсными конечными точками. Например, здесь, если мы посмотрим на эти запросы, вы увидите, что ресурс здесь — пользователи. Поэтому вы всегда ожидаете увидеть конечную точку пользователей или конечную точку подписчиков или, скажем, конечную точку постов. Таким образом, это ресурсный подход, и иногда нам может потребоваться сделать несколько запросов для получения связанных данных. Как вы можете видеть здесь, нам нужны, скажем, детали пользователя, но нам также нужны его посты и подписчики. Поэтому в этом случае нам нужно сделать три запроса, чтобы получить все эти данные, и он использует методы HTTP для определения операций. Как вы можете видеть, это HTTP-конечные точки, и мы используем метод GET конкретно, а структуры ответов фиксированы, то есть если вы получили один ответ для этого конкретного пользователя, в следующий раз вы можете ожидать точно такую же структуру ответа. Возможно, некоторые данные будут изменены, но структура всегда остается прежней. И он также предоставляет явное версионирование. Поэтому, как вы можете видеть, он поставляется с v1 для API версии 1, затем позже, если он получил крупное обновление, это станет v2 и так далее. И вы можете использовать заголовки в запросах для использования HTTP-кэширования в RESTful API. Теперь, если сравнить это с GraphQL API, он поставляется с одной конечной точкой для всех операций. Поэтому чаще всего это /graphql или / какой-то API-эндпоинт, который обычно используется для всех операций, и в этом случае мы будем использовать один запрос, чтобы получить точные данные, которые нам нужны, и мы будем использовать язык запросов GraphQL. Вот как выглядит язык запросов. Как вы можете видеть, мы начинаем с запроса, а затем определяем, что нам нужно. Например, нам нужен пользователь с ID 1 2 3. Затем нам нужно имя пользователя, посты, а затем мы определяем, что нам нужно от постов. Возможно, нам нужен только заголовок и содержание, и ничего больше. А также подписчики и что нам нужно от подписчиков, возможно, только имена. Таким образом, это позволяет нам быть более эффективными в наших запросах по сравнению с RESTful API, где нам пришлось бы сделать три запроса для получения тех же данных. Это означает, что клиент должен указывать структуру ответа, и в этом случае эволюция схемы происходит без версионирования. Поэтому здесь, как вы видели, это с v1, v2 и так далее. В этом случае схема обычно эволюционирует без версионирования. Но также существует распространенный шаблон для начала версионирования полей. Например, у вас могут быть followers v2, и это будет второй тип схемы followers. Но вы также можете обойтись без версионирования. Поэтому вы можете просто начать изменять followers или posts, если вы уверены, что нет других клиентов, использующих ваш старый API, и в этом случае вы можете использовать кэширование на уровне приложения вместо HTTP-кэширования. Теперь давайте обсудим основные принципы проектирования, которые позволят нам создавать согласованные, простые, безопасные и производительные API. В конечном итоге лучший API — это тот, который мы можем использовать, даже не читая документацию. Например, если вы видели предыдущие конечные точки в разделе пользователей, вы видите, что у нас есть /users/123, и очевидно, что мы ожидаем получить детали пользователя этого конкретного пользователя. И если вы сделаете запрос, например, к этой конечной точке для получения деталей пользователя, но затем обнаружите, что она также обновляет каких-то подписчиков или что-то еще при выполнении этого запроса, то, очевидно, это очень плохой тип API, поскольку мы не ожидали, что он будет выполнять такие операции. Поэтому, прежде всего, хороший API должен быть последовательным, что означает, что он должен использовать последовательные имена, регистр и шаблоны. Например, если вы используете camelCase в одной из конечных точек, скажем, у вас есть user details, и вы делаете это в camelCase, но в другом случае вы делаете это с помощью snake_case, например, user/details, то это не является общим и не является последовательным. Второй ключевой принцип — сохранять простоту и сосредоточиться на основных сценариях использования и интуитивно понятном дизайне. Таким образом, вы должны минимизировать сложность и стремиться к дизайну, который разработчики могут быстро понять, даже не читая документацию. И простота снова сводится к этому: лучший API — это тот, который разработчики могут использовать, даже не читая документацию. Далее, очевидно, он должен быть безопасным. Поэтому у вас должен быть какой-то вид аутентификации и авторизации между пользователями. Также, если у вас есть входные данные, то вам нужно убедиться, что они проверены, и вы также должны применять ограничение скорости. Итак, это самые основные вещи, которые вам нужно сделать, чтобы обеспечить безопасность ваших API. И последний столп — производительность. Поэтому вы должны проектировать с учетом эффективности, используя соответствующие стратегии кэширования, пагинацию. Если у вас большой объем данных, скажем, тысячи постов, вы не хотите извлекать все это каждый раз, когда они делают запрос на получение постов. Поэтому у вас всегда должна быть пагинация с некоторым ограничением и смещением. Также полезная нагрузка, то есть данные, которые вы будете отправлять обратно, должна быть минимизирована, а также, когда это возможно, вы должны сократить количество круговых путей. Поэтому, если у вас есть возможность отправить небольшие данные вместе с запросом одной из конечных точек, лучше сделать это, если вы знаете, что будете их использовать, вместо того чтобы делать еще одну конечную точку для запроса тех же данных. Теперь каждый из этих API использует разные протоколы, и мы узнаем больше об этом в следующем уроке. Но, по сути, ваш выбор протокола будет фундаментально формировать ваши варианты дизайна API. Например, особенности протокола HTTP напрямую обеспечивают RESTful возможности. Поэтому имеет смысл использовать HTTP вместе с RESTful API, поскольку он также предоставляет коды состояния, и они отлично подходят для использования с CRUD-операциями, которые вы будете иметь в RESTful API. С другой стороны, WebSockets, который является другим типом протокола, обеспечивает передачу данных в реальном времени и также двунаправленные API. Поэтому его можно использовать вместе с API реального времени, где бы вам ни понадобилось чат-приложение или потоковое видео. Это хороший пример использования WebSocket API. В случае GraphQL API вы снова будете использовать протокол HTTP вместо WebSockets или gRPC. GRPC, с другой стороны, может использоваться вместе с микросервисами в вашей архитектуре, чтобы сделать ее быстрее по сравнению с HTTP. Таким образом, ваш выбор протокола повлияет на структуру API, а также на производительность и возможности. Поэтому вам следует выбирать его, исходя из его ограничений и сильных сторон, и того, что имеет больше смысла для типа API, который вы будете разрабатывать. Теперь давайте обсудим процесс проектирования API. Все начинается с понимания требований, то есть определения основных сценариев использования и пользовательских историй, которые вам нужно будет разработать. Также определение объема и границ, потому что если это огромный API, то вы, вероятно, не будете разрабатывать все функции сразу. Поэтому вам следует ограничить его конкретными функциями, которые вы будете разрабатывать, а также тем, что выходит за рамки на данный момент. Затем вам следует определить требования к производительности и, в частности, в случае вашего API, где будут узкие места и где вам нужно убедиться, что он производителен, и вам также не следует упускать из виду ограничения безопасности. Поэтому вам следует реализовать все основные функции, такие как аутентификация, авторизация, ограничение скорости, но, возможно, и некоторые другие вещи, в зависимости от API, который вы будете разрабатывать. Когда дело доходит до подходов к проектированию, есть несколько способов сделать это. Первый — это подход сверху вниз, когда вы начинаете с высокоуровневых требований и рабочих процессов. Это более распространено на собеседованиях, где вам дают требования к тому, каким будет API, а затем вы начинаете определять, какими будут конечные точки, какими будут операции и так далее. Но есть также подход снизу вверх, когда, если у вас уже есть существующие модели данных и возможности, вы должны проектировать API на их основе. Поэтому это более распространено, когда вы работаете в компании, и у них уже есть свои модели данных и возможности своих API. Поэтому вам следует учитывать это при проектировании API. И у нас также есть подход "контракт в первую очередь", когда вы определяете контракт API перед реализацией, то есть как должны выглядеть запросы и как должны выглядеть ответы. И это больше похоже на подход сверху вниз, и это также часто используется на собеседованиях. Когда дело доходит до управления жизненным циклом API, он начинается с этапа проектирования, где вы проектируете API, обсуждаете требования и ожидаемые результаты API, и только после этого вы можете начать разработку и, возможно, локальное тестирование вашего API. После этого вы обычно развертываете и отслеживаете его. Поэтому вы проводите еще несколько тестов, но теперь на этапе тестирования или в продакшене. Но затем также наступает этап обслуживания. И именно поэтому важно разрабатывать его с учетом простоты. Поэтому вам или другим разработчикам будет легче поддерживать его в будущем. И, наконец, API также проходят этапы устаревания и вывода из эксплуатации. Поэтому некоторые API в конечном итоге устаревают, потому что может появиться новая версия API, которую вы должны использовать. Или, скажем, вы переходите с v1 на v2. Это также этап устаревания API v1. Поэтому разработка API — это не только этап разработки, как вы можете предположить, это не просто кодирование. Поэтому большая часть этого — проектирование, а также поддержание его в рабочем состоянии, и в конечном итоге вам, возможно, придется вывести его из эксплуатации. Итак, давайте подведем итоги и посмотрим, каковы наши следующие шаги. Мы узнали, что такое API, и о трех наиболее распространенных типах стилей API: RESTful, GraphQL и gRPC. Мы рассмотрели четыре ключевых принципа, которые будут направлять нас при эффективном создании дизайнов API. И теперь вы также понимаете, как выбор протокола повлияет на дизайн вашего API, а также на весь процесс проектирования API от начала до конца. Но мы не обсуждали ограничения и сильные стороны этих протоколов API. Поэтому в следующем уроке мы узнаем все об API-протоколах, которые мы можем использовать при проектировании API, и какой из них нам следует выбрать, исходя из требований нашего API. Выбор неправильного протокола для нашего API может привести к узким местам в производительности, а также к ограничениям в функциональности. Поэтому нам нужно сначала понять эти протоколы, что позволит нам создавать API, которые соответствуют нашим конкретным пользовательским требованиям к задержке, пропускной способности, а также моделям взаимодействия. Поэтому в этом уроке мы рассмотрим роль API-протоколов в сетевом стеке. Два фундаментальных протокола, HTTP и HTTPS, а также их связь с API.

Также существует еще один распространенный тип протокола — WebSocket, предназначенный для связи в реальном времени. Мы также рассмотрим Advanced Message Queuing Protocol (AMQP), который часто используется для асинхронной связи. И, наконец, мы рассмотрим gRPC, который представляет собой удаленный вызов процедур от Google и также является распространенным типом протокола, часто используемым между серверами. Давайте начнем с понимания прикладных протоколов в сетевом стеке. Протоколы прикладного уровня находятся на вершине сетевого стека, опираясь на такие протоколы, как TCP и UDP, которые находятся на транспортном уровне. Эти протоколы прикладного уровня определяют форматы и структуры сообщений, а также шаблоны запросов-ответов и управление соединениями и обработку ошибок. Ниже мы имеем множество других уровней, таких как сетевой уровень, уровень канала передачи данных или даже физические уровни. Но при создании API нас в основном интересуют протоколы уровня API, такие как HTTP, HTTPS, WebSocket и так далее. Самым распространенным типом протокола и основой веб-API является HTTP, что означает Hypertext Transfer Protocol (протокол передачи гипертекста). Это типичное взаимодействие между клиентом и сервером, когда они взаимодействуют через HTTP. Как вы можете видеть, клиент всегда отправляет запрос и определяет метод, который может быть GET, POST или другими методами, а также определяет URL ресурса, который может быть, например, `/api/products`. Допустим, они запрашивают данные для этого конкретного идентификатора продукта, и они также определяют версию протокола HTTP, которую они используют. Они также определяют хост, который является доменом вашего сервера, где осуществляется доступ к информации, и обычно они также аутентифицируются перед доступом к любым ресурсам. Таким образом, это может быть либо токен-носитель (bearer token), либо базовая аутентификация и так далее. Итак, после того как запрос аутентифицирован на сервере, он получает ответ, который имеет схожий формат и является ответом HTTP. Таким образом, вы получаете версию HTTP, которая снова совпадает с запрошенной, и код состояния, который может быть 200, если все прошло успешно, или 400, если была ошибка клиента, или 500, если ошибка произошла на сервере, и так далее. Вы получаете тип содержимого, который обычно может быть `application/json`, но также может быть статичной веб-страницей или чем-то еще. И есть много других заголовков, которыми вы можете управлять, например, управление кэшем. Вы можете использовать заголовок `Cache-Control` или некоторые другие свойства. Но это основные вещи, которые вы заметите в циклах запросов-ответов HTTP. Теперь, что касается методов, у вас есть GET для получения данных, POST для создания данных на сервере, PUT или PATCH для частичного или полного обновления данных и DELETE для удаления данных с сервера. И когда речь идет о кодах состояния, которые получает сервер. У вас есть серия 200, которые являются успешными случаями. У вас есть 300 для перенаправления. 400 означает, что клиент совершил ошибку в запросе. Так что это проблема со стороны клиента, или 500, что означает, что сервер совершил ошибку или произошла какая-то ошибка на сервере, что означает, что это проблема на этом сервере. И это распространенные заголовки, такие как `Content-Type`, который обычно определяется сервером, но также и клиентом, `Authorization` для выполнения запроса и авторизации на сервере. Заголовки `Accept`, `Cache-Control`, `User-Agent`, и есть еще заголовки, но это распространенные. Затем у нас также есть HTTPS, который, по сути, является тем же протоколом HTTP, но с некоторой формой шифрования TLS или SSL, что означает, что наши данные теперь защищены при передаче, когда мы делаем запросы. Таким образом, он добавляет уровень безопасности через эти сертификаты TLS или SSL и шифрование, и защищает данные при передаче. Преимущества HTTPS, очевидно, заключаются в том, что ваши данные зашифрованы при передаче. Он обеспечивает целостность данных, и вы также аутентифицируете пользователей перед предоставлением каких-либо данных, а также добавляет преимущества для SEO. И у вас есть много рисков, когда вы используете только HTTP без какого-либо шифрования. Поэтому золотым стандартом является всегда использование HTTPS на серверах. Следующий тип протоколов — это WebSocket. В то время как HTTP отлично подходит для шаблонов запросов-ответов, иногда HTTP имеет ограничения. Например, предположим, вы извлекаете какие-то данные. Допустим, это чат пользователя. У вас есть клиент и сервер. На стороне клиента у вас есть чат пользователя, а на сервере у вас есть сообщения между двумя пользователями. Когда один из пользователей отправляет сообщение другому, он отправляет запрос на сервер, чтобы уведомить о том, что сообщение было отправлено. И он получает ответ от сервера, возможно, сообщения от других пользователей, если они есть. И тогда в следующий раз, если вам нужно узнать, есть ли у вас новые сообщения, вам нужно снова сделать еще один запрос на сервер, и, возможно, у вас нет новых сообщений. Таким образом, вы получите пустой ответ без новых данных. Так что это был, по сути, ненужный цикл запросов-ответов, и вы можете запросить через некоторое время, скажем, через 1 минуту, и получить ответ. Теперь у вас есть сообщения, но они также могут быть пустыми снова. Так что этот способ не идеален для связи в реальном времени. Как вы можете видеть, у вас увеличивается задержка. Вы тратите некоторую пропускную способность, делая запросы, которые пусты, и вы также используете ресурсы сервера без необходимости делать запросы к этому серверу. И для таких случаев у нас есть WebSocket, который решает эту проблему. Итак, в WebSocket обычно происходит рукопожатие, которое происходит в первом запросе. И теперь у вас есть двусторонняя связь между клиентом и сервером. Это означает, что после установления рукопожатия сервер может самостоятельно решить отправлять данные клиенту. Допустим, теперь у вас есть два новых сообщения на сервере. Таким образом, сервер может решить отправить эти сообщения клиенту, даже если клиент их не запрашивал. Но клиент все еще может запрашивать данные. Так что, если клиенту нужны какие-то внешние данные или больше данных с сервера, он все равно может делать запросы. Но теперь сервер также может самостоятельно отправлять данные клиенту. Вот что обеспечивает передачу данных в реальном времени с минимальной задержкой. Как только у вас появятся новые данные на сервере, он отправляет новые данные клиенту, а также снижает использование пропускной способности, позволяя двустороннюю связь. В модели клиент-сервер с HTTP вы бы делали, скажем, новые запросы каждые 5 или 10 секунд, чтобы проверить, есть ли новые данные на сервере. Но в этом сценарии вы не делаете никаких дополнительных запросов, кроме первого. И теперь, когда появляются новые данные, сервер будет их отправлять. И когда нет данных для запроса, вам не нужно делать ненужные запросы к серверу. Следующий очень распространенный тип протокола — это Advanced Message Queuing Protocol (AMQP), который является корпоративным протоколом обмена сообщениями, используемым для постановки сообщений в очередь и гарантированной доставки. В этой настройке у вас обычно есть продюсер, который может быть веб-сервисом, платежной системой или чем-то подобным. А с другой стороны, у вас есть потребитель, который может быть обработчиком платежей, системами уведомлений и тому подобным. Таким образом, продюсер публикует сообщения в брокер сообщений. И вот где у вас есть Advanced Message Queuing Protocol. У вас есть очереди посередине. Допустим, одна из этих очередей предназначена для обработки заказов. Таким образом, всякий раз, когда размещается новый заказ, продюсер публикует сообщение в эту очередь. И тогда, когда потребитель свободен, он может извлекать сообщения из этой очереди и начинать обновлять инвентарь и данные в базе данных. Это позволяет потребителю извлекать данные только тогда, когда у него есть возможность. И когда этот потребитель занят другими задачами, он оставляет сообщение в очереди, а затем позже, когда у него появится свободная возможность, он извлечет сообщение и начнет обновлять данные. И когда речь идет о типах обмена, у вас есть прямой обмен один на один, или веерный (fan-out), или тематическая (topic-based) связь. И мы подробнее рассмотрим это, когда перейдем к разделу о постановке сообщений в очередь. Другой распространенный тип протокола — gRPC, который работает с Protocol Buffers. Это высокопроизводительная RPC-платформа, изобретенная Google, и она использует HTTP/2 для транспорта, то есть вторую версию HTTP. Это означает, что клиенты должны поддерживать HTTP/2, иначе это не может быть использовано между клиентом и сервером, но именно поэтому он чаще всего используется между серверами. Таким образом, обычно клиент — это другой сервер, и у нас есть другие микросервисы, взаимодействующие друг с другом с помощью этой gRPC-платформы. Он в основном использует Protocol Buffers и также поставляется со встроенными возможностями потоковой передачи, потому что он использует HTTP/2. Итак, это наиболее распространенные типы API-протоколов. Существует гораздо больше, но обычно в 90% случаев вы увидите только эти протоколы. И при выборе правильного вы должны в основном учитывать шаблоны взаимодействия. Обычно по умолчанию вы выбираете HTTP. Если это просто цикл запросов-ответов, но если вы создаете что-то вроде чата в реальном времени или какой-либо связи в реальном времени, тогда вам нужно перейти на WebSocket. Выбор также зависит от требований к производительности. Так что, если у вас есть несколько серверов, микросервисы, взаимодействующие друг с другом, и нет возможности использовать gRPC, например, тогда вы можете выбрать его, чтобы повысить производительность и скорость связи. Но это также зависит от совместимости клиента. Например, большинство браузеров не поддерживают последнюю версию HTTP. Вот почему gRPC не так уж распространен для связи между браузером и сервером. Это также зависит от размера полезной нагрузки, то есть объема данных и кодирования, потребностей в безопасности, основанных на аутентификации, шифровании и так далее, а также от опыта разработчика. Таким образом, инструментарий и документация, а также опыт разработчика, потому что вы в основном будете работать с этим API, и он должен иметь хорошую документацию и инструментарий, чтобы вы могли полностью работать с этим типом API-протокола. Итак, чтобы подытожить, мы рассмотрели роль прикладных протоколов в сетевом стеке. HTTP и HTTPS, которые являются наиболее фундаментальными типами протоколов. WebSocket для связи в реальном времени. AMQP, что означает Advanced Message Queuing Protocol, который позволяет нам иметь асинхронную связь и добавлять очереди сообщений между потребителем и продюсером, а также gRPC, что означает Google Remote Procedure Call. И основное преимущество этого заключается в том, что это высокопроизводительная RPC-платформа, использующая HTTP/2 для транспорта. Итак, мы обсудили прикладной уровень, который включает эти протоколы, которые мы обычно используем для создания API. Но мы еще не знаем о транспортном уровне, который включает TCP и UDP. Итак, в следующем уроке мы обсудим этот уровень и поймем, какой из этих транспортных уровней, TCP или UDP, является лучшим выбором в зависимости от API, который мы создаем. Большинство разработчиков работают с API, но никогда не задумываются о том, что на самом деле доставляет эти пакеты, как происходит запрос от клиента к серверу и как этот запрос проходит через Интернет. Вот где на сцену выходит второй уровень в модели OSI, который является транспортным уровнем, содержащим TCP и UDP. Оба являются протоколами транспортного уровня, что означает, что они обрабатывают перемещение данных с одной машины на другую по сети, но оба делают это очень по-разному. В этом уроке мы узнаем об этих протоколах транспортного уровня. Мы начнем с TCP, который является надежной, но более медленной версией. Затем мы узнаем об UDP, который, короче говоря, является более быстрой и ненадежной версией TCP, и мы сравним оба и решим, какой из них нам нужно выбрать в зависимости от требований API. Давайте начнем с TCP, что означает Transmission Control Protocol (протокол управления передачей). Думайте об этом как об отправке пакета с квитанцией, отслеживанием и также требуемой подписью. Итак, когда вы отправляете пакеты через Интернет, вы обычно не отправляете их все сразу. Иногда данные больше. Допустим, они разделены на три части. Итак, вам нужно отправить их отдельно. Первая часть, вторая часть и третья часть. Итак, в этом случае TCP гарантирует доставку всех этих трех частей. Если один из этих пакетов потерян или прибывает не по порядку, TCP отправит его повторно или переупорядочит. Это также ориентировано на соединение, что означает, что перед отправкой каких-либо данных оно выполняет трехстороннее рукопожатие, которое устанавливает соединение между клиентом и сервером. Оно также упорядочивает эти пакеты. Допустим, клиент получает первый пакет первым, затем третий пакет, затем второй пакет. Он гарантирует, что они будут переупорядочены в первый, второй и третий. Это, конечно, добавляет накладные расходы, но обеспечивает точность и надежность. Вот почему API, связанные с платежами, аутентификацией или пользовательскими данными, всегда используют TCP. С другой стороны, у нас есть UDP, что означает User Datagram Protocol (протокол пользовательских дейтаграмм). Он быстрый и эффективный. Но недостатком этого является то, что он не гарантирует, что все пакеты прибудут. Например, если вы отправляете четыре пакета с сервера клиенту, один из этих пакетов может быть потерян, и он не будет отправлен клиенту, и UDP не позаботится о том, чтобы он в конечном итоге был доставлен. Таким образом, нет гарантии доставки. Нет также рукопожатия, соединения или какого-либо отслеживания. Но благодаря этим компромиссам он обеспечивает более быструю передачу и имеет меньшие накладные расходы, поскольку ему не нужно гарантировать, что все пакеты доставлены или в правильном порядке. Например, в видеозвонках UDP может быть лучшим протоколом, потому что если какая-то информация была потеряна в середине или, скажем, вы разговариваете с кем-то, и у него плохое интернет-соединение, вам не нужно получать это старое соединение или старые данные о том, что он сказал, потому что вы сейчас в звонке. Так что UDP — это выбор для видеозвонков, онлайн-игр или прямых трансляций, потому что если один из этих пакетов потерян, это все равно нормально, и вам не нужно возвращаться и повторно отправлять этот пакет. Вы можете просто продолжить и отправить следующие пакеты. Вот как выглядит трехэтапное рукопожатие в TCP. Как вы можете видеть, первый шаг — клиент отправляет запрос серверу. На втором шаге сервер синхронизируется и подтверждает запрос. И на первом шаге клиент подтверждает серверу, и здесь устанавливается соединение между клиентом и сервером. И теперь они могут начать отправлять данные туда и обратно поверх этого протокола TCP. Итак, короче говоря, TCP — это более безопасная и надежная версия UDP, но она медленнее. И, с другой стороны, UDP быстрее и легче, но он рискованный. Например, если один из пакетов между источником и назначением потерян, он не отправляется повторно. Так что нет гарантированной доставки. Но, с другой стороны, если в TCP один из пакетов потерян после истечения некоторого времени ожидания, он все равно повторно отправляет первые пакеты. И таким образом, он гарантирует, что все данные будут доставлены по сравнению с UDP, где некоторые данные могут быть потеряны, но он будет продолжать работать. И при выборе между этими двумя, это основные вещи, на которые вам нужно обратить внимание. Если вам нужно, чтобы соединение было безопасным и надежным, тогда вам нужно выбрать TCP. Или если вам нужно, чтобы оно было быстрым, легким, но некоторая потеря данных может быть приемлемой, тогда вам нужно выбрать UDP. Например, лучше всего использовать TCP в банковских операциях, электронной почте, платежах и так далее. И, с другой стороны, UDP в основном используется в потоковом видео, играх и так далее. Это основные вещи, которые вам нужно знать о прикладном и транспортном уровнях. И это единственные уровни, которые потребуются для создания API. И в следующем уроке мы узнаем о RESTful API и о том, как мы обычно проектируем API в RESTful формате. RESTful API позволяют различным частям системы общаться друг с другом, используя стандартные методы HTTP. Это самый распространенный способ, которым разработчики создают и используют API сегодня. И в этом видео вы узнаете, как проектировать чистые REST API, следуя проверенным лучшим практикам, чтобы избежать создания запутанных и непоследовательных шаблонов, которые затрудняют использование и поддержку API. Мы начнем с изучения архитектурных принципов и ограничений RESTful API, моделирования ресурсов и проектирования URL, а также кодов состояния и обработки ошибок, а также фильтрации, сортировки и так далее. И мы изучим лучшие практики при использовании и разработке RESTful API. Давайте начнем с моделирования ресурсов. Ресурсы — это основные концепции в REST. Допустим, у вас есть бизнес-домен, который состоит из продуктов, заказов и отзывов. При моделировании этого для RESTful API вы обычно преобразуете это в существительные, а не в глаголы. Это означает, что продукт становится `products`, заказ становится `orders`, и то же самое для отзывов. Это могут быть коллекции или отдельные элементы. Например, первый запрос, который направлен на `/api/products`, вернет вам коллекцию продуктов, а не один продукт. Но, с другой стороны, у вас может быть `/products/{id}` для конкретного продукта, который вернет вам отдельный элемент. И обратите внимание, что мы используем `/products` при получении коллекции продуктов. И мы не используем что-то вроде `getProducts`, что не является лучшей практикой в RESTful API. Как я уже упоминал, мы используем здесь существительные, а не глаголы. Так что, чтобы получить заказы, например, вы не определяете URL как `getOrders`. Вы просто определяете его как `/orders`, и в зависимости от используемого метода, скажем, это метод GET, тогда вы получите заказы. Если это метод POST, тогда вы создадите заказ и так далее. Таким образом, все ресурсы должны быть четко идентифицируемы через URL. Например, это пример получения коллекции. Это пример получения конкретного элемента. И вложенные ресурсы также должны быть четко определены. Например, если вы хотите получить отзывы для какого-то конкретного продукта, то мы предположим, что если вы сделаете запрос на `/products/{id}/reviews`, вы получите отзывы для этого конкретного продукта. Но в реальных API вы редко хотите возвращать все результаты сразу. Вот почему мы обычно включаем фильтрацию, сортировку и пагинацию в API. Итак, давайте начнем с фильтрации. Например, если вы делаете запрос на получение всех продуктов, вы обычно добавляете некоторый параметр запроса, который в данном случае, как вы видите, — `category`. Таким образом, вы сначала фильтруете их по категории. А затем также с помощью амперсанда добавляете, что они должны быть в наличии. Так что `in_stock` должно быть `true`. И таким образом вы возвращаете только те элементы, которые собираетесь отобразить в пользовательском интерфейсе. И вы не делаете запросы, которые будут тратить пропускную способность этого API. А также это будет огромный ответ для вас на стороне фронтенда. Далее у нас также есть сортировка. В этом случае она снова управляется через параметры запроса, а параметры запроса — это все, что начинается после вопросительного знака в URL. Так что в этом случае вы обычно передаете атрибут `sort`, и это может быть, например, по возрастанию цены или по возрастанию отзывов, или это может быть также порядок по убыванию. Таким образом, на основе этого вы получите ответ от API в отсортированном порядке, потому что, например, если у вас есть тысяча элементов на бэкенде в базе данных, вы не хотите получать все эти элементы в неотсортированном порядке на фронтенд, потому что, скажем, фронтенд теперь должен отсортировать их по цене по возрастанию. Это означает, что ему нужно сделать запрос, чтобы получить все продукты, которые составляют эти тысячу элементов, которые у вас есть в базе данных. Так что это будет очень неэффективно. Вот почему мы делаем сортировку на бэкенде вместо этого. Таким образом, ваш бэкенд должен поддерживать функцию сортировки. Таким образом, фронтенд может просто сделать запрос к вашему бэкенду и передать этот параметр запроса `sort`, и таким образом он получит отсортированные продукты для отображения на экране. И далее у нас также есть пагинация. Снова с помощью параметра запроса вы обычно передаете страницу, которую хотите получить, а также лимит, потому что если вы не передадите лимит, то снова получите все продукты, начиная со страницы два до конца, что может быть много элементов. Так что вы также передаете какой-то лимит, и этот лимит — это то, что вы собираетесь отобразить на фронтенде, а затем на основе этого вы получите ответ, и здесь, скажем, вы получили 10 элементов, так что вы собираетесь отобразить эти 10 в пользовательском интерфейсе, а затем, когда они нажмут на следующую страницу, вы сделаете еще один запрос на страницу три на этот раз, и вы получите следующие элементы с сервера. Обычно мы используем `page` для пагинации, но есть еще один распространенный атрибут — `offset`. Так что некоторые API используют `offset` вместо `page`, и они используют его в сочетании с `limit`, что, по сути, означает, что если у вас есть тысяча элементов. Таким образом, `offset` сообщит API, с какого места начать отсчет этих тысяч элементов, а затем `limit` — это то же самое, что у вас здесь. Таким образом, он, по сути, ограничивает количество элементов, которые вы получаете от этого `offset` для получения на фронтенд. И последний вариант — вы также можете использовать пагинацию на основе курсора. Так что вместо `page` и `limit` вы передадите курсор, который будет хэшем страницы, которую вы хотите получить. Таким образом, этот подход добавления фильтрации, сортировки и пагинации имеет преимущества. Итак, во-первых, он экономит пропускную способность вашего сервера. Он также улучшает производительность как на стороне сервера, так и на стороне фронтенда. И он также дает фронтенду больше гибкости, потому что теперь вы можете получать только те вещи, которые вам нужны, а не какие-то ненужные данные из базы данных. Теперь перейдем к методам HTTP, которые используют REST API, потому что они полагаются на протоколы HTTP и, следовательно, используют методы HTTP, особенно для операций CRUD. Итак, это наиболее распространенные типы операций CRUD, которые вы увидите в REST API. Прежде всего, у нас есть метод GET, который используется для чтения данных из API. Так что это для получения ресурсов, как вы видели, например, получения продуктов, получения отзывов и так далее. И URL обычно выглядит так: вы делаете GET-запрос к `/api/version_of_the_api/resource_name`. И эти типы запросов являются безопасными и идемпотентными. Что, по сути, означает, что если вы сделаете запрос на `/products` два или три раза, вы ожидаете получить абсолютно одинаковый результат каждый раз, если, очевидно, не были добавлены новые продукты в базу данных. Далее у нас есть метод POST. Это обычно, когда вы создаете ресурс на своем сервере. Типичный пример — вы снова сделаете запрос к тому же конечной точке, что и для GET, чтобы создать коллекцию, но в этом случае вместо GET вы используете метод POST, и это говорит API, что вам нужно создать ресурс в `products`, а не получать их. Эти типы запросов изменяют состояние сервера. Они добавляют новый элемент, а также они не идемпотентны, что означает, что они создают ресурс. Так что в первый раз, когда вы создаете ресурс, вы получите идентификатор первого созданного вами элемента. Во второй раз, когда вы его создадите, вы получите идентификатор второго и так далее. Далее у нас есть методы PUT и PATCH, которые очень похожи, но они обновляют ресурсы в вашем API, но делают это немного по-разному. Метод PUT заменяет весь ресурс, в то время как метод PATCH частично обновляет ресурс в вашем API. Теперь вы можете видеть, что URL запроса одинаков в обоих случаях. Так что это `/products/{id_of_a_product}` для изменения, просто в случае запроса PUT он возьмет весь этот продукт с идентификатором 123 и фактически заменит его новым, который поступает с фронтенда. В то время как в случае PATCH он снова возьмет этот элемент из базы данных с идентификатором 123, но обновит его частично. Допустим, вы просто обновили заголовок с фронтенда и сделали запрос методом PATCH. Так что это обновит только заголовок этого продукта и оставит другие части, другие свойства без изменений. И последняя операция CRUD — это DELETE, и мы используем метод DELETE в этом случае, и, очевидно, как следует из названия, он удаляет ресурс из базы данных. Так что снова URL точно такой же, как у вас для изменения элементов. это `/products/{id_of_the_resource}`, и в этом случае вы ничего не передаете в теле запроса. Так что вы просто делаете DELETE-запрос к этому элементу, и вы удаляете его из базы данных, и каждая из этих операций возвращает вам различные коды состояния в зависимости от того, как прошел запрос, был ли он успешным или нет. Для этого у нас есть коды состояния и обработка ошибок в RESTful API. Так что вы должны использовать соответствующие коды состояния при работе с REST API. Например, серия 200 предназначена для успешных запросов. Например, 200 — OK. 201 — ресурс был создан. 204 — здесь нет содержимого. Допустим, вы сделали запрос, о котором мы говорили ранее, на `/products/{id_of_a_product}`, и вы успешно получили этот элемент. Это означает, что вы также должны установить код состояния 200, потому что запрос был успешным. В другом случае, когда вы создаете продукт и делаете POST-запрос на `/products`, на этот раз вы не должны отвечать тем же кодом 200, потому что 200 обычно означает, что статус был OK. Но в случае 201 это означает, что ресурс был создан. И в этом случае, поскольку вы создаете новый продукт, вы, очевидно, должны ответить кодом состояния 201, означающим, что ресурс был создан. У нас также есть серия 300, которая предназначена для перенаправления. Допустим, вы делаете запрос на URL, и теперь этот URL был перемещен куда-то еще. Так что он ответит серией 300 и перенаправит вас на новый URL. В серии 400 у нас есть ошибки клиента. Так что это всякий раз, когда ваш фронтенд сделал плохой запрос или пользователь сделал плохой запрос. Например, 400 — это общий плохой запрос. В 401 у нас есть неавторизованные запросы, что означает, что пользователь не аутентифицирован для выполнения этого запроса. Для 404 — не найдено. Так что, как правило, когда вы посещаете какой-то URL или делаете запрос на какой-то конкретный ресурс, который не существует, вы получите этот код состояния 404. Так что в случае 400, скажем, вы сделали запрос с недействительными параметрами или каким-то неправильным форматом JSON. В этом случае вы получите общий 400 повторяющийся запрос. Но если пользователь делает запрос на получение какого-то продукта, который, скажем, продукт с этим идентификатором, и он не существует в базе данных после запроса, тогда вы должны ответить кодом состояния 404, означающим, что ресурс не найден. И, наконец, у нас есть серия 500. Это случаи, когда ошибка происходит на вашем сервере. Так что вы не знаете точную причину, и это также не ошибка клиента, то есть клиент запросил все правильно. И в этом случае мы выбрасываем непредвиденные ошибки на стороне сервера. Вы обычно отвечаете сообщением об ошибке сервера и возвращаете код состояния 500 вместе с ним. Когда речь идет о лучших практиках RESTful API, во-первых, обратите внимание, что мы используем множественные существительные для всех ресурсов. Так что вместо `/product` мы используем `/products` для получения коллекции продуктов. Так что вы всегда должны использовать множественное число в этом случае. Также в операциях CRUD мы используем правильные методы HTTP. Например, при выполнении запроса на удаление пользователей мы ожидаем сделать запрос на `/users/{id_of_a_user}`, а не какой-то POST-запрос на `/users/{id}`. Так что, во-первых, методы HTTP должны быть правильно настроены, а также URL. Мы не ожидаем каких-то случайных вещей, таких как `/delete`, чтобы удалить ресурс из базы данных. Как вы видели, мы также поддерживаем фильтрацию, сортировку и пагинацию в хороших REST API. Не только пагинация, например, в этом случае у нас есть только страница три, но мы не можем ограничить количество продуктов, которые мы хотим получить. В то время как в этом случае мы можем полностью контролировать, что мы хотим получить от API. Мы хотим получить элементы со страницы три. Мы хотим, чтобы это количество лимита применялось к продуктам. И мы также хотим применить некоторую сортировку, например, сортировку по цене или сортировку по рейтингам и так далее. И также версионирование в RESTful API. Как вы заметили во всех этих запросах, они все поставляются с префиксом `/api`, а затем `/ID_of_the_API`, который является либо `v1`, `v2`, `v3` и так далее. Допустим, в будущем вы мигрируете свой API и начнете использовать кучу новых функций, но также сломаете что-то в предыдущей версии один, тогда, если вы используете версионирование, вы не сломаете его на фронтенде, потому что они могут использовать старую версию вашего API и по-прежнему использовать старые функции и функциональность, пока вы продолжаете разрабатывать новую версию, скажем, версию три, и вы поддерживаете новые функции здесь, и вы могли что-то сломать здесь, но они все еще используют старый API, так что это не влияет на конечных пользователей. Итак, чтобы подытожить, мы узнали об архитектурных принципах и ограничениях REST. А также о моделировании ресурсов и дизайне URL и о том, как мы моделируем бизнес-домен в домен RESTful API. А также коды состояния, обработка ошибок и правильные методы, которые следует использовать с основными операциями CRUD. И, наконец, мы рассмотрели лучшие практики для RESTful API, которые вы должны использовать, чтобы ваши API оставались последовательными и предсказуемыми для других разработчиков, которые их используют. Традиционные RESTful API часто возвращают слишком много или слишком мало данных, что требует от нас выполнения нескольких запросов для одного представления, чтобы получить все необходимые данные. GraphQL решает эту проблему, предоставляя клиентам именно то, что они запросили. Но проектирование GraphQL API отличается от проектирования REST API. Вот почему в этом видео мы рассмотрим основные концепции GraphQL и почему он существует. Проектирование схемы и система типов GraphQL, запросы и мутации, обработка ошибок, а также лучшие практики для проектирования GraphQL API. Давайте начнем с понимания того, почему GraphQL существует в первую очередь. Он был создан Facebook для решения очень специфической проблемы: клиентам приходилось делать несколько вызовов API и при этом не получать именно те данные, которые им были нужны. Например, если представить, что у нас есть API Facebook, такие как API пользователей, API постов, комментариев и лайков для страницы Facebook. В большинстве случаев клиент может делать запросы ко всем этим API отдельно и при этом не получать все необходимые данные, что потребует от него выполнения нескольких запросов к одному и тому же API. Это, конечно, увеличивает общую задержку страницы, потому что страница еще не загружена, пока не будут выполнены все эти запросы и не будут получены данные. Но в случае GraphQL API у вас есть единая конечная точка GraphQL. Таким образом, клиент указывает форму ответа, и эта единая конечная точка обрабатывает все взаимодействия с данными. Это все еще HTTP-запрос, но, как вы можете видеть, мы можем указать именно те данные, которые нам нужны. Например, нам нужен пользователь с ID 123, и нам нужно только имя пользователя, а также посты, и из постов мы можем указать только заголовок. Так что нам не нужны изображения для этого представления. И снова с комментариями вы можете указать именно те данные, которые вам нужны в объекте, чтобы вы не перегружали данные. Теперь давайте посмотрим на дизайн схемы и систему типов GraphQL и чем они отличаются от REST API. Схема в данном случае — это контракт между клиентом и сервером. В схеме, прежде всего, у вас есть типы, которые могут быть, например, типом пользователя, который вы указываете, и вы указываете все поля, которые существуют в этом типе пользователя, такие как ID, имя, посты и так далее. И, как вы можете видеть, если тип не является примитивным типом, таким как `posts`, тогда вы можете указать другой тип массива `post`, и тогда этот тип `post` может быть определен отдельно. Далее у нас есть запросы для чтения данных. Это эквивалент выполнения GET-запросов в REST API. Вы указываете запрос и функцию этого запроса. Это может быть запрос `user`, который получает пользователя с определенным ID, а также тип возвращаемого значения этого запроса, который в данном случае является типом `user`, определенным выше. И GraphQL также поставляется с мутациями. Вы можете думать об этом как об эквиваленте методов POST, PUT, PATCH и DELETE в REST API. Так что всякий раз, когда вы изменяете данные в базе данных, вы выполняете мутационный запрос. Здесь, как вы можете видеть, у нас есть пример метода `createUser`, который принимает имя и, конечно, множество вещей в реальном мире, а затем возвращает тип `user`, который мы определили выше. Так что, если у вас хороший дизайн схемы в GraphQL, он должен отражать вашу модель домена и быть интуитивно понятным и гибким. Далее, после того как вы определили дизайн схемы и систему типов, вы можете начать запрашивать и изменять данные с помощью этого GraphQL API. Для этого у нас есть запросы для получения данных. Опять же, это похоже на GET-запросы в REST API. И здесь вы можете указать именно то, что вам нужно от пользователя. Это тот же метод `user`, который мы определили там в схеме. Так что здесь вы также можете указать точные атрибуты, такие как имя, посты, и из постов вам нужен только заголовок, и это сделает запрос к вашему GraphQL API и вернет именно те данные, которые вы запросили. Аналогично, вы также можете использовать определенные вами мутации. Например, если у вас есть метод `createPost`, определенный как мутация, вы можете использовать его для изменения поста. Например, установка заголовка и тела поста. А затем вы также указываете, какие данные вам нужно получить после создания этого поста, а именно ID и заголовок. Когда речь идет об обработке ошибок в GraphQL API, это немного отличается от REST API, поскольку GraphQL всегда возвращает статус 200 OK для всех ответов, даже если произошла ошибка. В этом случае мы должны вернуть поле `errors` в ответе, которое будет указывать на то, что произошла ошибка. Таким образом, частичные данные все еще могут быть возвращены с ошибками, как в этом случае у нас есть пользователь, который равен null, а затем у нас есть поле `errors`, которое указывает, что у вас есть код состояния 404, сообщение "не найдено" и путь, который является пользователем в вашей схеме. Как вы можете видеть, в этом случае вы можете указать код состояния в массиве `errors`. Поскольку мы возвращаем коды состояния 200 для всех GraphQL-запросов, поэтому у нас есть код состояния, специально указанный в ошибках, чтобы мы знали, какой это тип ошибки, а именно "пользователь не найден". Существуют также лучшие практики, которым мы обычно следуем при проектировании GraphQL API. Во-первых, схемы, которые мы видели, — это хорошая практика — держать их небольшими и модульными. Также мы должны избегать глубоко вложенных запросов. Например, у вас может быть пользователь, затем вложенный пост, а затем внутри поста — комментарий. Так что это может быть бесконечно вложено. И чтобы избежать этого, мы обычно реализуем глубину ограничения запросов, которая определяет, насколько глубоко вы можете зайти, сколько слоев вложенности вы можете иметь в своих данных. Так что вы указываете что-то вроде шести или семи слоев вложенности. Мы также используем осмысленные имена для типов и полей, чтобы это также облегчало работу с клиентской стороны, потому что оба будут использовать одну и ту же схему. И при изменении данных мы всегда используем входные типы для мутаций. Прежде чем система сможет что-либо авторизовать или ограничить, она сначала должна узнать личность субъекта запроса. Вот что делает аутентификация. Она проверяет, является ли человек или система, пытающаяся получить доступ к вашему приложению, законной. И в этом видео вы узнаете, как современные приложения обрабатывают аутентификацию: от базовых токенов до токенов-носителей, аутентификации OAuth 2 и JWT, а также токенов доступа и обновления, а также единого входа и протоколов идентификации. Прежде чем изучить различные типы, давайте сначала разберемся, что такое аутентификация. Аутентификация, по сути, отвечает на вопрос, кто пользователь, и разрешен ли ему доступ к вашей системе. Так что всякий раз, когда отправляется запрос на вход, либо пользователем, либо другой службой, здесь мы подтверждаем личность пользователя и либо предоставляем ему доступ, то есть одобряем его запрос, либо отклоняем его с неавторизованным запросом. Это, по сути, первый шаг перед началом авторизации, которая является темой следующего урока. Так что перед тем, как получить доступ к каким-либо данным или выполнить какие-либо действия в этой службе, система должна знать, кто вы, и именно здесь используется аутентификация. Первый и самый простой тип аутентификации — базовая аутентификация. Здесь вы используете имя пользователя и пароль в сочетании и отправляете запрос на вход, который содержит версию имени пользователя и пароля, закодированную в Base64. Это очень простой способ кодирования данных, и он легко обратим. И поскольку он легко обратим, он теперь считается небезопасным, если он не обернут в HTTPS. Но даже с этим, он теперь очень редко используется за пределами внутренних инструментов в компании. Далее у нас есть токены-носители (bearer tokens), которые более безопасны по сравнению с базовой аутентификацией. Здесь вы отправляете токен доступа с каждым запросом вместо кодирования имени пользователя и пароля. Так что всякий раз, когда клиенту нужно получить доступ к ресурсам, он отправляет этот токен в запросе, а затем ваш API проверяет или отклоняет токен, и если он проверяет, то вы отправляете успешный ответ с данными, которые они запросили. Токены-носители — это стандартный подход в настоящее время, особенно в дизайне API, потому что он быстрый и без состояния, что делает его легким для масштабирования этих API. Следующий тип — аутентификация OAuth 2 в сочетании с токенами JWT. Так что OAuth 2 — это протокол, который является второй версией OAuth. Он позволяет пользователям входить через доверенного поставщика, такого как Google или GitHub. Так что пользователь отправляет запрос на доступ к вашим ресурсам, и если вы разрешаете им аутентифицироваться через Google, Google отправляет вашему приложению токен JWT, который содержит информацию об этом пользователе. Вот как будет выглядеть полезная нагрузка. Обычно они отправляют вам ID пользователя или электронную почту, имя пользователя и другую информацию, а также дату истечения срока действия этих токенов JWT. Это подписанный объект, который затем вы передаете из вашего приложения в API, а затем ваш API будет аутентифицироваться на основе этой информации. JWT также без состояния, как и токены-носители, что означает, что вам не нужно хранить сессии между запросами, и каждый запрос может быть выполнен отдельно. Далее у нас также есть типы токенов доступа и обновления. Так что современные системы используют токены доступа с коротким сроком действия, которые истекают быстрее, а также токены обновления с длительным сроком действия, которые обычно истекают позже, чем токены доступа. Токены доступа используются для вызовов API. Так что всякий раз, когда вы хотите получить какие-то данные из API, вы отправляете этот токен доступа для доступа к данным, а токены обновления, с другой стороны, используются для обновления токенов доступа. Так что всякий раз, когда токен доступа истекает, именно здесь вы будете использовать токен обновления, чтобы получить новый, новый токен доступа за кулисами. Таким образом, пользователи не будут выходить из системы, они останутся в системе, а ваша система останется безопасной, потому что вы часто обновляете этот токен доступа. И одно примечание здесь: токены обновления следует хранить на стороне сервера из соображений безопасности. И, наконец, у нас есть SSO, что означает Single Sign-On (единый вход), и протоколы идентификации, которые с ним используются. Единый вход позволяет пользователям иметь один вход. Так что войдите один раз и получите доступ к нескольким службам. Например, когда вы входите в Google, вы можете получить доступ как к Gmail, так и к Drive, а также к Календарю и всем их другим службам. И за кулисами этот SSO использует либо протокол SAML, либо протокол OAuth 2. OAuth 2 чаще используется в настоящее время для современных приложений для входа через Google или GitHub или любого другого поставщика услуг. Это современный и основанный на JSON. А с другой стороны, протокол SAML использует подход на основе XML. Но все же, SAML очень популярен в устаревших системах и в компаниях, которые используют такие вещи, как Salesforce или внутренние панели управления. Так что это протоколы идентификации, что означает, что они будут определять, как приложения безопасно обмениваются информацией о входе пользователя между собой. Но аутентификация — это только первый шаг, прежде чем пользователи смогут получить доступ к вашей службе. Так что это говорит вам, кто пользователь, и разрешен ли ему доступ к вашей службе. Это когда они отправляют запрос на вход, и вы подтверждаете или отклоняете их личность. Но после этого у вас также есть этап авторизации, который говорит вам, к каким именно ресурсам этот пользователь может получить доступ. По сути, он говорит вам, что они могут делать, что пользователь может делать в вашей системе, и это то, что мы рассмотрим далее в следующем видео. Авторизация — это шаг, который происходит после аутентификации. Как только кто-то входит в нашу систему. Так что, как только запрос на вход одобрен, что означает, что система теперь знает, кто пользователь, следующим шагом является решение, что они могут делать, что является шагом авторизации. Необходимо проверить, к каким ресурсам или действиям у этого пользователя есть разрешения на доступ, а также какие действия запрещены для этого пользователя. Вот как мы контролируем безопасность и конфиденциальность в системах. И в этом видео вы узнаете, как приложения и системы управляют разрешениями, используя три основные модели авторизации. Первая — это контроль доступа на основе ролей (Role-Based Access Control). Далее у нас есть контроль доступа на основе атрибутов (Attribute-Based Access Control). Также список контроля доступа (Access Control List), который является еще одним способом управления авторизацией. Кроме того, вы узнаете, как такие технологии, как OAuth 2 и JWT, помогают нам применять эти правила на практике. Таким образом, аутентификация происходит первой, что говорит нам, кто пользователь, и разрешен ли ему доступ к нашей системе. Но на следующем шаге у нас есть авторизация, которая определяет, что вы можете фактически делать как пользователь в этой системе. Если мы посмотрим, например, на GitHub и доступ к репозиториям на GitHub, там у вас есть разные разрешения для разных пользователей. Например, пользователь А может иметь только права на запись, что означает, что он может только отправлять код в этот репозиторий. Но, с другой стороны, у нас может быть пользователь Б, и здесь вы можете предоставить только права на чтение, что означает, что он может только читать этот репозиторий, но не может отправлять код в него или создавать запросы на слияние и так далее. И с другой стороны, у нас также могут быть администраторы, которые имеют полный контроль. Так что они могут управлять всеми настройками репозитория. Они могут даже решить удалить этот репозиторий и так далее. Таким образом, вы можете видеть, что разные пользователи могут иметь разные элементы контроля доступа в системах. Для управления этими элементами контроля доступа у нас есть общие модели авторизации. Так что та, которую мы только что рассмотрели, — это модель авторизации на основе ролей, которая назначает роли пользователям, таким как администратор, редактор или доступ только для чтения, доступ только для записи. И это самый распространенный подход среди этих моделей авторизации. Но у нас также есть контроль доступа на основе атрибутов, который основан на атрибутах пользователя или ресурса. Так что это более гибко и сложнее по сравнению с авторизацией на основе ролей. И другой распространенный подход — иметь списки контроля доступа (ACL), и каждый ресурс здесь имеет свой собственный список разрешений. Так что вы можете назначить списки разрешений ресурсу, и это будет определять, к каким ресурсам вы можете получить доступ. Например, это распространенный способ управления Google Docs. И мы рассмотрим это более подробно сейчас. И каждая из этих моделей имеет свои компромиссы, плюсы и минусы. Так что это зависит от конкретных требований системы. Но реальные системы часто комбинируют несколько моделей вместе, чтобы иметь более сложную и безопасную настройку. Итак, прежде всего, у нас есть контроль доступа на основе ролей (RBAC). Здесь пользователям назначаются роли, и каждая роль имеет определенный набор разрешений. Например, как вы видели с GitHub, у вас могут быть администраторы, и администраторы обычно имеют полный доступ ко всем ресурсам. Так что они могут создавать, читать или обновлять ресурсы. Они могут даже удалять ресурсы, а также управлять другими пользователями в ролях. И далее у вас есть редактор, который обычно немного меньше, чем администратор. Так что они могут редактировать контент, например, создавать или читать контент или обновлять ресурсы, но они не могут удалять ресурсы и не могут управлять другими пользователями. И далее у вас могут быть пользователи-зрители, которые могут только читать данные. Так что они могут читать ресурсы и контент, но не могут ничего обновлять или создавать в вашей системе. Это самый распространенный способ в моделях авторизации, и он используется в приложениях, которые вы используете ежедневно, как вы видели с GitHub или панелями управления, или инструментами CMS, инструментами управления командами и так далее. Следующая модель — контроль доступа на основе атрибутов (ABAC). Этот контроль доступа выходит за рамки ролей. Так что он использует атрибуты пользователя или атрибуты ресурса и условия среды для определения доступа. Пример политики, которую вы можете увидеть здесь. Так что, скажем, вы хотите разрешить доступ только в том случае, если выполнены некоторые условия. В этом случае, когда атрибут отдела пользователя установлен на HR, и вы можете комбинировать это с несколькими условиями, например, когда атрибут ресурса равен "внутренний" и так далее, и только в этом случае вы разрешаете им доступ, и вы либо разрешаете им доступ на чтение, либо на запись. Так что это также может быть объединено с авторизацией на основе ролей. Но в этом случае вы проверяете модель пользователя или модель ресурса в вашей базе данных, и на основе атрибутов вы разрешаете или запрещаете доступ. Так что здесь, как вы видите, мы проверяем атрибуты пользователя, такие как отдел, возраст или все, что вы хотите проверить здесь. Далее, вы также можете комбинировать это с атрибутами ресурса, такими как конфиденциальность

или владельца ресурса или классификации. И это также может быть объединено с окружением, таким как время суток, местоположение, тип устройства и так далее. Поскольку вы объединяете эти атрибуты для предоставления или ограничения доступа, это более гибко, чем авторизация на основе ролей, но требует хорошего управления политиками и, как правило, более сложно, и здесь вы можете столкнуться с конфликтами с контролем доступа на основе атрибутов. Третий распространенный тип — списки контроля доступа. Вместо предоставления доступа на основе ролей или доступа на основе атрибутов, вы можете иметь список контроля доступа для конкретного ресурса. Допустим, у вас есть ресурс, такой как документ или JSON-файл, и здесь у вас может быть список разрешений, на которых пользователи могут получить доступ к этому документу, например, пользователь Алиса имеет только права на чтение, или пользователь Боб имеет права на чтение и запись, а другой пользователь не имеет доступа к этому документу. Таким образом, как вы видите, мы управляем здесь двумя вещами. Во-первых, какие пользователи имеют право доступа к этому документу, и во-вторых, каковы их разрешения. Таким образом, каждый из пользователей имеет разные разрешения на этот документ. Списки контроля доступа очень специфичны и ориентированы на пользователя, что означает, что их трудно масштабировать в системах с миллионами пользователей или объектов, если вы не управляете ими тщательно. Но, например, Google Drive — один из примеров этого, где у вас есть документы, такие как Google Docs, а затем вы делитесь этим Google Docs с вашими коллегами, верно? Так вы делитесь с кем-то только правами на чтение, а затем делитесь этим документом с кем-то еще, но теперь они также могут редактировать и добавлять комментарии к этому документу. Итак, это пример списка контроля доступа (ACL), который используется в Google Drive и Google Docs. Это дает вам больше контроля над ресурсами и документами, но также труднее масштабировать при миллионах пользователей. Но это возможно, как вы видите, потому что Google Drive использует это для своих документов, Excel-таблиц и так далее. Итак, это были модели контроля доступа. Но как системы обеспечивают соблюдение этих авторизаций? Здесь в игру вступают OAuth2 и JWT или токены доступа. Итак, сначала у нас есть OAuth2, который является делегированной авторизацией, протоколом, используемым, когда сервис хочет получить доступ к ресурсам другого сервиса от имени пользователя. Например, если вы хотите разрешить стороннему приложению читать ваши репозитории GitHub. Допустим, вы развертываете свое приложение на Vercel. Вам нужно предоставить Vercel контроль над вашим репозиторием на GitHub. Вместо того, чтобы передавать ваше имя пользователя и пароль стороннему приложению, что совершенно небезопасно, потому что вы не знаете, что они могут сделать с вашим именем пользователя и паролем. Таким образом, вы даете им полный контроль. Вместо этого GitHub дает им токен, который представляет разрешения, которые вы одобрили для использования. Таким образом, вы, как пользователь, отправляете запрос вместе со сторонним приложением для запроса доступа к вашим репозиториям, а затем GitHub выдает вам токен доступа, который вы должны создать. Таким образом, вы также должны указать, к каким ресурсам, к каким репозиториям это стороннее приложение может получить доступ, а также что они могут делать. Могут ли они создавать, читать, обновлять или удалять, или любые другие разрешения, которые вы установили, а затем GitHub отправляет им токен, который содержит разрешения, которые это стороннее приложение имеет право использовать, а OAuth2 определяет поток для безопасной выдачи и проверки этих токенов. Таким образом, вы даете им токен доступа, а не ваш пароль, который представляет разрешения, которые вы лично одобряете. Таким образом, это может быть чтение определенных репозиториев или создание и отправка в эти репозитории, но не удаление этих репозиториев. И далее у нас также есть авторизация на основе токенов с использованием JWT или токенов-носителей и логики разрешений. После аутентификации пользователя большинство систем используют токен, обычно токен JWT, или это может быть также токен-носитель, который несет такую информацию, как идентификатор пользователя, роли, такие как администратор или редактор, а также области действия, к которым им разрешен доступ, и когда этот токен истекает, и кто является эмитентом этого токена. Таким образом, когда пользователь делает запрос, он всегда несет эту информацию о токене и достигает серверной части. Здесь сервер проверит ваш токен и его действительность, и применит соответствующую логику разрешений. Чтобы не путать это с моделями авторизации, существует ключевое различие. Токен обычно несет личность и утверждения вашего пользователя, как вы видите здесь. Но модели авторизации, такие как основанные на ролях или основанные на атрибутах, определяют, к чему разрешен доступ в качестве пользователя. Таким образом, токены — это просто механизмы, в то время как это модели авторизации. Итак, в итоге авторизация — это не просто предоставление доступа пользователям, как аутентификация, но и контроль над тем, к чему они могут получить доступ, когда они уже внутри. Мы узнали, что такое авторизация. Каковы три наиболее распространенные модели авторизации: основанные на ролях, основанные на атрибутах и списки контроля доступа, а также вы видели несколько примеров из реальной жизни, например, как GitHub управляет вашими токенами авторизации, и это должно дать вам представление о том, когда использовать каждую модель в зависимости от системы, которую вы строите, и вы также видели некоторые шаблоны реализации с OAuth2 или JWT токенами. Каждая из этих моделей имеет свои компромиссы, свои плюсы и минусы, и реальные системы часто комбинируют несколько моделей, чтобы оставаться гибкими и безопасными. API — это как двери в вашу систему. Если вы оставите их незащищенными, то злоумышленники и кто угодно могут войти и делать все, что захотят с вашими пользовательскими данными и системой в целом. Вот почему в сегодняшнем видео мы рассмотрим семь проверенных методов, которые помогут вам защитить ваши API от нежелательных атак. Первое в списке — ограничение скорости запросов (rate limiting), которое контролирует, сколько запросов клиент может сделать за определенный период времени. Например, вы можете установить лимит для пользователя А, чтобы он делал, скажем, 100 запросов за определенный период времени к вашему API. И если они превысят этот лимит и, скажем, сделают 101 запрос, то вы заблокируете следующий запрос и дадите некоторое время пройти, прежде чем они смогут отправить свой следующий запрос. Если вы не установите это для своего API, то злоумышленники могут перегрузить вашу систему. Они могут отправлять тысячи запросов в минуту и перегрузить ваш API, что выведет вашу систему из строя, или они могут также перебирать ваши данные. И эти ограничения скорости могут быть установлены для каждого конечного пункта. Например, скажем, у вас есть конечный пункт /comments, и здесь они могут отправить запрос либо для создания комментария, либо для получения комментариев. Вы можете установить этот лимит на уровне конечного пункта. Таким образом, для этого конечного пункта комментариев будет установлен определенный строгий лимит запросов в минуту. Вы также можете установить его для каждого пользователя или IP-адреса. Допустим, у нас есть IP-адрес первого пользователя, затем B для второго, C для этого, а у вашего злоумышленника есть IP-адрес, соответствующий D. Если вы получите 101-й запрос с IP-адреса D, вы будете знать, что этот пользователь злоупотребил API. Таким образом, вы заблокируете его на уровне пользователя/IP-адреса. И также существует общее ограничение скорости запросов для защиты от DDoS-атак. Поскольку вы можете установить ограничение скорости запросов для работы по пользователю или IP-адресу, это означает, что этот злоумышленник сам по себе не может отправлять так много запросов. Вы заблокируете его с помощью ограничения скорости запросов в API. Но что они могут сделать, это запустить несколько ботов, и у каждого бота будет свой лимит, верно? Скажем, вы установили его на 100 на IP-адрес. Таким образом, у каждого из этих ботов есть 100, и в общей сложности у них больше, чем вы бы разрешили, или ваша система могла бы справиться. Вот почему у вас также есть общее ограничение скорости запросов, которое может быть большим числом. Таким образом, когда весь трафик, поступающий на ваш сервер, достигает или превышает это число, вы временно заблокируете все запросы, пока не выясните первопричину. И, конечно, эти числа — просто примеры. В реальности их гораздо больше тысячи, но это просто пример. Второе в списке — CORS, что означает Cross-Origin Resource Sharing (совместное использование ресурсов между разными источниками). Это контролирует, какой домен может вызывать ваш API из браузера, и без надлежащего CORS вредоносные веб-сайты могут обмануть браузеры пользователей, заставляя их делать запросы от их имени. Например, если ваш API предназначен только для обслуживания вашего фронтенд-приложения, которое находится по адресу app.yourdomain.com, yourdomain.com, то должны быть разрешены только запросы из этого источника. Если кто-то другой отправляет вам запрос, например, с другого домена, anotherdomain.com, то вы должны заблокировать этот запрос и не разрешать им использовать ваш API для аутентификации или использования каких-либо его данных. Третье — это также распространенное явление — SQL- и NoSQL-инъекции. Атаки с внедрением могут произойти, когда пользовательский ввод напрямую включается в запрос к базе данных. Например, злоумышленник может изменить его и отправить некоторые запросы для чтения или удаления ваших данных. Здесь, например, эта часть полностью обходит проверки, и тогда злоумышленник может использовать этот запрос для начала чтения данных из вашей базы данных или изменения чего-либо, или они могут также удалить все данные, все пользовательские данные и любые другие таблицы, которые у вас есть в этой базе данных. Чтобы исправить это, мы всегда используем параметризованные запросы или ORM-защиту. Следующий метод, который следует использовать, — это брандмауэры. Брандмауэр действует как привратник, фильтруя вредоносный трафик от другого нормального трафика. Обычно он находится между вашим API и входящим трафиком. Например, если вы используете веб-приложение брандмауэра AWS, они могут блокировать запросы с неизвестными шаблонами атак, такими как подозрительные SQL-ключевые слова или странные HTTP-методы, что означает, что они будут блокировать любые подозрительные запросы от злоумышленников, но позволят другим обойти запрос и достичь вашего API. Некоторые API также являются частными и должны быть доступны только из определенных сетей. Вот почему у нас также есть VPN, что означает Virtual Private Networks (виртуальные частные сети). API, которые находятся в сети VPN, могут быть доступны только тем, кто находится в той же сети. Это означает, что некоторые API являются общедоступными, то есть эти API будут разрешать любые запросы из Интернета от ваших пользователей. Но это, например, может быть в сети VPN, что означает, что если пользователь из Интернета пытается получить доступ к вашему API, то этот запрос будет заблокирован, потому что пользователь не находится в той же сети. Но с другой стороны, если у вас есть другой пользователь здесь, который находится в сети VPN, они могут сделать запрос к этим API, и в этом случае они обойдут проверки, и их запрос достигнет ваших API. Это полезно, когда у вас есть внутренние инструменты. Допустим, у вас есть внутренняя панель администратора, и API для этой панели администратора будет доступен только сотрудникам, подключенным к VPN компании. Далее у нас есть CSRF, что означает Cross-Site Request Forgery (подделка межсайтовых запросов). Это обманывает браузер вошедшего в систему пользователя, заставляя его делать нежелательные запросы к API. Допустим, вы, как пользователь, вошли в систему вашего банка, и ваша банковская система использует файлы cookie для аутентификации. Если банковская система небезопасна и использует только сессионные файлы cookie, другой вредоносный сайт может использовать ваш файл cookie и отправить скрытый запрос на перевод денег через ваш файл cookie. Чтобы предотвратить такие атаки, компании также используют CSRF-токены в сочетании с сессионными файлами cookie. Таким образом, банковская система проверит наличие сессионного файла cookie, но также проверит, соответствует ли CSRF-токен тому, который у них есть. И если нет, то она заблокирует этот запрос из другого неизвестного источника, в то время как она разрешит запрос от вашего имени. И последнее, что у нас есть, — это XSS, или его также называют Cross-Site Scripting (межсайтовый скриптинг). Это позволяет злоумышленникам внедрять скрипты в веб-страницы, обслуживаемые другим пользователям. Например, если у вас есть раздел комментариев, и этот комментарий отправляется в ваш API. Далее ваш API также сохранит его в базе данных. Вы можете получать обычные запросы, такие как «красивая картинка» или что-то в этом роде, и это достигнет вашего API. Ваш API сохранит это в базе данных. Так что все в порядке. Но что, если злоумышленник поместит скрипт в этот раздел комментариев, и внутри этого скрипта они могут попытаться сделать много разных вещей. Например, они могут попытаться получить файл cookie другого пользователя или попытаться внедрить что-то в вашу базу данных. И если вы это разрешите, это достигнет вашего сервера, и информация будет записана в базу данных. Позже, когда другие пользователи загрузят этот раздел комментариев на свой экран, они также получат внедренный комментарий непосредственно на свою веб-страницу, и браузер выполнит этот вредоносный JavaScript-код в браузере других пользователей. Это были первые два столпа курса «Мастерство проектирования систем». Если вы хотите продолжить обучение и по-настоящему освоить проектирование систем и стать уверенным старшим разработчиком, который получает шестизначные зарплаты, вам также нужен практический опыт построения этих систем с нуля в облачных провайдерах, таких как AWS, и объяснения ваших архитектурных решений на реальных собеседованиях. В течение следующих 7 дней только вы можете присоединиться к менторству Dev Mastery с 7-дневной бесплатной пробной версией. Вы получите полный курс по проектированию систем, реальные проекты и мое менторство, чтобы стать уверенным старшим инженером, который не беспокоится об увольнениях или о том, что ИИ займет его работу, потому что у вас будут архитектурные навыки, которые компании отчаянно нуждаются и всегда готовы платить за них шестизначные суммы. Нажмите на ссылку в описании, чтобы начать свою бесплатную пробную версию сегодня.