Lorawan-stack: Investigue o futuro do plugin gogo proto

Criado em 25 jun. 2020  ·  15Comentários  ·  Fonte: TheThingsNetwork/lorawan-stack

Resumo

https://github.com/gogo/protobuf não é mais mantido https://github.com/gogo/protobuf/issues/691 (atualmente)

Por que nós precisamos disso?

É nossa dependência, que é incompatível com a nova versão golang/protobuf , da qual mais e mais pacotes dependem, portanto, precisamos substituir a versão golang/protobuf , dependendo das versões desatualizadas de nossas dependências diretas e potencialmente até quebrando pacotes desta forma

O que já está aí? O que você vê agora?

Dependência de gogo/protobuf

O que está faltando? O que você quer ver?

Descobrir isso

Como você pretende implementar isso?

Descobrir se um novo mantenedor aparecerá ou um plugin diferente com paridade de recursos?
Usar apenas protobuf de baunilha?

Como você pretende testar isso?

testes

Você pode fazer isso sozinho e enviar uma solicitação pull?

sim

dependencies technical debt

Comentários muito úteis

Planeje uma ligação para a próxima semana para que possamos discutir off-line.

Acho que devemos passar por esse processo doloroso e nos concentrar em resolver isso em uma ou duas semanas. E para evitar que façamos outras coisas, pois de outra forma, isso vai causar muitos conflitos. Ter o maior número de mãos possível exige saber exatamente o que vamos fazer e em que casos, dividindo as tarefas o máximo possível e de olho no prêmio.

Todos 15 comentários

Plug-in de validação que estamos usando para compatibilidade com GoGo
https://github.com/envoyproxy/protoc-gen-validate/pull/340

Acho que o melhor caminho a seguir é seguir o ecossistema e migrar para longe do gogo / protobuf. Com cada vez mais nossas outras dependências se afastando do gogo, acho que será cada vez mais difícil continuar a usá-lo. É claro que vai dar muito trabalho migrar, então, se fizermos isso, precisaremos apresentar um bom plano.

@rvolosatovs provavelmente sabe mais sobre as opções personalizadas definidas em nosso gerador gogottn , mas aqui está o que encontrei para as opções explícitas em nossos arquivos proto:

  • Poderíamos começar removendo as opções gogoproto.customname , gogoproto.stdtime e gogoproto.stdduration e goproto_enum_prefix . Esses são relativamente fáceis de remover, uma vez que o compilador Go reclamará imediatamente sobre quaisquer problemas resultantes.
  • Remover a opção gogoproto.embed significaria que não podemos mais acessar os campos incorporados (o compilador Go nos ajudará a encontrá-los) e que as mensagens não atenderão mais a algumas interfaces (isso pode ser mais difícil).
  • A opção gogoproto.nullable será muito mais trabalhosa, porque teremos que começar a usar os getters e adicionar nil-checks. Os problemas resultantes podem não ser detectados pelo compilador Go. A solução possível seria tornar temporariamente esses campos privados e, em seguida, reescrever para getters / setters e, finalmente, tornar os campos públicos novamente.
  • O que será bastante problemático são os campos que usam gogoproto.customtype e enums que usam gogoproto.enum_stringer options. Para aqueles, muitas vezes mudamos a maneira como eles são empacotados / desempacotados para JSON. Para os campos bytes , como EUI, DevAddr etc., poderíamos alterar o tipo (nas mensagens proto) para string (que é compatível com o binário). Com os enums, temo que vá quebrar a API JSON, já que eles agora são aceitos (por UnmarshalJSON) como strings e ints.

Talvez este também seja um bom momento para começar a pensar em nossa API v4, porque posso imaginar que possamos descobrir mais algumas surpresas (quebra de API).

Acho que a melhor maneira de avançar seria primeiro experimentar https://github.com/alta/protopatch. Dependendo do resultado:

  • Se todas as nossas necessidades forem atendidas -> migre e esqueça isso (deve ser apenas pesquisar e substituir no diretório api )
  • Se apenas algumas de nossas necessidades forem atendidas e houver opções personalizadas não cobertas pelo plug-in -> devemos avaliar por opção e remover essas opções personalizadas ou, talvez, contribuir para o protopatch se for um recurso de baixo esforço. No entanto, isso realmente depende da opção - se estamos falando de customtype - que a IMO definitivamente justifica a contribuição, mas talvez algo como stdtime - nem tanto.

