Прежде чем мы ответим на главные вопросы: что использовать для взаимодействия между сервисами, в чем отличие REST от RPC, и, наконец, можно ли вообще выделить победителя в состязании REST vs RPC, давайте попробуем более подробно рассмотреть оба подхода.
Однако, в самом начале, давайте «договоримся о терминах» — разберем, что такое API, REST, RPC, HTTP и прочее.
API — это акроним для Application Programming Interface или прикладной программный интерфейс. Представим себе некий информационный сервис, программную библиотеку или приложение в виде черного ящика, детали реализации которого нам недоступны. API в этом случае будет неким набором органов управления и индикации, при помощи которых мы можем с этим черным ящиком взаимодействовать.
HTTP — это аббревиатура от Hypertext Transfer Protocol или протокол передачи гипертекста. Слово «протокол» тут неспроста: HTTP находится на седьмом, прикладном, уровне сетевой модели OSI. Прикладной характер HTTP в данном случае выражается в том, что контроль передачи данных осуществляется посредством приложений (web-серверы, http-клиенты, браузеры и многое другое). Реализация этого протокола оказалась очень удачной и гибкой; сегодня он применяется не только для доставки WEB-страниц, но также и для передачи файлов, потокового медиа и конечно же для сетевого взаимодействия информационных систем посредством открытых API.
REST — REpresentational State Transfer, передача репрезентативного состояния или передача состояния представления. Забавно, но найти точный перевод, полностью раскрывающий смысл этого акронима на русский язык довольно трудно. Что это за состояние представления (еще и репрезентативное)? Рассмотрим это чуть ниже, когда перейдем к REST. Тем не менее, следует отметить одну очень важную вещь: REST не является ни протоколом, ни стандартом, ни даже технологией — это, прежде всего, архитектурное решение (а иногда REST упоминается даже как архитектурные ограничения!) для построения распределенных информационных систем.
RPC — Remote Procedure Call или вызов удаленной процедуры. RPC — это технология, смысл которой заключается в том, что клиентское программное обеспечение осуществляет запуск вычислений на стороне сервера посредством вызова процедуры или функции, с передачей параметров и получением результата. Вызов такой функции оформляется так, словно она является непосредственной частью кода клиента.
Идея запуска вычислений маломощным клиентом на высокопроизводительном сервере появилась очень давно. Первыми «поставщиками» и «потребителями» новой технологии стали базы данных (в то время их гордо величали банки данных и даже базы знаний). Технология RPC оказалась довольно гибкой и обладающей большим потенциалом. Большой вклад в формирование этой концепции внесли Sybase, Sun Microsystems, Microsoft и многие другие.
Впоследствии, когда монолитные архитектурные решения стали уступать место многозвенным, RPC органичным образом вписались в новые концепции. Из RPC также выросло много разных промышленных стандартов и протоколов. Мы рассмотрим два архитектурных решения, которые используют технологию вызова удаленной процедуры, — это CORBA и веб-сервисы.
VDS
CORBA — или Common Object Request Broker Architecture, обобщенная архитектура брокеров объектных запросов. Это, пожалуй, самая полная архитектурная спецификация для построения распределенных систем. Зародилась в 80-х годах XX века, наибольшее распространение получила в 90-х годах того же столетия. Самым большим преимуществом CORBA по сравнению с другими распределенными архитектурами стало то, что в сети запуска вычислений и обмена результатами могли присутствовать гетерогенные (или разнородные) элементы, которые реализовывали стандарты этой архитектурной спецификации. Стало возможным сочетать различные экосистемы: Java, C/C++ и даже Erlang.
CORBA будучи весьма гибкой и эффективной архитектурой, тем не менее довольно сложна внутренне, содержит множество различных описаний, соглашений и, положа руку на сердце, представляет собой ту еще головную боль для разработчиков, которые интегрируют свою (или новую) экосистему в эту архитектурную парадигму.
Вторым серьезным препятствием для использования CORBA является ее сетевой стек. Он работает поверх протокола TCP и довольно сложен, некоторые реализации CORBA используют стандартные порты TCP (определенные и зарезервированные для CORBA), а некоторые — произвольные, и это никак не регламентируется; всё это идет вразрез с политиками безопасности корпоративных сетей. Также это делает применение CORBA в Интернете весьма неудобным и даже невозможным.
Рабочей лошадкой большинства информационных систем является протокол HTTP. Он использует два четко определённых TCP-порта: 80 и 443. CORBA в то же самое время требует 4 разных TCP-порта для своих протоколов, при этом каждый протокол имеет свои временные характеристики и особенности. Поэтому CORBA будет хороша там, где требуется интеграция в уже существующую архитектуру информационной системы, которая уже построена с применением CORBA. Разрабатывать же новую информационную систему с применением этого архитектурного решения скорее всего не следует, поскольку сегодня существуют более эффективные и простые механизмы.
С учетом всех недостатков CORBA, в конце 1990-х годов был сформирован стандарт, который положил начало так называемым веб-сервисам (web services). В отличие от CORBA, веб-сервисы использовали уже существующий, очень надежный и простой протокол — HTTP и полностью опирались на его архитектурные соглашения. У каждого сервиса имелся свой уникальный адрес URL (universal resource locator) и набор методов, также базирующихся на соглашениях HTTP. В качестве «носителя» информации используются машинно- и архитектурно-независимые форматы, такие как XML или JSON. В частности, некоторые реализации веб-сервисов используют формат, который называется SOAP — (Simple Object Access Protocol), который базируется на XML. Новое решение было значительно удобнее тяжеловесной CORBA, использовало простой и надежный протокол HTTP и, по сути, не зависело от технологий, механизмов развертывания и аспектов масштабирования информационных систем.
Однако, вскорости новая технология «обросла» стандартами, правилами, спецификациями и прочими необходимыми, но очень скучными атрибутами такого явления, как Enterprise. SOAP — это удачное решение потому, что XML, который лежит в его основе, — это управляемый, машинно-независимый, определяемый пользователем язык обмена. В XML уже имеется валидация, описание структур данных и многое другое. Но у XML есть и обратная, темная сторона. XML — это до предела перегруженный вспомогательными элементами язык. Это атрибуты, теги, пространства имен, разные скобки, кавычки и многое другое. Львиную долю в пакетах SOAP составляет эта служебная информация. В пересчете на миллионы миллионов вызовов мы можем получить очень серьезные накладные расходы на весь этот информационный шум. Помочь «горю» тут сложно, это связано с особенностями использования пространств имен XML и чрезвычайно насыщенной такими семантическими определениями спецификацией SOAP. Применение менее «шумных» форматов данных (например JSON, в спецификации JSON-RPC) сопряжено с другими рисками, такими как несогласованность описаний данных и отсутствие описания их структуры.
Поскольку веб-сервисы — это одна из реализаций концепции RPC, они являются синхронным каналом обмена информацией. Синхронная передача неудобна, она плохо масштабируется и может легко перегрузить систему.
Может показаться, что RPC — это устаревшая концепция, которой в новых реалиях лучше не пользоваться, во избежание разного рода проблем и неприятностей, связанных с ошибками проектирования. Однако мы неспроста затратили так много времени на рассказ о технологиях прошлого. Если взять всё самое лучшее от CORBA, обернуть это в современные архитектурные решения и, подобно веб-сервисам, запустить поверх надежных сетевых протоколов, то мы с вами получим… gRPC!
gRPC — это открытый фреймворк, который придумали и реализовали в Google. Он действительно очень похож на CORBA, но в отличие от нее работает поверх стандартного протокола HTTP версии 2. Эта версия популярного транспорта в значительной степени переработана, расширена и улучшена (в сравнении с предыдущими версиями) и обеспечивает эффективную передачу сообщений с низкой задержкой. CORBA использует свой собственный язык описания интерфейсов IDL (Interface Definition Language). В gRPC для этих же целей применяется современный фреймворк Protocol Buffers. Подобно CORBA, среда gRPC — гетерогенная, она позволяет различным экосистемам эффективно взаимодействовать друг с другом. ProtoBuff использует собственный транспортный формат (сериализация и десериализация объектов), который намного компактнее JSON и XML, оставаясь при этом машинно-независимым форматом. Сегодня gRPC потихоньку вытеснил всё, что только можно, в сегменте внутреннего взаимодействия микросервисов и потихонечку начинает захватывать те области, где раньше царствовали WEB-сервисы и REST. Некоторые отважные разработчики пробуют интегрировать gRPC во frontend! Всё потому, что gRPC был очень удачно спроектирован, он надежен, быстр, позволяет строить информационные системы из гетерогенных узлов и компонентов, подобно владычице морей CORBA.
Однако, предположим: мне не нужно взаимодействие разных экосистем, я программирую только лишь на Python/Golang/Java/(впишите свое) и хочу иметь в своем распоряжении средства для распределения вычислений. Следует ли мне использовать gRPC, который, к слову сказать, потребует определенное время на вхождение в технологию, или есть что-то такое, что выручит меня «сразу и недорого»?
Нам повезло. Сегодня пакеты и сервисные библиотеки, которые предоставляют RPC, есть практически во всех языковых экосистемах, например:
Каждый из вышеупомянутых пакетов внутри языковых экосистем позволит вам связывать компоненты между собой.
Проиллюстрируем это кодом, возьмем простой пример из документации модуля xmlrpc
, стандартной библиотеки языка Python.
Код RPC-сервера:
from xmlrpc.server import SimpleXMLRPCServer
def is_even(n):
return n % 2 == 0
server = SimpleXMLRPCServer(("localhost", 8000))
print("Listening on port 8000...")
server.register_function(is_even, "is_even")
server.serve_forever()
Код RPC-клиента:
import xmlrpc.client
with xmlrpc.client.ServerProxy("http://localhost:8000/") as proxy:
print("3 is even: %s" % str(proxy.is_even(3)))
print("100 is even: %s" % str(proxy.is_even(100)))
Как мы видим, на стороне клиента всё выглядит очень наглядно и просто, словно функция is_even
является частью кода самого клиента. На стороне сервера всё тоже довольно просто и понятно: мы определяем функцию, затем регистрируем ее в контексте серверного процесса, отвечающего за RPC. При этом важно обратить внимание на то, что функция, которую мы «открываем» для доступа извне, — это обычная функция, написанная на языке программирования Python. Ее запросто можно использовать в коде на стороне сервера, локально, передавая ей параметры и получая значение, которая она возвращает. Концепция RPC очень проста, элегантна и гибка: для вызова функции «на стороне» нужно лишь сменить транспорт: с локальных вызовов внутри процесса, на некий сетевой протокол обмена и обеспечить двунаправленную трансляцию параметров и результатов.
Что же не так с RPC и почему мы в итоге получили в свое распоряжение еще и REST? Первая и, пожалуй, самая серьезная причина — RPC обязательно должен иметь некий слой, который описывает характер данных, интерфейсы, функции и возвращаемые вызовы. В CORBA — это IDL, в gRPC — ProtoBuf. Малейшее изменение потребует синхронизации всей совокупности определений и интерфейсов. Второй момент, пожалуй, проистекает из самого понятия «функция» — это черный ящик, который получает на вход аргументы и возвращает некое значение. Функция никак себя не описывает и не характеризует, понять, что именно она делает, можно только вызвав ее и получив некий результат. Соответственно, как и было упомянуто выше, нужно некое описание, опираясь на которое можно определять характер и порядок вычислений.
REST, как уже было сказано в начале этой статьи, — это REpresentational State Transfer, протокол передачи представительного состояния. Следует раскрыть смысл этого понятия — представительный. Это означает «самоописываемый», представляющий себя. Следовательно, некое состояние, которое передается между участниками обмена, не требует дополнительных соглашений, описаний, определений — все необходимое, как говорится, ясно и без слов и содержится в самом сообщении.
Термин REST был введен Роем Филдингом, одним из авторов HTTP, в 2000 году, в его диссертации «Архитектурные стили и дизайн сетевых программных архитектур». Он подвел теоретическую основу под способ взаимодействия клиентов и серверов в глобальной сети, абстрагировав его и назвав «передачей представительного состояния». Рой Филдинг выработал концепцию построения распределенных приложений, в которой каждый запрос (REST-запрос) от клиента к серверу уже содержит в себе исчерпывающую информацию о желаемом ответе сервера (желаемом представительном состоянии), и сервер не обязан сохранять информацию о состоянии клиента («клиентской сессии»).
Как же это устроено? В REST API каждый сервис, каждая единица информации обозначается своим URL. Таким образом, получить данные можно просто обратившись по этому URL к серверу. URL в REST устроен следующим образом:
/object/
— адресует нас к списку объектов/object/id
— адресует нас к единственному объекту с указанным ID или возвращает ответ 404, если такой объект не найденТаким образом, уже сам характер задания URL представляет нам характер ответа сервера: в первом случае — список объектов, во втором — единичный объект.
Но это далеко не всё. REST, как было упомянуто выше, в качестве транспорта использует HTTP. А в HTTP одним из ключевых параметров, определяющих характер возвращаемых сервером данных является метод.
С использованием HTTP-методов мы можем определить еще один ряд самоописываемых состояний:
GET /object/
— возвращает список объектовGET /object/id
— возвращает объект с указанным ID или 404POST /object/
— создает новый объект или возвращает ошибку (чаще всего ошибку с кодом 400 или иную)PUT /object/id
— «редактирует» объект с указанным ID или возвращает ошибкиDELETE /object/id
— удаляет объект с указанным ID или возвращает ошибкиНекоторые серверы игнорируют семантику методов PUT и DELETE, в этом случае используется метод
POST /object/id
с телом запроса (данные объекта) для редактирования или тот же самый POST-запрос с пустым телом для удаления объекта.
Таким образом, вместо богатства выбора, которое нам предоставляет REST, мы получаем минимальный набор операций над данными. В чем же тут выигрыш?
Как уже было сказано выше, REST — это архитектурное решение, а не технология. А это означает, что REST не налагает никаких специальных требований на участников обмена в такой сети так, как это принято в gRPC, CORBA или SOAP. Нужно лишь поддерживать семантику самоопределяемого состояния и единый протокол передачи информации. Таким образом, REST-сети могут сочетать несочетаемое — мощный кластер, с балансировщиками нагрузки, базами данных и простенькую «умную» лампочку, с микроконтроллером, которая управляется посредством REST. Таким образом, REST является чрезвычайно гибкой архитектурой, с практически нулевыми затратами на обеспечение интероперабельности.
Однако, чтобы гарантировать столь впечатляющий результат, REST вводит ряд ограничений (недаром это решение еще называют архитектурными ограничениями).
Кратко перечислим каждое из них.
Архитектура сетей REST должна базироваться на модели «клиент-сервер». Отделение потребности интерфейса клиента от потребностей сервера повышает переносимость кода клиентского интерфейса, а упрощение серверной части улучшает масштабируемость.
Отсутствие состояния. Сервер не должен хранить никакой специальной информации о клиенте в промежутках между вызовами. Традиционные для WEB сессии здесь неприемлемы. Всю необходимую информацию о состоянии клиента сервер должен получать из запроса.
Кеширование. Результаты ответа сервера могут быть закешированы. Это позволит повысить производительность системы. Сервер должен заботиться о том, чтобы клиент получал актуальную информацию, если применяется кеширование.
Единообразие интерфейса. Это касается единого способа записи URL объектов, который уже рассмотрен, и семантики HTTP-методов. Также это предполагает, что в качестве транспортного формата данных используется такой формат, который идентично транслируется и сервером и клиентом. Обычно это JSON, но возможны комбинированные варианты, когда используется JSON и CBOR (характер данных описывается в заголовке Content-type).
Масштабируемость и слои. Клиент не должен делать никаких заключений о том, как устроен сервер. Это позволяет гибко масштабировать системы, использовать кэши, балансировщики нагрузки и многое другое.
Следуя вышеупомянутым ограничениям, можно строить весьма эффективные системы. В этом нас убеждает современный опыт распределенных систем и веб-сервисов.
Один из самых популярных паттернов, который реализуется при помощи REST, — это CRUD. Этот акроним образуется по первым буквам от названия операций Create, Read, Update и Delete. Четыре базовых операции, которых достаточно для работы с любой сущностью данных. Более сложные операции, так называемые use cases (сценарии использования) могут использовать CRUD REST API для доступа к сущностям данных. Use Cases также могут следовать предписаниям и ограничениям REST; в таком случае мы называем нашу информационную систему RESTful. В такой системе везде используются соглашения REST и расширяется такая система также с применением вышеупомянутых соглашений. Это очень прагматичный и в то же время очень гибкий подход: единая архитектура уменьшает сложность системы, а с уменьшением сложности системы уменьшается процент ошибок.
Концепция REST API настолько популярна, что есть практически в каждой языковой экосистеме. REST включен в Django и Laravel. В Go можно использовать пакет Gin Gonic или собрать свою RESTful-систему, применяя только лишь пакеты стандартной библиотеки. Для Erlang можно использовать библиотеку erf, а в Elixir REST API уже встроен в фреймворк Phoenix. REST, будучи архитектурой, не накладывает никаких ограничений на языковые среды, фреймворки и прочее — эта архитектура декларирует сервисам: просто «разговаривайте» на REST и всё будет замечательно.
REST или RPC — «Одно Кольцо, чтобы повелевать всеми»? REST vs RPC – «Скорпион vs Саб-зиро»? Попробуем дать ответ на вопрос, который мы задали в самом начале. Как вы уже поняли из этой весьма обширной статьи, каждый подход обладает своими явными преимуществами и совершенно конкретными недостатками. В данном вопросе всё же хороша золотая середина. Для ответственных сервисов, которые обрабатывают огромные массивы данных, в первую очередь важна стабильность. Как в коде — ошибки в определениях данных тут просто исключены, так и в инфраструктуре — чем быстрее отклик системы, тем лучше. Для таких участков безусловно более удобной будет концепция RPC в ее современной реализации — gRPC. Однако там, где у нас «живет» бизнес-логика и разные сложные процессы взаимодействия уровней информационной системы, жесткий и ограниченный в средствах выражения REST будет более предпочтительным. Разумно и гибко применяйте оба подхода, и ваша информационная система будет использовать выгоды каждой концепции (или архитектурного решения).
Надежные VDS/VPS для ваших проектов
Говоря о RPC и REST в чистом виде, мы всякий раз абстрагировались от инфраструктуры, от языков, от машин, памяти, процессоров и многого другого. Но в реальном бизнесе эта часть также не менее важна. Чаще всего REST API и RPC API развертывают либо в контейнерах (Docker, Podman и подобные), либо на так называемых VPS. Реже — на собственном или арендуемом железе. Инфраструктура как сервис — это удобный и сравнительно недорогой способ для эксплуатации своих проектов. Сетевые службы Timeweb Cloud — идеальное решение для этого. Здесь можно точно рассчитать планируемую нагрузку и сообразно этому планировать свои расходы. Внутренняя сеть VPC от Timeweb Cloud позволит объединять контейнеры и VPS между собой, при этом трафик, который передается по такой сети, полностью изолирован от Интернета. Идеальное решение для RPC, или REST, или…? Решать безусловно вам. А как всё это запустить и как обеспечить бесперебойную работу ваши служб — об этом позаботится Timeweb Cloud.