sgcProtoBuf: od plików .proto do modułów Delphi | Blog eSeGeCe

sgcProtoBuf: od plików .proto do modułów Delphi

· Komponenty
Generator kodu sgcProtoBuf

Kiedy partner przekazuje Ci usługę gRPC, przekazuje Ci folder z plikami .proto. Te pliki są kontraktem, a każdy komunikat w nich zawarty musi stać się czymś, co Twój kod Delphi potrafi odczytać i zapisać. Robienie tego ręcznie jest żmudne i łatwo o pomyłkę, a całość trzeba powtarzać za każdym razem, gdy druga strona wypuszcza nową wersję kontraktu.

sgcProtoBuf to generator kodu, który robi to za Ciebie. Wskaż mu plik .proto, a wygeneruje moduły Delphi z klasą dla każdego komunikatu, gotowe do serializacji i deserializacji. Czyta proto2 i proto3, a składnię pobiera z samego pliku. Jest dostarczany zarówno jako kreator, jak i jako narzędzie wiersza poleceń.

Podaj jeden plik, otrzymaj całe drzewo

Prawdziwe drzewa proto nigdy nie składają się z jednego pliku. Pliki importują się nawzajem, a do tego importują definicje Google. Typowy plik usługi zaczyna się tak:

syntax = "proto3";

package acme.telemetry.v1;

import "acme/common/v1/device.proto";
import "google/protobuf/timestamp.proto";

message Reading {
  string device_id = 1;
  double value = 2;
  google.protobuf.Timestamp taken_at = 3;
}

Przy włączonej opcji Rozwiązuj importy wybierasz ten jeden plik, a sgcProtoBuf rekurencyjnie podąża za instrukcjami import, generując każdą znalezioną zależność i dodając wygenerowane moduły do klauzuli uses. Folder główny Twojego drzewa proto jest wykrywany automatycznie na podstawie wybranego pliku, więc zwykle nie ma czego konfigurować.

Jeśli definicji nie da się znaleźć na dysku, zostaje to zgłoszone na liście komunikatów, zamiast po cichu wygenerować moduł, który się nie kompiluje. Widzisz dokładnie, którego pliku brakuje, zamiast szukać przyczyny błędu kompilatora.

Nazwy modułów pochodzą z package

Wygenerowany moduł bierze nazwę z instrukcji package, więc package acme.telemetry.v1; daje unit acme.telemetry.v1; zapisany jako acme.telemetry.v1.pas. Nazwa pliku zawsze odpowiada nazwie modułu, co eliminuje całą kategorię możliwych do uniknięcia błędów kompilacji.

Oznacza to również, że kilka plików .proto deklarujących ten sam package trafia do jednego modułu, dokładnie tak, jak zamierzyli autorzy kontraktu. Folder z plikami device.proto, device_service.proto i units.proto, z których wszystkie deklarują acme.common.v1, staje się jednym plikiem acme.common.v1.pas.

Typy Google są już w zestawie

Niemal każdy rzeczywisty plik proto importuje coś z google.protobuf. Tych typów nie musisz generować. sgcWebSockets dostarcza je już w module sgcProtoBuf_WellKnownTypes, a generator mapuje je automatycznie: Timestamp, Duration, Any, Empty, FieldMask, Struct oraz typy opakowujące, takie jak StringValue i Int32Value.

Dzięki temu pole google.protobuf.Timestamp z powyższego przykładu staje się polem typu TsgcProtoBufTimestamp, z gotową implementacją ToDateTime i FromDateTime. Plików .proto od Google nie trzeba w ogóle przekształcać.

Wyliczenia jako prawdziwe typy Delphi

Domyślnie wyliczenie jest generowane jako blok stałych. Włącz opcję Generuj wyliczenia jako typy wyliczeniowe Delphi, a zamiast tego otrzymasz właściwy typ, który jest krótszy w zapisie i którego nie da się pomylić z niepowiązaną liczbą całkowitą:

CountryCodeDto = (
  COUNTRY_CODE_DTO_NONE = 0,
  COUNTRY_CODE_DTO_NL = 1,
  COUNTRY_CODE_DTO_DE = 2,
  COUNTRY_CODE_DTO_CH = 3
);

Jest jedno ograniczenie, o którym warto uczciwie powiedzieć. Wyliczenie Protocol Buffers potrafi rzeczy, których typ wyliczeniowy Delphi nie potrafi: może przechowywać wartości ujemne, może przypisać dwie nazwy do tej samej liczby, a dwa wyliczenia w tym samym package mogą deklarować tę samą nazwę wartości. Kiedy generator natrafi na jeden z takich przypadków, dla tego jednego wyliczenia wraca do postaci stałych i zapisuje komentarz z wyjaśnieniem, dlaczego. Pozostałe wyliczenia w pliku pozostają nienaruszone, więc otrzymujesz prawdziwe typy wszędzie tam, gdzie są możliwe, i działający moduł w pozostałych miejscach.

Wygenerowany kod

Każda klasa komunikatu dziedziczy po wygenerowanej klasie bazowej i udostępnia LoadFromBytes, LoadFromStream oraz ToBytes, wraz z właściwością dla każdego pola. Poniższy przykład korzysta z opcji Zachowaj oryginalne nazwy elementów proto, która pomija prefiks Tsgc i zachowuje nazwy w postaci, w jakiej występują w kontrakcie:

uses
  acme.telemetry.v1;

var
  oReading: Reading;
begin
  oReading := Reading.Create;
  Try
    oReading.LoadFromBytes(vBytes);

    ShowMessage(oReading.DeviceId);
    ShowMessage(FloatToStr(oReading.Value));
    ShowMessage(DateTimeToStr(oReading.TakenAt.ToDateTime));

    vBytes := oReading.ToBytes;
  Finally
    oReading.Free;
  End;
end;

W wierszu poleceń

Kreator jest wygodny na pierwszy rzut oka, ale drzewo proto się zmienia, a jego ponowne generowanie należy do procesu budowania. Ten sam generator uruchomisz z wiersza poleceń:

sgcProtoBuf.exe --input=protos\acme\telemetry\v1\telemetry.proto --resolve-imports --output-dir=units --enum-as-type

Przełączniki odpowiadają opcjom kreatora: --resolve-imports, --import-root (można powtarzać, dla drzew rozproszonych w więcej niż jednym katalogu głównym), --output-dir, --enum-as-type, --original-names, --short-enum-consts, a także --no-classes, --no-services i --no-docs. Uruchom go z --help, aby zobaczyć pełną listę.

Skąd go pobrać

sgcProtoBuf jest dostarczany razem z sgcWebSockets, obok TsgcGRPCClient i modułów uruchomieniowych sgcProtoBuf. Pobierz najnowszą kompilację ze strony pobierania sgcWebSockets, a pełny opis opcji znajdziesz w temacie sgcProtoBuf w pomocy.

Pytania, uwagi albo drzewo proto, które nie generuje się poprawnie? Skontaktuj się z nami, odpowiedź otrzymasz od osób, które napisały ten kod.