protocol buffers - protobuf - ¿Cómo maneja el buffer de protocolo el control de versiones?
protoc-gen-go: program not found or is not executable (2)
Google diseñó protobuf para ser bastante tolerante con el control de versiones:
- los datos inesperados se almacenan como "extensiones" (lo que lo hace seguro para el viaje de ida y vuelta) o se eliminan de forma silenciosa, según la implementación
- los campos nuevos generalmente se agregan como "opcional", lo que significa que los datos antiguos se pueden cargar con éxito
sin embargo:
- no renumerar los campos - eso rompería los datos existentes
- normalmente no debería cambiar la forma en que se almacena un campo determinado (es decir, de un int. fijo de 32 bits a un "varint")
Sin embargo, en términos generales, funcionará y no tendrá que preocuparse mucho por las versiones.
¿Cómo manejan los búferes de protocolo el tipo de versión?
Por ejemplo, ¿cuándo necesito cambiar una definición de tipo a lo largo del tiempo? Como agregar y quitar campos.
Sé que esta es una vieja pregunta, pero me encontré con esto recientemente. La forma en que lo manejé es usando fachadas y decisiones de tiempo de ejecución para serializar. De esta manera puedo desaprobar / actualizar un campo a un nuevo tipo, con mensajes antiguos y nuevos manejándolo con gracia.
Estoy usando protobuf.net (v2.3.5) de Marc Gravell y C #, pero la teoría de las fachadas funcionaría para cualquier idioma y la implementación original de protobuf de Google.
Mi clase anterior tenía una marca de tiempo de DateTime que quería cambiar para incluir el "Kind" (un anacronismo de .NET). Agregar esto efectivamente significó que se serializó a 9 bytes en lugar de 8, ¡lo que sería un cambio de serialización de última hora!
[ProtoMember(3, Name = "Timestamp")]
public DateTime Timestamp { get; set; }
Un elemento fundamental de protobuf es ¡NUNCA cambiar los proto ids! Quería leer antiguos binarios serializados, lo que significaba que "3" estaba aquí para quedarse.
Asi que,
Cambié el nombre de la propiedad antigua y la hice privada (sí, aún puede deserializarse a través de la magia de reflexión), ¡pero mi API ya no lo muestra como utilizable!
[ProtoMember(3, Name = "Timestamp-v1")]
private DateTime __Timestamp_v1 = DateTime.MinValue;
Creé una nueva propiedad de marca de tiempo, con un nuevo proto ID, e incluí el DateTime.Kind
[ProtoMember(30002, Name = "Timestamp", DataFormat = ProtoBuf.DataFormat.WellKnown)]
public DateTime Timestamp { get; set; }
Agregué un método de "AfterDeserialization" para actualizar nuestro nuevo tiempo, en el caso de mensajes antiguos.
[ProtoAfterDeserialization]
private void AfterDeserialization()
{
//V2 Timestamp includes a "kind" - we will stop using __Timestamp - so keep it up to date
if (__Timestamp_v1 != DateTime.MinValue)
{
//Assume the timestamp was in UTC - as it was...
Timestamp = new DateTime(__Timestamp_v1.Ticks, DateTimeKind.Utc) //This is for old messages - we''ll update our V2 timestamp...
}
}
Ahora, tengo los mensajes antiguos y nuevos serializando / deserializando correctamente, y mi marca de tiempo ahora incluye DateTime.Kind! Nada roto
Sin embargo, esto significa que AMBOS campos estarán en todos los mensajes nuevos en el futuro. Por lo tanto, el toque final es usar una decisión de serialización en tiempo de ejecución para excluir la marca de tiempo antigua (tenga en cuenta que esto no funcionará si usara el atributo requerido de protobuf)
bool ShouldSerialize__Timestamp_v1()
{
return __Timestamp_v1 != DateTime.MinValue;
}
Y eso es. Tengo una buena prueba de unidad que lo hace de extremo a extremo si alguien lo quiere ...
Sé que mi método se basa en la magia .NET, pero creo que el concepto podría traducirse a otros idiomas ...