RPC, Messaging, REST: Терминология
Цель данной статьи — обсудить терминологию. Статья — не о том, как и для чего, а только исключительно об использовании терминологии. Статья отражает мнение автора и не претендует на научность.
Вступление
Если вы работаете в области программирования распределенных систем или в интеграции систем, то большая часть изложенного здесь вам не в новинку.
Проблема возникает, когда встречаются люди, использующие разные технологии, и когда эти люди начинают технические разговоры. При этом часто возникает взаимное недопонимание, обусловленное терминологией. Я здесь попытаюсь свести воедино терминологии, используемые в разных контекстах.
Терминология
Четкой терминологии и классификации в этой области нет. Используемая ниже терминология является отражением модели, сложившейся у автора, то есть она строго субъективна. Любая критика и любые обсуждения приветствуются.
Я разделил терминологию на три области: RPC (Remote Procedure Call), Messaging и REST. Эти области имеют под собою исторические корни.
RPC
RPC технологии — наиболее старые технологии. Наиболее яркие представители RPC, это — CORBA и DCOM.
В те времена в основном приходилось связывать системы в быстрых и относительно надежных локальных сетях. Главная идея RPC была в том, чтобы сделать вызов удаленных систем очень похожим на вызов функций внутри программы. Вся механика удаленных вызовов пряталась от программиста. По крайней мере её пытались спрятать. Программисты во многих случаях вынуждены были работать на более глубоком уровне, где появлялись термины маршалинг (marshalling) и unmarshalling (как это по-русски?), что по сути означало сериализацию. Обычные вызовы функций внутри процессов обрабатывались на вызывающей стороне в Proxy, а на стороне системы, выполняющей функцию, в Dispatcher. В идеале ни вызывающая система, ни обрабатывающая система не занимались тонкостями передачи данных между системами. Все эти тонкости сосредотачивались в связке Proxy — Dispatcher, код которых генерировался автоматически.
Поэтому вы не заметите, не должны заметить, никакой разницы между вызовом локальной функции и вызовом удаленной функции.
Сейчас наблюдается своеобразный ренесанс RPC, наиболее яркие представители которого: Google ProtoBuf, Thrift, Avro.
Messaging
С течением времени выяснилось, что попытка оградить программиста от того, что вызываемая функция все же отличается от локальной, не привела к желаемому результату. Детали реализации и принципиальные отличия распределенных систем были слишком велики, чтобы решаться с помощью автоматически генерируемого кода Proxy. Постепенно пришло понимание, что факт того, что системы связывает ненадежная, медленная, низкоскоростная среда, должен быть явно отражен в коде программы.
Появились технологии веб-сервисов. Мы стали говорить ABC: Address, Binding, Contract. Не совсем понятно, почему появились контракты, которые по сути являются Envelope (конвертами) для входных аргументов. Контракты чаще усложняют всю модель, чем упрощают ее. Но… неважно.
Теперь программист явным образом создавал сервис (Service) или клиента (Client), вызывающего сервис. Сервис представлял из себя набор операций (Operation), каждая из которых на входе принимала запрос (Request) и выдавала ответ (Response). Клиент явным образом посылал (Sent) запрос, сервис явным образом получал (Receive) его и отвечал (Sent), высылая ответ. Клиент получал (Receive) ответ и на этом вызов завершался.
Так же, как и в RPC, где-то здесь работали Proxy и Dispatcher. И как прежде их код генерировался автоматически и программисту не надо было в нем разбираться. Разве только что, клиент явным образом использовал классы из Proxy.
Запросы и ответы явным образом преобразуются к формату, предназначенному для передачи по проводам. Чаще всего это массив байт. Преобразование называется Serialization и Deserialization и иногда прячется в коде Proxy.
Кульминация messaging проявилась в появлении парадигмы ESB (Enterprise Service Bus). Никто толком не может сформулировать, что это такое, но все сходятся на том, что данные по ESB движутся в виде сообщений.
REST
В постоянной борьбе со сложностью кода, программисты сделали очередной шаг и создали REST.
Основной принцип REST в том, что операции-функции резко ограничили и оставили только набор операций CRUD: Create — Read — Update — Delete. В этой модели все операции всегда применяются к некоторым данным. Имеющихся в CRUD операций достаточно для большей части приложений. Так как REST технологии в большинстве случаев подразумевают использование протокола HTTP, то команды CRUD отразились на команды HTTP (Post — Get — Put — Delete). Постоянно утверждается, что REST не обязательно привязан к HTTP. Но на практике повсеместно используется отражение сигнатур операций на синтаксис HTTP команд. К примеру, вызов функции
EntityAddress ReadEntityAddress(string param1, string param2)
выразится в таком виде:
GET: entityAddress?param1=value1¶m2=value2
Заключение
Прежде, чем начинать дискуссию по распределенным системам или по интеграции, определитесь с терминологией. Если Proxy всегда будет означать одно и то же в разных контекстах, то, к примеру, request мало что будет значить в терминах RPC, а marshalling вызовет недоумение при обсуждении REST технологий.
Комментарии (20)
sndl
05.08.2015 11:26+2RPC технологии — наиболее старые технологии. Наиболее яркие представители RPC, это — CORBA и DCOM.
Вы, наверное, можете сказать, что они ранние. RPC цветет и пахнет сейчас. CORBA и DCOM — не наиболее яркие предствители. Thrift, Produbuf с новым gRPC. Все вполне актуально…
Детали реализации и принципиальные отличия распределенных систем были слишком велики, чтобы решаться с помощью автоматически генерируемого кода. Постепенно пришло понимание, что факт того, что системы связывает ненадежная, медленная, низкоскоростная среда, должен быть явно отражен в коде программы.
Я не могу проследить тут логику. Кодогенерация RPC имплентацию за вас не сделает. Реализация транспорта (ненадежная, медленная, низкоскоростная среда) может быть отделена от сгенерированного кода и вообще не является проблемой присущей только RPC.
Как именно помогает с этим Messaging?.. Чем контракты веб-сервисов с конвертами (видимо, подразумевается SOAP с его Envelope) отличется от контрактов (API) в RPC?lair
05.08.2015 12:02Я не могу проследить тут логику. Кодогенерация RPC имплентацию за вас не сделает. Реализация транспорта (ненадежная, медленная, низкоскоростная среда) может быть отделена от сгенерированного кода и вообще не является проблемой присущей только RPC.
Как именно помогает с этим Messaging?..
RPC предпочтительно синхронен (и гарантированно точка-точка). Messaging изначально асинхронен, не требует точка-точка, не требует ответа, позволяет легко добавить гарантированную доставку, маршрутизацию, обработку ошибок и так далее.sndl
05.08.2015 12:30Cинхронность RPC в настоящее время никак не предпочтительна. Насчет отличий Messaging, которые Вы приводите, согласен. Но Ваше (и мое) понятие этого термина существенно отличается от того, что приводится в этой статье.
C картинки Messaging описывается как:
Response GetPerson(Request request)
В пример приводятся веб-сервисы, контракты и тд…lair
05.08.2015 12:38Cинхронность RPC в настоящее время никак не предпочтительна.
… только в языках, которые умеют нативные асинхронные вызовы. Иначе уже не RPC получается.
Но Ваше (и мое) понятие этого термина существенно отличается от того, что приводится в этой статье.
К статье у меня вопросов на отдельный комментарий.
Response GetPerson(Request request)
Это не messaging, это (семантически) тот же RPC, просто с другими типами данных. Честный messaging — это:
void Send(Message msg); ... class GetPersonRequest: Message { Guid PersonId; }
Leo_Gan
05.08.2015 18:36У вас сложилась модель, в которой RPC — Messaging ложится на термины synchronous — asynchronous. В моей модели такой явной связи нет. Но я не претендую на истину. Я видел рассуждения, подтверждающие вашу мысль. Думаю в этом что-то есть.
«Это не messaging, это (семантически) тот же RPC, просто с другими типами данных.»
Да, вы правы. Я именно об этом и говорил.lair
05.08.2015 18:38У вас сложилась модель, в которой RPC — Messaging ложится на термины synchronous — asynchronous. В моей модели такой явной связи нет. Но я не претендую на истину. Я видел рассуждения, подтверждающие вашу мысль. Думаю в этом что-то есть.
Вызов процедур в большей части языков синхронен, поэтому RPC исторически синхронная парадигма. Messaging — точно так же исторически — асинхронная.
Да, вы правы. Я именно об этом и говорил.
О чем?
Leo_Gan
05.08.2015 18:21Спасибо за дискуссию!
По новым RPC я полностью согласен! Пойду текст подправлю. Как же у меня из головы ProtoBuf выскочил.
По «автоматически генерируемый код», я имел в виду генерацию Proxy и Dispatcher. Надо тоже текст уточнить. Спасибо!
lair
05.08.2015 12:00+1RPC технологии
RPC — это не технология, это подход, характеризующийся «процедурностью» мышления — мы туда аргументы, нам оттуда результат. Желательно синхронно.
Поэтому вы не заметите, не должны заметить, никакой разницы между вызовом локальной функции и вызовом удаленной функции.
А это совершенно не обязательное свойство RPC. WCF — тоже RPC, но его дизайн прекрасно предполагает long-distance calls. Вообще, разница между локальным и удаленным — это дизайн API, а не технология или RPC-vs-messaging.
Messaging
[...]
Появились технологии веб-сервисов.
Давайте, опять-таки, не путать. Есть messaging, это интеграционный подход (см. Enteprise Integration Patterns); есть веб-сервисы, это группа технологий, которые могут реализовывать как RPC, так и messaging (и, скажем, WCF, пусть и веб-сервисы, в базовом виде предполагает именно RPC-стиль).
Не совсем понятно, почему появились контракты, которые по сути являются Envelope (конвертами) для входных аргументов.
Нет, контракты — это не конверты для входных аргументов. Контракт — это набор допустимых операций и данных в этих операциях (если шире — то еще предусловий, постусловий и инвариантов, но в распределенных вычислениях нас это не очень интересует). И именно поэтому, кстати, контракты чаще применяются к RPC-стилю, чем к messaging-стилю (потому что вот сообщение действительно чаще определяется схемой, нежели контрактом).
Сервис представлял из себя набор операций (Operation), каждая из которых на входе принимала запрос (Request) и выдавала ответ (Response). Клиент явным образом посылал (Sent) запрос, сервис явным образом получал (Receive) его и отвечал (Sent), высылая ответ. Клиент получал (Receive) ответ и на этом вызов завершался.
Вы c WCF или ASMX работали когда-нибудь?
Запросы и ответы явным образом преобразуются к формату, предназначенному для передачи по проводам.
Явным для кого? Если для программиста, то нет, я ни разу не видел адекватного messaging-решения, в котором бы была нужна явная сериализация, работа всегда идет с типизированным или хотя бы ключ-значение форматом.
Чаще всего это массив байт
Нет, понятно, что по проводам чаще всего передают массив байтов. Но вот утверждать, что это основной формат для messaging-решений я бы не стал. Как раз наоборот, messaging предполагает self-contained-сообщения, а для этого они должны быть легко понятны на промежуточных узлах, а это означает, что используются общеизвестные форматы. В период широкого расцвета этого добра использовался XML, сейчас тоже он используется, другое дело, что бесит всех адски.
Основной принцип REST в том, что операции-функции резко ограничили и оставили только набор операций CRUD: Create — Read — Update — Delete.
Нет, основной принцип REST в том, что состояние описано в униформном интерфейсе, предпочтительно — гипермедийном.
К примеру, вызов функции
EntityAddress ReadEntityAddress(string param1, string param2)
выразится в таком виде:
GET: entityAddress?param1=value1¶m2=value2
Это совершенно не обязательно REST. Более того, передача параметров в query string намекает нам, что это скорее всего не REST. А вотGET /entities/187/address
— это, скорее всего, REST.RPG18
05.08.2015 18:45Куда вы сунете AMQP, на котором при необходимости можно сделать RPC?
lair
05.08.2015 18:51Для кода, который вызывает
response = fibonacci_rpc.call(30)
— это RPC, потому что для него это синхронный блокирующий вызов (и транспорт для него не имеет значения). Для кодаFibonacciRpcClient
, который явным образом взамодействует с очередями, сообщениями и корреляцией, это типичный messaging.
Наглядная демонстрация того, что это всего лишь подходы, и одно при необходимости конвертируется в другое.
Leo_Gan
05.08.2015 19:05Хороший вопрос. Я бы его расширил: Куда деть очереди (queues)?
Хмм… Думаю, что этот термин используют все три типа, RPC, Messaging, REST. REST — меньшей степени (?)lair
05.08.2015 19:29А чем подход, использующий очереди, отличается от подхода, использующего сообщения? Как вообще, по вашему, передаются сообщения?
Leo_Gan
05.08.2015 19:38Сообщения можно передать синхронно и асинхронно. Очереди можно использовать, а можно и не использовать. На самом деле очереди используются на многих реализациях транспортных протоколов. Если и не явным образом (без пользовательского доступа), то в конкретных реализациях очереди очень часто присутствуют.
Здесь я рассуждаю о терминологии application layer, хотя явно и не говорю об этом. Очереди присутствуют на разный layers, к примеру транспорт может быть целиком сделан на очередях (MSMQ, MQ). Но я здесь не об этом. Я тупо о том, какие термины всплывают в каком контексте. Грубо определил три контектса. Грубо раскидал термины по ним. Никто не определял эти контектсы с научной, непротиворечивой точки зрения.lair
05.08.2015 19:41Я тупо о том, какие термины всплывают в каком контексте. Грубо определил три контектса. Грубо раскидал термины по ним. Никто не определял эти контектсы с научной, непротиворечивой точки зрения.
Если у вас (терминологически) неверно определены контексты, то любое последующее терминологическое разделение бессмысленно.
(не говоря уже о том, что я вообще не понимаю, зачем вам нужно раскидывать термины по контекстам, учитывая, что каждый термин обозначает конкретное явление, и есть оно в контексте, или нет — вопрос не терминологический, а бытийный)
Gaer
05.08.2015 20:27На картинке в REST: «POST = Update», «PUT = Create». Разве не наоборот «POST = Create», «PUT = Update»?
lair
05.08.2015 21:52На самом деле, третьим образом. PUT может использоваться как для создания, так и для обновления, POST может использоваться для создания или выполнения произвольного действия, а для обновления еще может использоваться PATCH.
Meredian
REST != CRUD. CRUD — это только паттерн управления ресурсами, а REST — это очень широкий архитектурный подход к проектирования API, который позволяет целиком перенести состояние системы в запрос.