Lorawan-stack: Enquêter sur l'avenir du plugin gogo proto

Créé le 25 juin 2020  ·  15Commentaires  ·  Source: TheThingsNetwork/lorawan-stack

Résumé

https://github.com/gogo/protobuf n'est plus maintenu https://github.com/gogo/protobuf/issues/691 (actuellement)

Pourquoi avons nous besoin de ça?

C'est notre dépendance, qui est incompatible avec la nouvelle version golang/protobuf , dont dépendent de plus en plus de packages, nous devons donc remplacer la version golang/protobuf , en fonction des versions obsolètes de nos dépendances directes et potentiellement même casser les paquets de cette façon

Qu'est-ce qu'il y a déjà ? Que voyez-vous maintenant?

gogo/protobuf dépendance

Que manque-t-il? Qu'est-ce que tu veux voir?

Comprenez ceci

Comment proposez-vous de mettre cela en œuvre ?

Déterminer si un nouveau mainteneur apparaîtra ou un plugin différent avec la parité des fonctionnalités ?
Utiliser uniquement du protobuf à la vanille ?

Comment proposez-vous de tester cela ?

essais

Pouvez-vous le faire vous-même et soumettre une Pull Request ?

Oui

dependencies technical debt

Commentaire le plus utile

Veuillez planifier un appel pour la semaine prochaine afin que nous puissions discuter hors ligne.

Je pense que nous devrions traverser ce processus douloureux et nous concentrer sur la résolution de cela dans une semaine ou deux. Et pour éviter que nous fassions autre chose car cela va provoquer beaucoup de conflits sinon. Avoir le plus de mains possible nécessite de savoir exactement ce que l'on va faire dans quels cas, de répartir les tâches autant que possible et de garder un œil sur le prix.

Tous les 15 commentaires

Le plugin de validation que nous utilisons a abandonné la prise en charge de GoGo
https://github.com/envoyproxy/protoc-gen-validate/pull/340

Je pense que la meilleure façon d'avancer est de suivre l'écosystème et de migrer loin de gogo/protobuf. Avec de plus en plus de nos autres dépendances qui s'éloignent de gogo, je pense qu'il deviendra de plus en plus difficile de continuer à l'utiliser. Bien sûr, la migration va demander beaucoup de travail, donc si nous le faisons, nous devons élaborer un bon plan.

@rvolosatovs en sait probablement plus sur les options personnalisées définies dans notre générateur gogottn , mais voici ce que j'ai trouvé pour les options explicites dans nos fichiers proto :

  • Nous pourrions commencer par supprimer les options gogoproto.customname , gogoproto.stdtime et gogoproto.stdduration , et goproto_enum_prefix . Ceux-ci sont relativement faciles à supprimer, car le compilateur Go se plaindra immédiatement de tout problème résultant.
  • Supprimer l'option gogoproto.embed signifierait que nous ne pouvons plus accéder aux champs intégrés (le compilateur Go nous aidera à les trouver), et que les messages ne satisfont plus certaines interfaces (cela peut être plus difficile).
  • L'option gogoproto.nullable demandera beaucoup plus de travail, car nous devrons commencer à utiliser les getters et ajouter des contrôles nil. Les problèmes résultants peuvent ne pas être détectés par le compilateur Go. Une solution de contournement serait de rendre temporairement ces champs privés, puis de les réécrire en getters/setters et enfin de rendre les champs à nouveau publics.
  • Ce qui va être assez problématique, ce sont les champs qui utilisent gogoproto.customtype et les énumérations qui utilisent les options gogoproto.enum_stringer . Pour ceux-là, nous avons souvent changé la façon dont ils sont marshalés/désassemblés en JSON. Pour les champs personnalisés bytes tels que EUI, DevAddr, etc., nous pourrions changer le type (dans les messages proto) en string (qui est compatible binaire). Avec les énumérations, je crains que cela ne brise l'API JSON, car celles-ci sont désormais acceptées (par UnmarshalJSON) à la fois comme des chaînes et des entiers.