Olhando para o futuro, não acho que devamos usar diretamente protobuf protos vanilla em componentes internamente em tempo de execução (dado o conjunto de recursos fornecido de protobuf hoje).
Só faz sentido usar protobuf para (des) serialização, ou seja, para armazenamento e na camada API. Internamente, no entanto, usar protos Go gerados por plain vanilla não faz sentido para mim.
Então, por exemplo, NS:

  1. obter *ttnpb.EndDevice (tipo Go gerado por vanilla) do registro, desserializado dos dados binários armazenados
  2. converter *ttnpb.EndDevice em T_device , (NOTA: talvez isso possa ser apenas um invólucro inicialmente ou para sempre)
  3. use T_device internamente no NS
  4. converter T_device em *ttnpb.EndDevice (NOTA: esta pode ser uma tarefa trivial e muito rápida se estivermos usando um wrapper, uma vez que só precisamos modificar os campos alterados e isso pode até mesmo ser executado em binários dados diretamente)
  5. definir *ttnpb.EndDevice , serializar em dados binários

Não sou a favor de uma alternativa (menor) ao gogo. É como empurrar a lata. Vamos manter as coisas o mais simples possível, especialmente quando precisamos decidir novamente qual é o melhor caminho a seguir.

Eu concordo que podemos considerar o uso de tipos intermediários em alguns lugares, em vez de depender de todos os protos gerados. É basicamente separar objetos de transferência de dados (DTOs: protos, também para armazenamento) de objetos de acesso a dados (DAOs: como os usamos). Se for principalmente leitura, também podemos declarar interfaces e ver o quão longe chegamos com isso.

Dito isso, eu não iria tão longe a ponto de mudar todo o NS para usar T_device , mas sim estruturas e / ou interfaces específicas conforme necessário.

Vamos mover essa discussão para novembro

@rvolosatovs qual é sua objeção contra a mudança para o vanilla com um empacotador JSON personalizado?

A enorme carga de migração e cargas de clichê se acabarmos usando apenas protos vanilla diretamente.
Eu realmente não me oponho a isso, só acho que devemos tentar encontrar uma alternativa simples e não intrusiva primeiro e, se isso não for possível, recorrer a refazer tudo isso.

Receio que qualquer plugin com o qual começamos a contar acabará sem manutenção em algum ponto. De um modo geral, sou a favor de manter as coisas o mais próximo possível da baunilha. Se isso significa que nil verifica com mais frequência do que gostaríamos, então que seja. Também pode funcionar a nosso favor que saibamos que as coisas não estão definidas, em vez de uma estrutura inicializada.

Temo que refatorar toda a nossa base de código será uma dor, não importa como o façamos. Nossas protoestruturas (geradas por gogo) são usadas em todos os lugares agora (gRPC API, HTTP API, eventos, erros, internamente, Redis DB, ...), portanto, mudar para outra coisa (seja lá o que for) afetará praticamente tudo em nossa base de código e a forma como está agora, tudo ao mesmo tempo.

O principal requisito é que não quebremos a compatibilidade de nossa API v3. Mesmo se decidirmos usar essa situação como o momento para começar a trabalhar em uma API v4 (pelo menos internamente), ainda teremos que manter o suporte a essa API v3 para os usuários existentes.

A longo prazo, acho que faríamos a nós mesmos um grande favor desacoplando nossas APIs externas (com versão, estável dentro do principal) de nossa API interna (sem versão, estável dentro do menor) e nossos documentos de banco de dados (com versão, estável) . Poderíamos então escrever ou gerar funções para converter entre nossas APIs internas e as outras.

Mas acho que existem alguns passos que já podemos dar agora:

JSON

A fim de manter nossa API JSON v3 compatível, acho que nosso primeiro TODO é trabalhar na geração de marshalers e unmarshalers JSON que entendam como empacotar / desempacotar nossos tipos personalizados. Acho que fazer isso é inteligente de qualquer maneira, porque não há promessa de estabilidade para a implementação do formato JSON do Go para buffers de protocolo , então é melhor termos controle sobre isso nós mesmos. Isso também pode nos permitir considerar as máscaras de campo ao fazer o empacotamento para JSON. No tempo de execução grpc-gateway , podemos registrar codecs, portanto, podemos apenas escrever um codec que chame nossos próprios (gerados) (des) marshalers em vez de jsonpb de {gogo, golang} / protobuf.

