Protobuf mit Rabbitmq in NodeJS integrieren - Teil I

Oft geht es darum, ein System aufzubauen, das die Kommunikation zwischen Diensten mit asynchronen Methoden erleichtert. Es gibt mehrere Möglichkeiten, dies zu erreichen, aber für unsere Zwecke werden wir ein Warteschlangensystem verwenden.

Außerdem wollen wir ein Protokoll in unsere Liste der Dienstverträge aufnehmen, das es jedem Dienst ermöglicht, davon zu profitieren und einen darauf basierenden Client zu erzeugen.

Diese Reise ist in drei Teile gegliedert. Im ersten Teil konzentrieren wir uns auf die Erstellung von Protobuf-Verträgen und die Generierung von TypeScript-Definitionen aus ihnen.

Beginnen wir mit der Definition eines Beispiels für unseren Anwendungsfall. Stellen Sie sich vor, wir haben ein System zur Erfassung von Metriken, das verschiedene Arten von Informationen sammelt, z. B. GPS-Daten und Geräteinformationen.

Wir werden auch Proto-Definitionen verwenden, um die Struktur der Nachrichten zu definieren.

Projekt einrichten

Zuerst müssen wir das Projektarchiv einrichten

mkdir nodejs-proto-rabbit
cd nodejs-proto-rabbit
npm init -y 
mkdir src 
mkdir proto 

Installation der erforderlichen Abhängigkeiten

npm install -D typescript @types/node

Initialisieren wir die Datei ts-config.json, indem wir sie ausführen:

npx tsc --init

Dadurch wird eine ts-config.json-Datei mit allen Standardeinstellungen in das Stammverzeichnis des Projekts eingefügt.

Implementation

Für GPS-Daten wird die folgende Struktur definiert:

// proto/gps.message.proto
syntax = "proto3";

import "google/protobuf/timestamp.proto";

message GPSMessage {
  google.protobuf.Timestamp time = 1;

  string latitude = 2;

  string longitude = 3;
}

Für die Geräteinformationsdaten wird die folgende Struktur definiert:

// proto/device-information.message.proto
syntax = "proto3";

import "google/protobuf/timestamp.proto";

message DeviceInformationMessage {
  google.protobuf.Timestamp time = 1;

  string mac = 2;

  string name = 3;
}

Nach der Definition dieser Proto-Definitionen besteht der nächste Schritt darin, eine Bibliothek zu finden, die TypeScript-Definitionen erzeugen kann. Meine bevorzugte Wahl ist @bufbuild/protoc-gen-es, ein Codegenerator-Plugin für Protocol Buffers tailored for ECMAScript.

Wir folgen den Installationsanweisungen und führen die folgenden Befehle aus

npm install --save-dev @bufbuild/protoc-gen-es
npm install @bufbuild/protobuf

Für die Generierung werden wir den einfachen Ansatz mit buf verwenden. Um damit fortzufahren, müssen wir eine Datei wie buf.gen.yaml definieren, in der wir die gesamte Konfiguration für die Generierung speichern

// buf.gen.yaml
version: v2
plugins:
  # This will invoke protoc-gen-es and write output to gen
  - local: protoc-gen-es
    out: gen
    opt:
      # Add more plugin options here
      - target=ts

Um Code für alle Protobuf-Dateien in unserem Projekt zu generieren, führen wir den folgenden Befehl aus:

npm install --save-dev @bufbuild/buf
npx buf generate

In Ihren Dateien sollten sich zwei neue Dateien befinden, gps.message_pb.ts & device-information.message_pb.ts, mit einem Inhalt wie im folgenden Beispiel:

// gen/proto/gps.message_pb.ts
// @generated by protoc-gen-es v2.0.0 
// with parameter "target=ts"
// @generated from file 
// proto/gps.message.proto (syntax proto3)
/* eslint-disable */

import type { 
  GenFile, 
  GenMessage
} from "@bufbuild/protobuf/codegenv1";
import { 
  fileDesc, 
  messageDesc 
} from "@bufbuild/protobuf/codegenv1";
import type { 
  Timestamp 
} from "@bufbuild/protobuf/wkt";
import { 
  file_google_protobuf_timestamp 
} from "@bufbuild/protobuf/wkt";
import type { Message } from "@bufbuild/protobuf";

/**
 * Describes the file proto/gps.message.proto.
 */
export const file_proto_gps_message: GenFile 
 = fileDesc("Chdwcm90by9ncHM ....(continues)");

/**
 * @generated from message GPSMessage
 */
export type GPSMessage = Message<"GPSMessage"> & {
  /**
   * @generated from field: 
   * google.protobuf.Timestamp time = 1;
   */
  time?: Timestamp;

  /**
   * @generated from field: string latitude = 2;
   */
  latitude: string;

  /**
   * @generated from field: string longitude = 3;
   */
  longitude: string;
};

/**
 * Describes the message GPSMessage.
 * Use `create(GPSMessageSchema)` to create a new message.
 */
export const GPSMessageSchema: GenMessage<GPSMessage> 
 = messageDesc(file_proto_gps_message, 0);
// gen/proto/device-information.message_pb.ts
// @generated by protoc-gen-es v2.0.0 
// with parameter "target=ts"
// @generated from file 
// proto/device-information.message.proto
/* eslint-disable */

import type { 
  GenFile, 
  GenMessage
} from "@bufbuild/protobuf/codegenv1";
import { 
  fileDesc, 
  messageDesc 
} from "@bufbuild/protobuf/codegenv1";
import type { 
  Timestamp 
} from "@bufbuild/protobuf/wkt";
import { 
  file_google_protobuf_timestamp 
} from "@bufbuild/protobuf/wkt";
import type { Message } from "@bufbuild/protobuf";

/**
 * Describes the file 
 * proto/device-information.message.proto.
 */
export const file_proto_device_information_message ...;

/**
 * @generated from message DeviceInformationMessage
 */
export type DeviceInformationMessage 
 = Message<"DeviceInformationMessage"> & {
  /**
   * @generated from field: 
   * google.protobuf.Timestamp time = 1;
   */
  time?: Timestamp;

  /**
   * @generated from field: string mac = 2;
   */
  mac: string;

  /**
   * @generated from field: string name = 3;
   */
  name: string;
};

/**
 * Describes the message DeviceInformationMessage.
 * Use `create(DeviceInformationMessageSchema)` 
 * to create a new message.
 */
export const DeviceInformationMessageSchema: ...;

Um eine Message zu erzeugen, müssen wir die folgenden Codezeilen schreiben:

import { create } from '@bufbuild/protobuf';
import { 
  DeviceInformationMessageSchema 
} from 'gen/proto/device-information.message_pb';

const message = create(DeviceInformationMessageSchema, {
  mac: '00:11:22:33:44:55',
  name: 'device',
});

console.log(message);
//{
//  '$typeName': 'DeviceInformationMessage',
//  mac: '00:11:22:33:44:55',
//  name: 'device'
//}

Im zweiten Teil dieser Reise werden wir uns auf den Aufbau einer generischen Lösung für RabbitMQ konzentrieren, die für jede Nachricht verwendet werden kann. Im dritten Teil werden wir uns mit der Konfiguration all dieser Komponenten beschäftigen, damit sie in einem einfachen Projekt nahtlos zusammenarbeiten.

Schreiten Sie weiter voran und genießen Sie jeden Schritt Ihrer Programmierreise.