C'est peut-être aussi le bon moment pour commencer à penser à notre API v4, car je peux imaginer que nous pourrions découvrir d'autres surprises (rupture d'API).

Je pense que la meilleure façon d'avancer serait d'abord d'essayer https://github.com/alta/protopatch. En fonction du résultat :

  • Si tous nos besoins sont couverts -> migrez et oubliez cela (devrait être simplement rechercher et remplacer dans le répertoire api )
  • Si seulement certains de nos besoins sont couverts et qu'il existe des options personnalisées non couvertes par le plugin -> nous devrions évaluer par option et supprimer ces options personnalisées ou, peut-être, contribuer au protopatch s'il s'agit d'un fonction à faible effort. Cela dépend vraiment de l'option, cependant - si nous parlons de customtype - que l'OMI justifie définitivement de contribuer, mais peut-être quelque chose comme stdtime - pas tellement.

Pour l'avenir, je ne pense pas que nous devrions utiliser directement les protos vanilla protobuf dans les composants en interne au moment de l'exécution (étant donné l'ensemble de fonctionnalités fourni de protobuf aujourd'hui).
Il n'est logique d'utiliser protobuf que pour la (dé-)sérialisation, donc pour le stockage et sur la couche API. En interne, cependant, l'utilisation de protos Go générés à la vanille n'a aucun sens pour moi.
Ainsi, par exemple, NS :

  1. obtenir *ttnpb.EndDevice (type Go généré à la vanille) à partir du registre, désérialisé à partir des données binaires stockées
  2. convertir *ttnpb.EndDevice en T_device , (REMARQUE : cela pourrait être juste un wrapper initialement ou pour toujours)
  3. utiliser T_device interne en NS
  4. convertir T_device en *ttnpb.EndDevice (REMARQUE : cela pourrait être une tâche triviale et très rapide si nous utilisons un wrapper, car nous avons seulement besoin de modifier les champs modifiés et cela pourrait même être effectué sur binaire données directement)
  5. définir *ttnpb.EndDevice , sérialiser en données binaires

Réfs également https://github.com/TheThingsNetwork/lorawan-stack/issues/342 (populators générés)

Je ne suis pas en faveur d'une alternative (plus petite) à gogo. C'est comme pousser la boîte. Gardons les choses aussi simples que possible, surtout lorsque nous devons décider à nouveau quelle est la meilleure voie à suivre.

Je suis d'accord que nous pouvons envisager d'utiliser des types intermédiaires à certains endroits, au lieu de compter partout sur les protos générés. Il s'agit essentiellement de séparer les objets de transfert de données (DTO : protos, également pour le stockage) des objets d'accès aux données (DAO : comment nous les utilisons). S'il s'agit principalement de lecture, nous pouvons également déclarer des interfaces et voir jusqu'où nous allons.

Cela dit, je n'irais pas jusqu'à changer l'ensemble du NS en utilisant T_device , mais plutôt des structures et/ou des interfaces spécifiques selon les besoins.

Déplaçons cette discussion en novembre

@rvolosatovs quelle est votre objection contre le passage à la vanille avec un marshaler JSON personnalisé ?

L'énorme charge de migration et les charges de passe-partout si nous finissons par utiliser directement les protos vanille.
Je ne m'y oppose pas vraiment, je pense juste que nous devrions d'abord essayer de trouver une alternative simple et non intrusive et si ce n'est pas possible, alors retravailler tout cela.

Je crains que tout plugin sur lequel nous commençons à nous appuyer ne se retrouve à un moment donné dans un état non maintenu. D'une manière générale, je suis favorable à ce que les choses soient aussi proches que possible de la vanille. Si cela signifie que nil vérifient plus souvent que nous le souhaitons, qu'il en soit ainsi. Cela peut également jouer en notre faveur que nous sachions que les choses ne sont pas définies, au lieu d'une structure initialisée.

Je crains que la refactorisation de l'ensemble de notre base de code ne soit pénible, peu importe comment nous le faisons. Nos proto structs (générés par gogo) sont utilisés partout en ce moment (API gRPC, API HTTP, événements, erreurs, en interne, Redis DB, ...), donc passer à autre chose (quelle que soit cette autre chose) va toucher à peu près tout dans notre base de code, et à quoi il ressemble maintenant, le tout en même temps.

L'exigence stricte est que nous ne rompons pas la compatibilité de notre API v3. Même si nous décidons d'utiliser cette situation comme moment pour commencer à travailler sur une API v4 (au moins en interne), nous devrons toujours continuer à prendre en charge cette API v3 pour les utilisateurs existants.

À long terme, je pense que nous nous rendrions service en dissociant nos API externes (versionnées, stables dans la majeure) de nos API internes (non versionnées, stables dans la mineure) et de nos documents de base de données (versionnées, stables). . Nous pourrions alors écrire ou générer des fonctions à convertir entre nos API internes et les autres.

Mais je pense qu'il y a des mesures que nous pouvons déjà prendre maintenant :

JSON

Afin de garder notre API JSON v3 compatible, je pense que notre premier TODO est de travailler sur la génération de marshalers et unmarshalers JSON qui comprennent comment marshaler/unmarshaler nos types personnalisés. Je pense que faire cela est de toute façon intelligent car il n'y a aucune promesse de stabilité pour l'implémentation par Go du format JSON pour les tampons de protocole , nous ferions donc mieux de contrôler cela nous-mêmes. Cela pourrait également nous permettre d'envisager des masques de champ lors du marshaling vers JSON. Dans l'environnement d'exécution grpc-gateway nous pouvons enregistrer des codecs, nous pouvons donc simplement écrire un codec qui appelle nos propres (générés) (un) marshalers au lieu du jsonpb de {gogo,golang}/protobuf.

Générer de nouveaux protos à côté des anciens

J'ai déjà essayé ça ici : https://github.com/TheThingsNetwork/lorawan-stack/commit/a41f62d98ae7ee719b576e6fcd2009a79cd38f4c

Cela amène protobuf à se plaindre du registre des types, nous devrons donc peut-être supprimer golang_proto.RegisterType de nos anciens protos pour que cela fonctionne. Supprimer cela pourrait potentiellement interrompre la résolution de google.protobuf.Any , mais nous ne les utilisons que dans les erreurs et les événements, nous pouvons donc probablement trouver des solutions de contournement pour ces cas spécifiques.

Générer des fonctions pour convertir entre les anciens et les nouveaux protos

Ceci est pour la période de transition uniquement, mais pour la solution à long terme, nous voudrions générer des convertisseurs similaires.

Mettre à jour les services un par un

J'ai déjà essayé cela avec un service simple ici : https://github.com/TheThingsNetwork/lorawan-stack/commit/cd7d75c8b42ad15eee1ac594ff6d0f2d5a75eb67 , mais pour des services plus compliqués, nous aurions certainement besoin de ces convertisseurs.

Notez que cela ne modifie que le service grpc lui-même. La passerelle grpc utilise toujours les anciens éléments gogo du côté JSON, puis appelle le serveur gRPC interne, qui exécute ensuite la nouvelle implémentation.

Poussé quelques mises à jour de dépendance initiales et des solutions de rétrocompatibilité ici : https://github.com/TheThingsNetwork/lorawan-stack/compare/issue/2798-codec

De plus en plus de nos dépendances sont mises à niveau vers protobuf 1.4 et l'API V2, et plus nous la garderons ouverte longtemps, plus nous aurons de problèmes en essayant de mettre à niveau nos dépendances.

Nous devrions vraiment donner plus de priorité à cela et prendre une décision sur ce que nous allons faire à propos de tout cela.

Veuillez planifier un appel pour la semaine prochaine afin que nous puissions discuter hors ligne.

Je pense que nous devrions traverser ce processus douloureux et nous concentrer sur la résolution de cela dans une semaine ou deux. Et pour éviter que nous fassions autre chose car cela va provoquer beaucoup de conflits sinon. Avoir le plus de mains possible nécessite de savoir exactement ce que l'on va faire dans quels cas, de répartir les tâches autant que possible et de garder un œil sur le prix.

Prochaines étapes:

  1. @rvolosatovs met à jour l'outillage pour être aussi proche que possible de "vanille":

    • Supprimer des choses comme unconvert , gofumpt et tout ce que nous faisons en plus du protocole

    • Passer de protoc-gen-gogottn à protoc-gen-gofast (ou tout ce qui se rapproche le plus de la vanille)

    • Ajoutez explicitement des options (gogoproto.*) dans nos fichiers proto, afin qu'ils soient rendus comme maintenant

  2. Nous devons refactoriser notre code pour utiliser des Getters au lieu d'un accès direct aux champs. Peut-être que des outils comme gopls et rf peuvent vous aider.
  3. Nous commençons à supprimer les options (gogoproto.*) une par une et mettons à jour le code qui les utilise. Peut-être que des outils comme gopls et rf peuvent vous aider.

    • Suppression de gogoproto.populate et mise à jour des tests (https://github.com/TheThingsNetwork/lorawan-stack/issues/342)

    • Supprimer gogoproto.customname et modifier EUI -> Eui etc.

    • Suppression de gogoproto.embed . Nous devons nous assurer que les messages implémentent toujours des interfaces telles que ValidateContext(context.Context) error et ExtractRequestFields(m map[string]interface{}) .

    • Supprimer gogoproto.nullable et s'assurer que nous utilisons des Getters dans la mesure du possible, et faire des vérifications nulles dans le cas contraire.

    • ...

@rvolosatovs essayons de faire ces premiers pas pour la v3.11.3. Lorsque cela est fait, veuillez ajouter à nouveau les autres attributaires et rediscutons.

Cette page vous a été utile?
0 / 5 - 0 notes