Nueva API de Go para búferes de protocolo

En vísperas del inicio del curso "Golang Developer", hemos preparado para ti una traducción de un artículo del blog oficial de Golang.








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 reflectpermite 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 interfazproto.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 Messagedebe definir completamente el comportamiento del mensaje, y las funciones que operan en valores Messagedeben 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 reflectproporciona 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 protoreflecttomarí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 .protodefina 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.Messageque proporciona acceso al contenido del mensaje.



(¿Por qué alias? Porque protoreflect.Messagetiene 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.Rangellama 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.FieldDescriptordescribiendo el tipo de búfer de protocolo del campo y protoreflect.Value que contiene el valor del campo.



El método protoreflect.FieldDescriptor.Optionsdevuelve 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 descriptorpbdepende 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 protopaquete. El original proto.GetExtensiondevolvió un valor y un error. El nuevo proto.GetExtensionsolo 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 proto2y 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 consideramosgoogle.golang.org/protobuf/v2para 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.0Es 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 entre proto.MessageAPIv1 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/protojsonconvierte 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 jsonpbque eran difíciles de cambiar sin causar nuevos problemas a los usuarios existentes.



El paquete google.golang.org/protobuf/types/dynamicpbproporciona una implementación proto.Messagepara mensajes cuyo tipo de búfer de protocolo se determina en tiempo de ejecución.



El paquete google.golang.org/protobuf/testing/protocmpproporciona 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/protogenproporciona soporte para escribir complementos de compilador de búfer de protocolo.



Conclusión



El módulo google.golang.org/protobufes 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.







All Articles