
Introdução
Temos o prazer de anunciar o lançamento de uma grande revisão da API Go para buffers de protocolo , o formato de troca de dados independente de idioma do Google.
Pré-requisitos para atualizar a API
As primeiras ligações de buffer de protocolo para Go foram apresentadas por Rob Pike em março de 2010. Go 1 não será lançado por mais dois anos.
Nos dez anos desde o primeiro lançamento, o pacote cresceu e se desenvolveu junto com Go. As solicitações de seus usuários também cresceram.
Muitas pessoas desejam escrever programas usando reflexão para trabalhar com mensagens de buffer de protocolo. O pacote
reflect
oferece a capacidade de visualizar os tipos e valores Go, mas omite as informações do sistema do tipo de buffer do protocolo. Por exemplo, podemos precisar escrever uma função que analise todo o log e limpe qualquer campo anotado como contendo dados confidenciais. As anotações não fazem parte do sistema de tipos do Go.
Outra necessidade comum é usar estruturas de dados diferentes das geradas pelo compilador do buffer de protocolo, como, por exemplo, um tipo de mensagem dinâmica capaz de representar mensagens de tipo desconhecido em tempo de compilação.
Também notamos que uma fonte comum de problemas era que a interface
proto.Message
, que identifica os valores dos tipos de mensagem gerados, economiza na descrição do comportamento desses tipos. Quando os usuários criam tipos que implementam essa interface (muitas vezes inadvertidamente, incorporando mensagens em outra estrutura) e passam valores desses tipos para funções que esperam valores de mensagem gerados, os programas travam ou se comportam de maneira imprevisível.
Todos os três problemas têm a mesma raiz e uma solução: a interface
Message
deve definir completamente o comportamento da mensagem, e as funções que operam em valores Message
devem aceitar livremente qualquer tipo que implemente corretamente a interface.
Visto que não é possível alterar a definição existente do tipo de mensagem mantendo a compatibilidade da API do pacote, decidimos que era hora de começar a trabalhar em uma nova revisão principal incompatível do módulo protobuf.
Hoje temos o prazer de lançar este novo módulo. Esperamos que você goste.
Reflexão
A reflexão é o principal recurso da nova implementação. Assim como o pacote
reflect
fornece uma visualização dos tipos e valores de Go, o pacote google.golang.org/protobuf/reflect/protoreflect fornece uma visualização dos valores de acordo com o sistema de tipo de buffer de protocolo.
Uma descrição completa do pacote
protoreflect
levaria muito tempo para este post, mas mesmo assim, vamos ver como poderíamos escrever a função de limpeza de log que mencionamos anteriormente.
Primeiro, temos que escrever um arquivo
.proto
definindo uma extensão como google.protobuf.FieldOptions para que possamos anotar os campos como contendo informações confidenciais ou não.
syntax = "proto3";
import "google/protobuf/descriptor.proto";
package golang.example.policy;
extend google.protobuf.FieldOptions {
bool non_sensitive = 50000;
}
Podemos usar esta opção para marcar certos campos como não sensíveis.
message MyMessage {
string public_name = 1 [(golang.example.policy.non_sensitive) = true];
}
Então, precisamos escrever uma função Go que pegue um valor de mensagem arbitrário e remova todos os campos confidenciais.
// Redact pb.
func Redact(pb proto.Message) {
// ...
}
Esta função aceita
proto.Message
- uma interface implementada por todos os tipos de mensagens geradas. Este tipo é um alias para o tipo definido no pacote protoreflect
:
type ProtoMessage interface{
ProtoReflect() Message
}
Para evitar o preenchimento do namespace da mensagem gerada, a interface contém apenas um método de retorno
protoreflect.Message
que fornece acesso ao conteúdo da mensagem.
(Por que alias? Porque ele
protoreflect.Message
tem um método correspondente que retorna o original proto.Message
e precisamos evitar o ciclo de importação entre os dois pacotes.)
O método
protoreflect.Message.Range
chama uma função para cada campo preenchido na mensagem.
m := pb.ProtoReflect()
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
// ...
return true
})
A função range é chamada para
protoreflect.FieldDescriptor
descrever o tipo de buffer de protocolo do campo e protoreflect.Value contendo o valor do campo.
O método
protoreflect.FieldDescriptor.Options
retorna as opções de campo como uma mensagem google.protobuf.FieldOptions
.
opts := fd.Options().(*descriptorpb.FieldOptions)
(Por que digitar asserção? Como o pacote gerado
descriptorpb
depende de protoreflect
, o pacote do protorefleto não pode retornar um tipo específico de opção sem chamar o ciclo de importação.)
Então, podemos verificar as opções para ver o valor da variável booleana de nossa extensão:
if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
return true // non-sensitive
}
Observe que aqui estamos olhando para o descritor de campo, não para o valor do campo. As informações nas quais estamos interessados são sobre o sistema de tipo de buffer de protocolo, não sobre Go.
Este também é um exemplo de uma área em que simplificamos a API do
proto
pacote. O original proto.GetExtension
retornou um valor e um erro. O novo proto.GetExtension
retorna apenas o valor, retornando o valor padrão para o campo se estiver ausente. Erros de decodificação de extensão são reportados a Unmarshal
.
Depois de identificar o campo que precisa ser editado, é fácil limpá-lo:
m.Clear(fd)
Juntando todos os itens acima, nossa função de edição se parece com isto:
// Redact pb.
func Redact(pb proto.Message) {
m := pb.ProtoReflect()
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
opts := fd.Options().(*descriptorpb.FieldOptions)
if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
return true
}
m.Clear(fd)
return true
})
}
Uma versão melhor poderia descer recursivamente nos campos de valor da mensagem. Esperamos que este exemplo simples forneça uma introdução à reflexão em um buffer de protocolo e seu uso.
Versões
Chamamos os buffers de protocolo da versão original de Go APIv1 e a versão mais recente de APIv2. Visto que APIv2 não é compatível com versões anteriores APIv1, precisamos usar caminhos de módulo diferentes para cada um.
(Esta versão da API não é o mesmo que a versão do protocolo tampão Idioma:
proto1
, proto2
, e proto3
. APIv1 e APIv2 - esta implementação particular no Go, que suporte tanto a versão do idioma proto2
e proto3
)
no módulo github.com/golang/protobuf - APIv1.
No módulo google.golang.org/protobuf - APIv2. Aproveitamos a necessidade de alterar o caminho de importação para mudar para um que não esteja vinculado a um provedor de hospedagem específico. (Nós consideramos
google.golang.org/protobuf/v2
para deixar mais claro que esta é a segunda versão principal da API, mas decidida por um caminho mais curto como a melhor escolha em longo prazo.)
Entendemos que nem todos os usuários migrarão para a nova versão principal do pacote na mesma velocidade. Alguns mudarão rapidamente; outros podem permanecer na versão antiga indefinidamente. Mesmo dentro do mesmo programa, algumas partes podem usar uma API e outras podem usar outra. Portanto, é importante que continuemos a oferecer suporte a programas que usam APIv1.
github.com/golang/protobuf@v1.3.4
É a versão mais recente da APIv1 anterior à APIv2.github.com/golang/protobuf@v1.4.0
É uma versão do APIv1 implementada com base no APIv2. A API é a mesma, mas a implementação básica é suportada pela nova API. Esta versão contém funções de conversão entreproto.Message
APIv1 e APIv2 para facilitar a transição entre eles.google.golang.org/protobuf@v1.20.0
— APIv2.github.com/golang/protobuf@v1.4.0
, , APIv2, APIv1, .
(Por que começamos com uma versão
v1.20.0
? Para maior clareza. Não esperamos que APIv1 chegue v1.20.0
, então um único número de versão deve ser suficiente para distinguir exclusivamente entre APIv1 e APIv2.)
Pretendemos continuar a oferecer suporte a APIv1 sem definir prazos.
Esse arranjo garante que qualquer programa usará apenas uma implementação de buffer de protocolo, independentemente da versão da API usada. Isso permite que os programas implementem a nova API gradualmente ou não implementem, enquanto mantém os benefícios da nova implementação. O princípio de escolher a versão mínima significa que os programas podem permanecer na implementação antiga até que os mantenedores decidam atualizá-la para a nova (diretamente ou atualizando as dependências).
Recursos adicionais a serem observados
O pacote
google.golang.org/protobuf/encoding/protojson
converte mensagens de buffer de protocolo de e para JSON usando mapeamento JSON canônico e também corrige uma série de problemas com o pacote antigo jsonpb
que eram difíceis de alterar sem causar novos problemas para os usuários existentes.
O pacote
google.golang.org/protobuf/types/dynamicpb
fornece uma implementação proto.Message
para mensagens cujo tipo de buffer de protocolo é determinado em tempo de execução.
O pacote
google.golang.org/protobuf/testing/protocmp
fornece funções para comparar o buffer de protocolo de uma mensagem a um pacote github.com/google/cmp
.
Este pacote
google.golang.org/protobuf/compiler/protogen
fornece suporte para escrever plug-ins de compilador de buffer de protocolo.
Conclusão
O módulo
google.golang.org/protobuf
é uma grande revisão do suporte Go para o buffer de protocolo, fornecendo suporte de primeira classe para reflexão, mensagens personalizadas e uma API limpa. Pretendemos continuar a oferecer suporte à API anterior como um wrapper para a nova, permitindo que os usuários implementem gradualmente a nova API em seu próprio ritmo.
Nosso objetivo com esta atualização é fortalecer os benefícios da API antiga e resolver seus pontos fracos. Conforme concluímos cada componente da nova implementação, começamos a usá-lo na base de código do Google. Essa implementação gradual nos deu confiança na usabilidade da nova API e no melhor desempenho e exatidão da nova implementação. Estamos confiantes de que está pronto para produção.
Estamos muito entusiasmados com este lançamento e esperamos que sirva bem ao ecossistema Go pela próxima década ou mais!
Saiba mais sobre o curso.