Breaking changes v Protobuf: co přežije na drátě
5 min čtení
REST API se mění, když se mění tvar JSON, a většina toho tvaru je viditelná v odpovědi, kterou lze
přečíst v prohlížeči. gRPC API se mění, když se mění soubor .proto, a binární drátový formát
Protocol Buffers má vlastní pravidla o tom, co klient snese, která nemají nic společného s tím, co
říkají jména polí. Dvě změny, které v diffu vypadají stejně drobně, přečíslování pole oproti
přidání nového, spadají na opačné strany čáry, kterou v obecné rovině kreslí breaking
changes: jedna je neviditelná pro každého existujícího klienta, druhá
je rozbije všechny naráz. Rozeznat breaking changes v Protobuf od těch bezpečných znamená číst
vlastní pravidla drátového formátu, ne hádat podle toho, jak změna vypadá v diffu .proto.
Proč záleží na čísle pole víc než na jeho jméně v Protobuf?
Protože drátový formát kóduje pole podle čísla, ne podle jména. Generovaný kód v každém jazyce čte
a zapisuje tato čísla; jméno pole email ve vašem souboru .proto je pohodlí pro lidi, které se
nikdy nedotkne binárních bytů posílaných po síti. Přejmenování pole, email na
email_address, je bezpečné v binárním drátovém formátu, pokud číslo zůstane stejné, což překvapí inženýrky
zvyklé na REST, kde přejmenovaný JSON klíč je přesně ten typ změny, který klienta rozbije.
Výjimkou je tentýž případ jako u REST:
ProtoJSON a textový formát serializují jméno, takže
přejmenování rozbije transkódování do JSON (například grpc-gateway), soubory v textovém formátu
a field masks. Přečíslování téhož pole, se zachovaným jménem ale změněnou 1 na 7, je pravý opak: neviditelné
v code review, který ukazuje jen jména, a rozbije každou zprávu, kterou klient od té chvíle pošle
nebo přijme.
| Změna | Bezpečná na drátě | Proč |
|---|---|---|
| Přejmenovat pole, zachovat číslo | Binárně ano, JSON a text ne | Binární kódování používá číslo; ProtoJSON a textový formát používají jméno |
| Změnit číslo pole | Ne | Každá existující zpráva se teď čte jako jiné pole |
| Přidat nové pole s novým číslem | Ano | Staří klienti ignorují pole, která neznají |
| Odstranit pole, znovu použít jeho staré číslo pro něco jiného | Ne | Stará data se dekódují do nesprávného nového pole |
Nekompatibilně změnit typ pole (např. int32 na string) | Ne | Drátové kódování se liší podle typu |
Co dělá odstranění pole jiným než totéž u odpovědi REST JSON?
Číslo se stane radioaktivním. Vlastní doporučení Protobuf doporučuje označit číslo smazaného pole
jako reserved, místo aby se povolilo jeho znovupoužití, protože právě tam vzniká skutečná škoda:
klient stále běžící na měsíc starém generovaném kódu pošle zprávu, používající staré číslo pole
pro starou hodnotu, a server, teď očekávající, že to číslo znamená něco jiného, potichu data
nesprávně interpretuje, místo aby je rovnou odmítl. REST nemá ekvivalentní past, protože smazaný
JSON klíč prostě přestane přicházet; neexistuje způsob, jak by byl požadavek starého klienta
potichu přeinterpretován jako něco jiného. Soubor .proto s reserved 4, 9, 12; na začátku zprávy
je trvalá jizva, a v tom je celý smysl: brání tomu, aby se číslo dostalo k novému poli od někoho,
kdo jeho historii neznal.
message Invoice {
reserved 4; // bývalo `legacy_customer_id`, odstraněno 2026-06-01
reserved "legacy_customer_id"; // i jméno, kvůli JSON/textu
string customer_id = 5;
string status = 6;
}
Vyžaduje přidání pole vůbec záznam v changelogu?
Obvykle ne záznam o breaking change, ale často běžný, protože „bezpečné na drátě” a „viditelné pro čtenářku, které na tom záleží” jsou dvě různá tvrzení. Přidání pole do zprávy odpovědi je strukturálně zdarma, staří klienti zprávu dekódují a nové pole automaticky ignorují. Ale ten, kdo staví novou integraci proti téhle službě, nemá způsob, jak zjistit, že pole existuje, pokud mu to někdo neřekne, protože nic v úspěšném buildu nebo prošlém testu nedělá nové volitelné pole viditelným. Changelog API obecně rozebírá, co aditivní záznam dluží čtenářkám; důvod specifický pro gRPC ho přesto napsat je, že neexistuje ekvivalent prohlížení odpovědi REST v debuggeru, kde by si všimla nového klíče.
Čím se to liší od toho, čemu čelí volající GraphQL?
Pravidla pro přidávání jsou stejná, ale expozice se liší. Deprecation schématu GraphQL rozebírá model, kde klient dostane jen pole, o která výslovně požádá, což dělá aditivní změny v podstatě bezrizikové a odstranění jedinou skutečnou hrozbou. Klienti gRPC naopak dostanou vše, co server pošle, a dekódují vše proti vlastní zkompilované kopii schématu; expozice klienta je omezena ne tím, o co požádal, ale jen tím, co jeho generovaný kód umí přečíst. Ten rozdíl má význam pro psaní changelogu: záznam GraphQL může rozumně předpokládat, že klienti jsou chráněni před poli, která nežádali, záznam gRPC to předpokládat nemůže vůbec.
Funguje verzování gRPC služby stejně jako /v1/, /v2/ v REST?
Mechanismus se liší, i když je záměr stejný. Co jsou v1 a v2 v REST
API rozebírá verzování jako paralelní URL cesty
obsluhující různé kontrakty; gRPC služby se obvykle verzují přes jméno balíčku v samotném souboru
.proto, payments.v1.InvoiceService se stane payments.v2.InvoiceService, což mění plně
kvalifikované jméno služby, které klient volá, místo segmentu URL, o který žádá. Oba přístupy řeší
stejný problém, umožnit starému kontraktu fungovat dál, dokud existuje nový, ale tým s REST
zázemím často hledá číslo verze na špatném místě a přehlédne, že tuhle práci dělá deklarace balíčku.
Co by měl záznam changelogu gRPC skutečně pojmenovat?
Zprávu, číslo pole, a zda je změna aditivní nebo odstranění vyžadující migraci, v tomto pořadí
důležitosti pro čtenářku, která se rozhoduje, zda jednat. „Přidáno shipping_address (pole 8) do
Order” řekne integrátorce vše potřebné, aby aktualizovala generovaný kód a začala ho používat.
„Rezervováno pole 4 v Invoice, legacy_customer_id zmizelo” jí řekne, ať zkontroluje, jestli
něco v její kódové bázi to pole ještě nečte, což poznámka ve stylu REST „odstraněno pole z odpovědi”
nesděluje se stejnou naléhavostí, protože REST odstranění prostě vrátí méně dat, zatímco
znovupoužití pole v Protobuf je aktivně poškodí.
FAQ
Může být typ pole někdy změněn bez rozbití drátového formátu?
Jen v rámci konkrétních kompatibilních skupin, které Protobuf dokumentuje, jako rozšíření int32
na int64 v některých případech. Zacházejte s jakoukoli změnou typu jako s breaking, pokud jste ji
neověřili proti vlastní tabulce kompatibility Protobuf; předpoklad kompatibility podle analogie s
typovým systémem jazyka je způsob, jak se to pokazí.
Funguje deprecation pole v Protobuf jako direktiva @deprecated v GraphQL?
Podobně: Protobuf podporuje volbu pole [deprecated = true], kterou mohou nástroje zobrazit. Ani
jedno není vynucené: server GraphQL na dotaz na zastaralé pole dál odpovídá, a klient protobuf ho
dál kóduje. Obojí je jen orientační a vyžaduje stejnou podporu changelogu.
Je přečíslování bezpečné, pokud kontrolujete každého klienta? V zcela uzavřeném systému v zásadě ano, ale to odstraňuje celou vlastnost bezpečnosti, kvůli které čísla polí existují, a „kontrolujeme každého klienta” je tvrzení, které přestává platit ve chvíli, kdy se sestavení cachuje, deploy odloží, nebo přibude klient, na kterého nikdo nepamatoval. Rezervujte číslo místo jeho znovupoužití, i uvnitř firmy.
Potřebují gRPC služby changelog stránku jako veřejné REST API?
Jen pokud je konzumují externí týmy, které nečtou diffy .proto přímo, stejný test „kdo je na
druhé straně”, jaký obecně aplikuje changelog interních API.
gRPC služba konzumovaná jen jinými službami stejného týmu se často obejde bez formálního changelogu
ve prospěch historie commitů, protože každý, kdo ji čte, má schéma už otevřené.
Technická tvrzení v tomto článku nikdo nezávisle neověřil. Pokud tu něco nesedí, dej nám vědět a opravíme to.