sgcProtoBuf: van .proto-bestanden naar Delphi-units | eSeGeCe Blog

sgcProtoBuf: van .proto-bestanden naar Delphi-units

· Componenten
sgcProtoBuf-codegenerator

Wanneer een partner u een gRPC-service aanreikt, krijgt u een map met .proto-bestanden. Die bestanden zijn het contract, en elk bericht erin moet iets worden dat uw Delphi-code kan lezen en schrijven. Dat met de hand doen is bewerkelijk en foutgevoelig, en het moet opnieuw gebeuren telkens wanneer de andere partij een nieuwe versie van het contract uitbrengt.

sgcProtoBuf is de codegenerator die het voor u doet. Wijs hem een .proto-bestand aan en hij produceert Delphi-units met een klasse voor elk bericht, klaar om te serialiseren en te deserialiseren. Hij leest proto2 en proto3, en neemt de syntax over uit het bestand zelf. Hij wordt geleverd als wizard en als commandoregelprogramma.

Geef hem één bestand, krijg de hele boom

Echte proto-bomen bestaan nooit uit één bestand. Ze importeren elkaar, en daarbovenop importeren ze de Google-definities. Een typisch servicebestand begint zo:

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;
}

Met Imports oplossen ingeschakeld selecteert u dat ene bestand en volgt sgcProtoBuf de import-statements recursief. Elke afhankelijkheid die hij vindt wordt gegenereerd en de gegenereerde units worden aan de uses-clausule toegevoegd. De hoofdmap van uw proto-boom wordt automatisch afgeleid uit het bestand dat u koos, dus meestal valt er niets in te stellen.

Als een definitie niet op schijf te vinden is, wordt dat gemeld in de berichtenlijst in plaats van stilletjes een unit te produceren die niet compileert. U ziet precies welk bestand ontbreekt in plaats van achter een compilerfout aan te jagen.

Unitnamen komen uit de package

De gegenereerde unit is vernoemd naar het package-statement, dus package acme.telemetry.v1; levert unit acme.telemetry.v1; op, opgeslagen als acme.telemetry.v1.pas. De bestandsnaam komt altijd overeen met de unitnaam, wat een hele categorie vermijdbare compilerfouten wegneemt.

Het betekent ook dat meerdere .proto-bestanden die dezelfde package declareren in één unit belanden, precies zoals de auteurs van het contract het bedoeld hebben. Een map met device.proto, device_service.proto en units.proto die alle drie acme.common.v1 declareren wordt één acme.common.v1.pas.

De Google-types zitten er al in

Bijna elk echt proto-bestand importeert iets uit google.protobuf. Die hoeft u niet te genereren. sgcWebSockets levert ze al mee in de unit sgcProtoBuf_WellKnownTypes, en de generator koppelt ze automatisch: Timestamp, Duration, Any, Empty, FieldMask, Struct en de wrappertypes zoals StringValue en Int32Value.

Het veld google.protobuf.Timestamp hierboven wordt dus een TsgcProtoBufTimestamp, met ToDateTime en FromDateTime al geïmplementeerd. De .proto-bestanden van Google hoeven dus nooit vertaald te worden.

Enums als echte Delphi-types

Standaard wordt een enum gegenereerd als een blok constanten. Zet Enums genereren als Delphi-opsommingstypes aan en u krijgt in plaats daarvan een echt type, dat korter leest en niet te verwarren is met een ongerelateerd geheel getal:

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

Er is één eerlijke beperking die het waard is om te kennen. Een Protocol Buffers-enum kan dingen die een Delphi-opsommingstype niet kan: hij kan negatieve waarden bevatten, hij kan twee namen aan hetzelfde getal koppelen, en twee enums in dezelfde package kunnen dezelfde waardenaam declareren. Komt de generator zoiets tegen, dan valt hij voor die ene enum terug op de constantenvorm en schrijft hij een commentaar met de reden erbij. Elke andere enum in het bestand blijft ongemoeid, dus u krijgt echte types overal waar dat mogelijk is en een werkende unit op alle andere plekken.

De gegenereerde code

Elke berichtklasse erft van een gegenereerde basisklasse en biedt LoadFromBytes, LoadFromStream en ToBytes, met een property voor elk veld. Het voorbeeld hieronder gebruikt de optie Oorspronkelijke proto-elementnamen behouden, die het voorvoegsel Tsgc weglaat en de namen houdt zoals ze in het contract staan:

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;

Op de commandoregel

De wizard is handig voor een eerste blik, maar een proto-boom verandert, en het opnieuw genereren hoort thuis in uw build. Dezelfde generator draait vanaf de commandoregel:

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

De schakelaars weerspiegelen de wizardopties: --resolve-imports, --import-root (herhaalbaar, voor bomen die over meer dan één hoofdmap verspreid zijn), --output-dir, --enum-as-type, --original-names, --short-enum-consts, plus --no-classes, --no-services en --no-docs. Draai hem met --help voor de volledige lijst.

Aan de slag

sgcProtoBuf wordt meegeleverd met sgcWebSockets, naast TsgcGRPCClient en de runtime-units van sgcProtoBuf. Download de nieuwste build van de sgcWebSockets-downloadpagina, en raadpleeg het onderwerp sgcProtoBuf in de help voor de volledige optiereferentie.

Vragen, feedback of een proto-boom die niet netjes genereert? Neem contact op, u krijgt antwoord van de mensen die de code hebben geschreven.