Gere novos protos _ ao lado_ dos antigos

Já tentei isso aqui: https://github.com/TheThingsNetwork/lorawan-stack/commit/a41f62d98ae7ee719b576e6fcd2009a79cd38f4c

Isso faz com que o protobuf reclame sobre o registro de tipos, então podemos precisar remover golang_proto.RegisterType de nossos protos antigos para fazer isso funcionar. A remoção disso poderia potencialmente interromper a resolução de google.protobuf.Any , mas só os usamos em erros e eventos, portanto, provavelmente podemos encontrar soluções alternativas para esses casos específicos.

Gere funções para converter entre velhos e novos protos

Isso é apenas para o período de transição, mas para a solução de longo prazo, gostaríamos de gerar conversores semelhantes.

Atualize os serviços um por um

Já tentei isso com um serviço simples aqui: https://github.com/TheThingsNetwork/lorawan-stack/commit/cd7d75c8b42ad15eee1ac594ff6d0f2d5a75eb67 , mas para serviços mais complicados, definitivamente precisaríamos desses conversores.

Observe que isso apenas altera o próprio serviço grpc. O grpc-gateway ainda usa o antigo gogo no lado JSON e, em seguida, chama o servidor gRPC interno, que executa a nova implementação.

Enviou algumas atualizações de dependência inicial e soluções de compatibilidade com versões anteriores aqui: https://github.com/TheThingsNetwork/lorawan-stack/compare/issue/2798-codec

Mais e mais de nossas dependências estão atualizando para protobuf 1.4 e a API V2, e quanto mais mantivermos isso aberto, mais problemas teremos ao tentar atualizar nossas dependências.

Devíamos realmente dar mais prioridade a isso e tomar uma decisão sobre o que faremos a respeito de tudo isso.

Planeje uma ligação para a próxima semana para que possamos discutir off-line.

Acho que devemos passar por esse processo doloroso e nos concentrar em resolver isso em uma ou duas semanas. E para evitar que façamos outras coisas, pois de outra forma, isso vai causar muitos conflitos. Ter o maior número de mãos possível exige saber exatamente o que vamos fazer e em que casos, dividindo as tarefas o máximo possível e de olho no prêmio.

Próximos passos:

  1. @rvolosatovs atualiza as ferramentas para ficar o mais próximo possível do "vanilla":

    • Remova coisas como unconvert , gofumpt e tudo o mais que estivermos fazendo em cima do protocolo

    • Mude de protoc-gen-gogottn para protoc-gen-gofast (ou o que quer que seja mais próximo ao baunilha)

    • Adicione explicitamente (gogoproto.*) options em nossos arquivos proto, para que sejam processados ​​da mesma forma que agora

  2. Precisamos refatorar nosso código para usar Getters em vez de acesso direto ao campo. Talvez ferramentas como gopls e rf possam ajudar com isso.
  3. Começamos removendo (gogoproto.*) options uma por uma e atualizamos o código que as utiliza. Talvez ferramentas como gopls e rf possam ajudar com isso.

    • Removendo gogoproto.populate e atualizando os testes (https://github.com/TheThingsNetwork/lorawan-stack/issues/342)

    • Removendo gogoproto.customname e alterando EUI -> Eui etc.

    • Removendo gogoproto.embed . Precisamos ter certeza de que as mensagens ainda implementam interfaces como ValidateContext(context.Context) error e ExtractRequestFields(m map[string]interface{}) .

    • Removendo gogoproto.nullable e certificando-se de usar Getters sempre que possível e não fazer nenhuma verificação caso contrário.

    • ...

@rvolosatovs vamos tentar dar os primeiros passos para a v3.11.3. Quando terminar, adicione novamente os outros designados e vamos discutir novamente.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

johanstokking picture johanstokking  ·  5Comentários

adamsondelacruz picture adamsondelacruz  ·  7Comentários

htdvisser picture htdvisser  ·  9Comentários

johanstokking picture johanstokking  ·  8Comentários

adriansmares picture adriansmares  ·  9Comentários