![](https://habrastorage.org/webt/ay/yn/en/ayynenalpsy41io5vcfwbx1qeie.png)
Introducción
Nos complace anunciar el lanzamiento de una revisión importante de la API de Go para búferes de protocolo , el formato de intercambio de datos independiente del idioma de Google.
Requisitos previos para actualizar la API
Rob Pike introdujo los primeros enlaces de búfer de protocolo para Go en marzo de 2010. Go 1 no se lanzará hasta dentro de dos años.
En los diez años transcurridos desde su primer lanzamiento, el paquete ha crecido y se ha desarrollado junto con Go. Las solicitudes de sus usuarios también han crecido.
Mucha gente quiere escribir programas usando la reflexión para trabajar con mensajes de búfer de protocolo. El paquete le
reflect
permite ver los tipos y valores de Go, pero omite la información del sistema del tipo de búfer de protocolo. Por ejemplo, es posible que necesitemos escribir una función que observe todo el registro y borre cualquier campo que esté anotado como que contiene datos sensibles. Las anotaciones no forman parte del sistema de tipos de Go.
Otra necesidad común es utilizar estructuras de datos distintas a las generadas por el compilador de memoria intermedia de protocolo, como, por ejemplo, un tipo de mensaje dinámico capaz de representar mensajes de tipo desconocido en el momento de la compilación.
También notamos que una fuente común de problemas era que la interfaz
proto.Message
, que identifica los valores de los tipos de mensajes generados, escatima en describir el comportamiento de estos tipos. Cuando los usuarios crean tipos que implementan esta interfaz (a menudo sin darse cuenta al incrustar un mensaje en otra estructura) y pasan valores de esos tipos a funciones que esperan generar valores de mensajes, los programas fallan o se comportan de manera impredecible.
Los tres problemas tienen la misma raíz y una solución: la interfaz
Message
debe definir completamente el comportamiento del mensaje, y las funciones que operan en valores Message
deben aceptar libremente cualquier tipo que implemente correctamente la interfaz.
Dado que no es posible cambiar la definición existente del tipo de mensaje mientras se mantiene la compatibilidad de la API del paquete, decidimos que era hora de comenzar a trabajar en una nueva revisión mayor incompatible del módulo protobuf.
Hoy nos complace lanzar este nuevo módulo. Esperamos que lo disfrutes.
Reflexión
La reflexión es la característica principal de la nueva implementación. Al igual que el paquete
reflect
proporciona una vista de los tipos y valores de Go, el paquete google.golang.org/protobuf/reflect/protoreflect proporciona una vista de los valores según el sistema de tipos de búfer de protocolo.
Una descripción completa del paquete
protoreflect
tomaría demasiado tiempo para esta publicación, pero sin embargo, veamos cómo podemos escribir la función de limpieza de registros que mencionamos anteriormente.
Primero tenemos que escribir un archivo que
.proto
defina una extensión como google.protobuf.FieldOptions para que podamos anotar los campos como confidenciales o no.
syntax = "proto3";
import "google/protobuf/descriptor.proto";
package golang.example.policy;
extend google.protobuf.FieldOptions {
bool non_sensitive = 50000;
}
Podemos usar esta opción para marcar ciertos campos como no sensibles.
message MyMessage {
string public_name = 1 [(golang.example.policy.non_sensitive) = true];
}
A continuación, necesitamos escribir una función Go que tome un valor de mensaje arbitrario y elimine todos los campos sensibles.
// Redact pb.
func Redact(pb proto.Message) {
// ...
}
Esta función acepta
proto.Message
: una interfaz implementada por todos los tipos de mensajes generados. Este tipo es un alias para el tipo definido en el paquete protoreflect
:
type ProtoMessage interface{
ProtoReflect() Message
}
Para evitar llenar el espacio de nombres del mensaje generado, la interfaz contiene solo un método de retorno
protoreflect.Message
que proporciona acceso al contenido del mensaje.
(¿Por qué alias? Porque
protoreflect.Message
tiene un método correspondiente que devuelve el original proto.Message
, y debemos evitar el ciclo de importación entre los dos paquetes).
El método
protoreflect.Message.Range
llama a una función para cada campo llenado en el mensaje.
m := pb.ProtoReflect()
m.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool {
// ...
return true
})
La función de rango se llama
protoreflect.FieldDescriptor
describiendo el tipo de búfer de protocolo del campo y protoreflect.Value que contiene el valor del campo.
El método
protoreflect.FieldDescriptor.Options
devuelve las opciones del campo como un mensaje google.protobuf.FieldOptions
.
opts := fd.Options().(*descriptorpb.FieldOptions)
(¿Por qué escribir aserción? Dado que el paquete generado
descriptorpb
depende de protoreflect
, el paquete protoreflect no puede devolver un tipo específico de opción sin llamar al ciclo de importación).
Luego, podemos verificar las opciones para ver el valor de la variable booleana de nuestra extensión:
if proto.GetExtension(opts, policypb.E_NonSensitive).(bool) {
return true // non-sensitive
}
Tenga en cuenta que aquí estamos mirando el descriptor de campo, no el valor del campo. La información que nos interesa es del sistema de tipo de búfer de protocolo, no de Go.
Este es también un ejemplo de un área en la que hemos simplificado la API del
proto
paquete. El original proto.GetExtension
devolvió un valor y un error. El nuevo proto.GetExtension
solo devuelve el valor, devolviendo el valor predeterminado para el campo si falta. Se informa a los errores de decodificación de extensiones Unmarshal
.
Una vez que hemos identificado el campo que necesita editar, es bastante fácil borrarlo:
m.Clear(fd)
Juntando todo lo anterior, nuestra función de edición se ve así:
// 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
})
}
Una versión mejor podría descender de forma recursiva a los campos de valor del mensaje. Esperamos que este sencillo ejemplo proporcione una introducción a la reflexión en un búfer de protocolo y su uso.
Versiones
Llamamos a los búferes de protocolo de la versión original Go APIv1 y a la versión más reciente APIv2. Dado que APIv2 no es compatible con APIv1, necesitamos usar diferentes rutas de módulo para cada uno.
(Esta versión de la API no es la misma que la versión del protocolo de búfer Idioma:
proto1
, proto2
, y proto3
. APIv1 y APIv2 - esta aplicación particular en el Go, que ayuda tanto en la versión en lengua proto2
y proto3
)
en el módulo github.com/golang/protobuf - APIv1.
En el google.golang.org/protobuf módulo - APIv2. Aprovechamos la necesidad de cambiar la ruta de importación para cambiar a una que no esté vinculada a un proveedor de alojamiento específico. (Nosotros consideramos
google.golang.org/protobuf/v2
para dejar más claro que esta es la segunda versión principal de la API, pero que se decidió por una ruta más corta como la mejor opción a largo plazo).
Entendemos que no todos los usuarios migrarán a la nueva versión principal del paquete a la misma velocidad. Algunos cambiarán rápidamente; otros pueden permanecer indefinidamente en la versión anterior. Incluso dentro del mismo programa, algunas partes pueden usar una API y otras pueden usar otra. Por lo tanto, es importante que continuemos apoyando los programas que utilizan APIv1.
github.com/golang/protobuf@v1.3.4
Es la versión más reciente de APIv1 anterior a APIv2.github.com/golang/protobuf@v1.4.0
Es una versión de APIv1 implementada basada en APIv2. La API es la misma, pero la nueva API admite la implementación básica. Esta versión contiene funciones para convertir entreproto.Message
APIv1 y APIv2 para facilitar la transición entre ellas.google.golang.org/protobuf@v1.20.0
— APIv2.github.com/golang/protobuf@v1.4.0
, , APIv2, APIv1, .
(¿Por qué comenzamos con una versión
v1.20.0
? Para mayor claridad. No esperamos que APIv1 llegue nunca v1.20.0
, por lo que un solo número de versión debería ser suficiente para distinguir de manera única entre APIv1 y APIv2).
Tenemos la intención de continuar admitiendo APIv1 sin establecer fechas límite.
Esta disposición asegura que cualquier programa solo usará una implementación de búfer de protocolo, sin importar qué versión de API use. Esto permite que los programas implementen la nueva API gradualmente o no implementen nada, mientras se mantienen los beneficios de la nueva implementación. El principio de elegir la versión mínima significa que los programas pueden permanecer en la implementación anterior hasta que los mantenedores decidan actualizarla a la nueva (directamente o actualizando las dependencias).
Características adicionales a tener en cuenta
El paquete
google.golang.org/protobuf/encoding/protojson
convierte los mensajes del búfer de protocolo hacia y desde JSON mediante el mapeo JSON canónico y también corrige una serie de problemas con el paquete anterior jsonpb
que eran difíciles de cambiar sin causar nuevos problemas a los usuarios existentes.
El paquete
google.golang.org/protobuf/types/dynamicpb
proporciona una implementación proto.Message
para mensajes cuyo tipo de búfer de protocolo se determina en tiempo de ejecución.
El paquete
google.golang.org/protobuf/testing/protocmp
proporciona funciones para comparar el búfer de protocolo de un mensaje con un paquete github.com/google/cmp
.
Este paquete
google.golang.org/protobuf/compiler/protogen
proporciona soporte para escribir complementos de compilador de búfer de protocolo.
Conclusión
El módulo
google.golang.org/protobuf
es una revisión importante del soporte de Go para el búfer de protocolo, proporcionando soporte de primera clase para la reflexión, mensajería personalizada y una API limpia. Tenemos la intención de seguir respaldando la API anterior como envoltorio de la nueva, lo que permitirá a los usuarios implementar gradualmente la nueva API a su propio ritmo.
Nuestro objetivo con esta actualización es fortalecer los beneficios de la antigua API y abordar sus debilidades. A medida que completamos cada componente de la nueva implementación, comenzamos a usarlo en la base de código de Google. Este lanzamiento gradual nos dio confianza tanto en la usabilidad de la nueva API como en el mejor rendimiento y corrección de la nueva implementación. Estamos seguros de que está listo para la producción.
Estamos muy entusiasmados con este lanzamiento y esperamos que sirva bien al ecosistema Go durante la próxima década o más.
Obtenga más información sobre el curso.