Integração Netty com Protocol Buffers para Comunicação em Rede

Ao construir aplicações de rede de alta performance com Netty, o Protocol Buffers (Protobuf) surge como uma alternativa eficiente para serialização de dados. Esta abordagem oferece melhor desempenho e menor footprint de memória comparada ao JSON ou XML tradicional.

Definição dos Contratos de Mensagem

O primeiro passo consiste em criar os arquivos .proto que descrevem a estrutura das mensagens trocadas entre cliente e servidor.

Arquivo de Requisição

Crie o arquivo OrderRequest.proto dentro do diretório src/main/proto:

syntax = "proto2";
package com.example.nettymessages;
option java_package = "com.example.nettymessages";
option java_outer_classname = "OrderRequestProto";

message OrderRequest {
    required int32 orderId = 1;
    required string customerName = 2;
    required string productCode = 3;
    repeated string deliveryNotes = 4;
}

Arquivo de Resposta

Em seguida, defina OrderResponse.proto na mesma localização:

syntax = "proto2";
package com.example.nettymessages;
option java_package = "com.example.nettymessages";
option java_outer_classname = "OrderResponseProto";

message OrderResponse {
    required int32 orderId = 1;
    required int32 statusCode = 2;
    repeated string feedbackMessages = 3;
}

Considerações sobre Sintaxe

Ao optar pela versão proto3, é necessário alterar a diretiva de sintaxe e remover os modificadores de obrigatoriedade:

syntax = "proto3";

No proto3, todos os campos são opcionais por padrão, eliminando a necessidade das palavras-chave required e optional. O operador repeated continua sendo utilizado para campos de lista.

Geração das Classes Java

Execute o compilador protobuf para gerar as classes Java correspondentes. Com Maven, utilize o plugin protobuf-maven-plugin:

<plugin>
    <groupId>org.xolstice.maven.plugins</groupId>
    <artifactId>protobuf-maven-plugin</artifactId>
    <version>0.6.1</version>
    <configuration>
        <protocExecutable>/usr/local/bin/protoc</protocExecutable>
        <protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
        <outputDirectory>${project.build.directory}/generated-sources</outputDirectory>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

As classes geradas conterão toda a lógica de serialização, desserialização, builders e parsers necessários para o funcionamento correto. Caso apareçam avisos no IDE relacionados a estilos de código, isso não afeta a funcionalidade — são apenas sugestões de convenções de formatação.

Estrutura da Classe Gerada - OrderResponse

A classe OrderResponseProto gerada pelo compilador encapsula a definição da mensagem com todas as funcionalidades necessárias:

// Código gerado pelo compilador protobuf
// Fonte: OrderResponse.proto

package com.example.nettymessages;

public final class OrderResponseProto {
  private OrderResponseProto() {}

  public static void registerAllExtensions(
      com.google.protobuf.ExtensionRegistryLite registry) {
  }

  public static void registerAllExtensions(
      com.google.protobuf.ExtensionRegistry registry) {
    registerAllExtensions(
        (com.google.protobuf.ExtensionRegistryLite) registry);
  }

  public interface OrderResponseOrBuilder extends
      com.google.protobuf.MessageOrBuilder {

    boolean hasOrderId();
    int getOrderId();

    boolean hasStatusCode();
    int getStatusCode();

    java.util.List<String> getFeedbackMessagesList();
    int getFeedbackMessagesCount();
    String getFeedbackMessages(int index);
    com.google.protobuf.ByteString getFeedbackMessagesBytes(int index);
  }

  public static final class OrderResponse extends
      com.google.protobuf.GeneratedMessageV3 implements
      OrderResponseOrBuilder {
    
    private static final long serialVersionUID = 0L;

    private OrderResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
      super(builder);
    }

    private OrderResponse() {
      orderId_ = 0;
      statusCode_ = 0;
      feedbackMessages_ = com.google.protobuf.LazyStringArrayList.EMPTY;
    }

    // Campos da mensagem
    private int bitField0_;
    public static final int ORDERID_FIELD_NUMBER = 1;
    private int orderId_;
    
    public boolean hasOrderId() {
      return ((bitField0_ & 0x00000001) == 0x00000001);
    }
    
    public int getOrderId() {
      return orderId_;
    }

    public static final int STATUSCODE_FIELD_NUMBER = 2;
    private int statusCode_;
    
    public boolean hasStatusCode() {
      return ((bitField0_ & 0x00000002) == 0x00000002);
    }
    
    public int getStatusCode() {
      return statusCode_;
    }

    public static final int FEEDBACKMESSAGES_FIELD_NUMBER = 3;
    private com.google.protobuf.LazyStringList feedbackMessages_;
    
    public com.google.protobuf.ProtocolStringList getFeedbackMessagesList() {
      return feedbackMessages_;
    }
    
