Автор Джош Лонг в весеннем блоге
На этой неделе я нахожусь в Сан-Паулу, Бразилия, на 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 structs. В документе вы определяете типы, поля в этих типах и их порядок (смещение памяти!) В типе относительно друг друга.
Эти .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. HttpMessageConverters кодировать и декодировать запросы и ответы в вызовах службы 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("crichardson@email.com")),
customer(2, "Josh", "Long", Arrays.asList("jlong@email.com")),
customer(3, "Matt", "Stine", Arrays.asList("mstine@email.com")),
customer(4, "Russ", "Miles", Arrays.asList("rmiles@email.com"))
).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 в зависимости от ситуации в этой оболочке.
HttpMessageConverters также может быть использован с 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). ). Я остаюсь, как всегда, к вашим услугам.