Автор Джош Лонг в весеннем блоге
На этой неделе я нахожусь в Сан-Паулу, Бразилия, на QCon SP. У меня была интересная дискуссия с кем-то, кто любит REST-стек Spring, но задавался вопросом, есть ли что-то более эффективное, чем обычный JSON. Действительно, есть! Меня часто спрашивают о поддержке Spring высокоскоростного двоичного кодирования сообщений. Давно поддерживаемая RPC-кодировка в Spring с помощью Hessian, Burlap и т. Д., А в Spring Framework 4.1 появилась поддержка буферов протокола Google, которые также можно использовать со службами REST.
С веб-сайта буфера протокола Google:
Буферы протокола — это независимый от языка, платформенно-независимый, расширяемый механизм Google для сериализации структурированных данных — думайте XML, но меньше, быстрее и проще. Вы определяете, как вы хотите, чтобы ваши данные были структурированы один раз, а затем вы можете использовать специальный сгенерированный исходный код, чтобы легко записывать и считывать ваши структурированные данные в различные потоки данных и из них, используя различные языки…
Google широко использует Protocol Buffers в своей собственной внутренней сервис-ориентированной архитектуре.
.proto
Документ описывает типы (_messages_) должны быть закодированы и содержит язык описания , которые должны быть знакомы любому , кто использовал C struct
s. В документе вы определяете типы, поля в этих типах и их порядок (смещение памяти!) В типе относительно друг друга.
Эти .proto
файлы не являются реализациями — они декларативные описания сообщений , которые могут быть переданы по проводам. Они могут предписывать и проверять ограничения — тип данного поля или количество элементов этого поля — для сообщений, которые кодируются и декодируются. Вы должны использовать компилятор Protobuf, чтобы сгенерировать подходящий клиент для вашего языка.
Вы можете использовать буфер протокола Google в любом случае, но в этом посте мы рассмотрим его использование в качестве способа кодирования полезных нагрузок службы REST. Этот подход эффективен: вы можете использовать согласование контента для обслуживания высокоскоростных полезных нагрузок буфера протокола для клиентов (на любом количестве языков), которые его принимают, и чего-то более традиционного, например JSON, для тех, кто этого не делает.
Сообщения в буфере протокола предлагают ряд улучшений по сравнению с типичными сообщениями в кодировке JSON, особенно в системе полиглотов, где микросервисы реализованы в различных технологиях, но должны иметь возможность рассуждать о связи между службами последовательным и долгосрочным образом.
Протокол Buffers — это несколько приятных функций, которые продвигают стабильные API:
- Протокол буфера предлагает обратную совместимость бесплатно. Каждое поле пронумеровано в буфере протокола, поэтому вам не нужно менять поведение кода в дальнейшем, чтобы поддерживать обратную совместимость со старыми клиентами. Клиенты, которые не знают о новых полях, не будут пытаться разобрать их.
- Протокол Буфера обеспечивают естественное место для указания проверки с использованием
required
,optional
иrepeated
ключевых слов. Каждый клиент применяет эти ограничения по-своему. - Протоколные буферы являются полиглотами и работают со всеми видами технологий . В примере кода только для этого блога показан клиент Ruby, Python и Java для службы Java. Это просто вопрос использования одного из многочисленных поддерживаемых компиляторов.
Вы можете подумать, что можете просто использовать встроенный механизм сериализации Java в однородной сервисной среде, но, поскольку команда Protocol Buffers поспешила указать, когда они впервые представили технологию, есть некоторые проблемы даже с этим. Светильник языка Java В эпическом томе Джоша Блоха « Эффективная Java» на стр. 213 содержатся дополнительные сведения.
Давайте сначала посмотрим на наш .proto
документ:
package demo; option java_package = "demo"; option java_outer_classname = "CustomerProtos"; message Customer { required int32 id = 1; required string firstName = 2; required string lastName = 3; enum EmailType { PRIVATE = 1; PROFESSIONAL = 2; } message EmailAddress { required string email = 1; optional EmailType type = 2 [default = PROFESSIONAL]; } repeated EmailAddress email = 5; } message Organization { required string name = 1; repeated Customer customer = 2; }
Затем вы передаете это определение protoc
компилятору и указываете тип вывода, например так:
protoc -I=$IN_DIR --java_out=$OUT_DIR $IN_DIR/customer.proto
Вот небольшой скрипт Bash, который я собрал для генерации кода для моих различных клиентов:
#!/usr/bin/env bash SRC_DIR=`pwd` DST_DIR=`pwd`/../src/main/ echo source: $SRC_DIR echo destination root: $DST_DIR function ensure_implementations(){ # Ruby and Go aren't natively supported it seems # Java and Python are gem list | grep ruby-protocol-buffers || sudo gem install ruby-protocol-buffers go get -u github.com/golang/protobuf/{proto,protoc-gen-go} } function gen(){ D=$1 echo $D OUT=$DST_DIR/$D mkdir -p $OUT protoc -I=$SRC_DIR --${D}_out=$OUT $SRC_DIR/customer.proto } ensure_implementations gen java gen python gen ruby
Это создаст соответствующие клиентские классы в src/main/{java,ruby,python}
папках. Давайте сначала посмотрим на сам сервис Spring MVC REST.
Spring MVC REST Service
В нашем примере мы зарегистрируем экземпляр Spring Framework 4.1 org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter
. Этот тип является HttpMessageConverter
. HttpMessageConverter
s кодировать и декодировать запросы и ответы в вызовах службы REST. Обычно они активируются после какого-либо согласования содержимого: например, если клиент указывает Accept: application/x-protobuf
, то наша служба REST отправит обратно ответ, закодированный с использованием буфера протокола.
package demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Bean ProtobufHttpMessageConverter protobufHttpMessageConverter() { return new ProtobufHttpMessageConverter(); } private CustomerProtos.Customer customer(int id, String f, String l, Collection<String> emails) { Collection<CustomerProtos.Customer.EmailAddress> emailAddresses = emails.stream().map(e -> CustomerProtos.Customer.EmailAddress.newBuilder() .setType(CustomerProtos.Customer.EmailType.PROFESSIONAL) .setEmail(e).build()) .collect(Collectors.toList()); return CustomerProtos.Customer.newBuilder() .setFirstName(f) .setLastName(l) .setId(id) .addAllEmail(emailAddresses) .build(); } @Bean CustomerRepository customerRepository() { Map<Integer, CustomerProtos.Customer> customers = new ConcurrentHashMap<>(); // populate with some dummy data Arrays.asList( customer(1, "Chris", "Richardson", Arrays.asList("[email protected]")), customer(2, "Josh", "Long", Arrays.asList("[email protected]")), customer(3, "Matt", "Stine", Arrays.asList("[email protected]")), customer(4, "Russ", "Miles", Arrays.asList("[email protected]")) ).forEach(c -> customers.put(c.getId(), c)); // our lambda just gets forwarded to Map#get(Integer) return customers::get; } } interface CustomerRepository { CustomerProtos.Customer findById(int id); } @RestController class CustomerRestController { @Autowired private CustomerRepository customerRepository; @RequestMapping("/customers/{id}") CustomerProtos.Customer customer(@PathVariable Integer id) { return this.customerRepository.findById(id); } }
Большая часть этого кода довольно проста. Это приложение Spring Boot. Spring Boot автоматически регистрирует HttpMessageConverter
bean-компоненты, поэтому нам нужно только определить ProtobufHttpMessageConverter
bean-компонент, и он настраивается соответствующим образом. В @Configuration
семена класса некоторые даты манекена и макет CustomerRepository
объекта. Я не буду воспроизводить тип Java для нашего буфера протокола demo/CustomerProtos.java
, здесь он представляет собой сгенерированный битовый код и код разбора; не все, что интересно читать. Одно удобство состоит в том, что реализация Java автоматически предоставляет методы компоновщика для быстрого создания экземпляров этих типов в Java.
Генерируемые кодом типы являются тупыми struct
объектами. Они подходят для использования в качестве DTO, но не должны использоваться в качестве основы для вашего API. Вы не расширять их с помощью наследования Java , чтобы ввести новые функциональные возможности ; в любом случае это нарушит реализацию, и это плохая практика ООП. Если вы хотите, чтобы все было чище, просто оберните и адаптируйте их соответствующим образом, возможно, обрабатывая преобразование из объекта ORM в тип клиента Protocol Buffer в зависимости от ситуации в этой оболочке.
HttpMessageConverter
s также может быть использован с REST клиента Spring, тем RestTemplate
. Вот соответствующий модульный тест на языке Java:
package demo; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.client.RestTemplate; import java.util.Arrays; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = DemoApplication.class) @WebAppConfiguration @IntegrationTest public class DemoApplicationTests { @Configuration public static class RestClientConfiguration { @Bean RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) { return new RestTemplate(Arrays.asList(hmc)); } @Bean ProtobufHttpMessageConverter protobufHttpMessageConverter() { return new ProtobufHttpMessageConverter(); } } @Autowired private RestTemplate restTemplate; private int port = 8080; @Test public void contextLoaded() { ResponseEntity<CustomerProtos.Customer> customer = restTemplate.getForEntity( "http://127.0.0.1:" + port + "/customers/2", CustomerProtos.Customer.class); System.out.println("customer retrieved: " + customer.toString()); } }
Все работает так, как вы ожидаете, не только в Java и Spring, но также в Ruby и Python. Для полноты, вот простой клиент, использующий Ruby (типы клиентов опущены):
#!/usr/bin/env ruby require './customer.pb' require 'net/http' require 'uri' uri = URI.parse('http://localhost:8080/customers/3') body = Net::HTTP.get(uri) puts Demo::Customer.parse(body)
..и вот клиент в Python (типы клиентов опущены):
#!/usr/bin/env python import urllib import customer_pb2 if __name__ == '__main__': customer = customer_pb2.Customer() customers_read = urllib.urlopen('http://localhost:8080/customers/1').read() customer.ParseFromString(customers_read) print customer
Куда пойти отсюда
Если вы хотите очень высокоскоростное кодирование сообщений, которое работает на нескольких языках, буферные протоколы являются привлекательным вариантом. Существуют и другие технологии кодирования, такие как Avro или Thrift , но ни одна из них не является настолько зрелой и укоренившейся, как буферы протокола. Вам также не обязательно использовать буфер протокола с REST. Вы можете подключить его к какой-либо службе RPC, если это ваш стиль. Клиентских реализаций почти столько же, сколько сборок для Cloud Foundry — так что вы можете запустить практически все на Cloud Foundry и наслаждаться одинаково высокоскоростным, согласованным обменом сообщениями во всех ваших сервисах!
Код для этого примера можно ознакомиться в Интернете , а также, так что не стесняйтесь , чтобы проверить это!
Также..
Привет, банда, в 2015 году я пытался делать посты в стиле случайных технических советов каждую неделю, основываясь на том, что вызывает интерес у сообщества, здесь или в блоге Pivotal . Я использую эти еженедельные _ish_ (ОК! ОК! — не так легко делать их так регулярно, как на этой неделе весной, но до сих пор я не пропустил неделю! :-)) публикует как возможность сосредоточиться не на конкретном новом выпуске как таковом, а на применении Spring в обслуживании к некоторым случаям использования в сообществе, которые могут быть междисциплинарными или просто полезными, если на них будет обращено внимание , До сих пор мы рассматривали разные вещи — Vaadin, Activiti, 12-факторная конфигурация стиля приложения, более интеллектуальные вызовы между сервисами, Couchbase и многое другое и т. Д. — и у нас есть кое-что интересное, выстроенное в очередь, тоже , Однако мне было интересно, о чем вы еще хотите поговорить. Если у вас есть какие-то идеи о том, что вы хотели бы, чтобы они были освещены, или о вашей публикации в сообществе, свяжитесь со мной в Twitter (@starbuxman) или по электронной почте (jlong [at] pivotal [dot] io). ). Я остаюсь, как всегда, к вашим услугам.