    public int getFeedbackMessagesCount() {
      return feedbackMessages_.size();
    }
    
    public String getFeedbackMessages(int index) {
      return feedbackMessages_.get(index);
    }
    
    public com.google.protobuf.ByteString getFeedbackMessagesBytes(int index) {
      return feedbackMessages_.getByteString(index);
    }

    private byte memoizedIsInitialized = -1;
    @Override
    public final boolean isInitialized() {
      byte isInitialized = memoizedIsInitialized;
      if (isInitialized == 1) return true;
      if (isInitialized == 0) return false;

      if (!hasOrderId()) {
        memoizedIsInitialized = 0;
        return false;
      }
      if (!hasStatusCode()) {
        memoizedIsInitialized = 0;
        return false;
      }
      memoizedIsInitialized = 1;
      return true;
    }

    // Builder pattern para construção das mensagens
    public static final class Builder extends
        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
        OrderResponseOrBuilder {
      
      private int bitField0_;
      private int orderId_;
      private int statusCode_;
      private com.google.protobuf.LazyStringList feedbackMessages_ = 
          com.google.protobuf.LazyStringArrayList.EMPTY;

      public Builder setOrderId(int value) {
        bitField0_ |= 0x00000001;
        orderId_ = value;
        onChanged();
        return this;
      }

      public Builder setStatusCode(int value) {
        bitField0_ |= 0x00000002;
        statusCode_ = value;
        onChanged();
        return this;
      }

      private void ensureFeedbackMessagesIsMutable() {
        if (!((bitField0_ & 0x00000004) == 0x00000004)) {
          feedbackMessages_ = new com.google.protobuf.LazyStringArrayList(feedbackMessages_);
          bitField0_ |= 0x00000004;
        }
      }

      public Builder addFeedbackMessages(String value) {
        if (value == null) {
          throw new NullPointerException();
        }
        ensureFeedbackMessagesIsMutable();
        feedbackMessages_.add(value);
        onChanged();
        return this;
      }

      public Builder clearFeedbackMessages() {
        feedbackMessages_ = com.google.protobuf.LazyStringArrayList.EMPTY;
        bitField0_ = (bitField0_ & ~0x00000004);
        onChanged();
        return this;
      }
    }

    public static OrderResponse getDefaultInstance() {
      return DEFAULT_INSTANCE;
    }

    private static final OrderResponse DEFAULT_INSTANCE;
    static {
      DEFAULT_INSTANCE = new OrderResponse();
    }

    public static OrderResponse parseFrom(java.nio.ByteBuffer data)
        throws com.google.protobuf.InvalidProtocolBufferException {
      return PARSER.parseFrom(data);
    }

    public static OrderResponse parseFrom(byte[] data)
        throws com.google.protobuf.InvalidProtocolBufferException {
      return PARSER.parseFrom(data);
    }

    @Deprecated
    public static final com.google.protobuf.Parser<OrderResponse> PARSER = 
        new com.google.protobuf.AbstractParser<OrderResponse>() {
          @Override
          public OrderResponse parsePartialFrom(
              com.google.protobuf.CodedInputStream input,
              com.google.protobuf.ExtensionRegistryLite extensionRegistry)
              throws com.google.protobuf.InvalidProtocolBufferException {
            return new OrderResponse(input, extensionRegistry);
          }
        };
  }

  public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() {
    return descriptor;
  }

  private static com.google.protobuf.Descriptors.FileDescriptor descriptor;
  static {
    // Metadados do descritor serializados
  }
}

Uso Prático com Netty

Para integrar as mensagens Protobuf com Netty, configure os handlers de codec na pipeline do canal:

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;

public class ProtobufChannelInitializer extends ChannelInitializer<SocketChannel> {
    
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline()
          .addLast(new ProtobufVarint32FrameDecoder())
          .addLast(new ProtobufDecoder(OrderResponseProto.OrderResponse.getDefaultInstance()))
          .addLast(new ProtobufVarint32LengthFieldPrepender())
          .addLast(new ProtobufEncoder())
          .addLast(new BusinessHandler());
    }
}

Construa uma mensagem de requisição utilizando o builder pattern fornecido pela classe gerada:

OrderRequestProto.OrderRequest request = OrderRequestProto.OrderRequest.newBuilder()
    .setOrderId(1001)
    .setCustomerName("Empresa XYZ")
    .setProductCode("PRD-5678")
    .addDeliveryNotes("Entrega na portaria principal")
    .addDeliveryNotes("Contatar recepção antes da entrega")
    .build();

As classes geradas pelo compilador protobuf podem ser utilizadas diretamente no projeto após ajustar o pacote e a estrutura de diretórios conforme a configuração do seu build. Certifique-se de incluir a dependência do Protobuf no arquivo de configuração do gerenciador de dependências.

Tags: Netty protobuf protocol-buffers serialização Networking

Publicado em 8-6 23:21