En savoir plus sur gRPC et créer une application fortement typée avec communication gRPC à l’aide de Node et Typescript
Dans cet article, je vais vous emmener dans l’aventure de la configuration et de l’exécution de deux programmes de nœuds qui communiquent à l’aide de gRPC. À la fin, vous comprendrez les bases de gRPC, comment l’utiliser et en quoi il est différent de REST. Nous utiliserons Typescript pour assurer une frappe forte.
Commençons par répondre à la question la plus importante : qu’est-ce que gRPC ?
Commençons par démystifier la partie RPC.
RPC représente Remote Pprocédure Ctout et c’est la capacité d’un programme à exécuter une fonction d’un autre programme. Ces programmes peuvent se trouver sur différents ordinateurs ou même être écrits dans différentes langues.
gRPC est un framework RPC open-source maintenu par google et il peut être utilisé avec un tas de langues les plus populaires.
gRPC peut être utilisé comme alternative à REST avec trois différences principales :
- Dans REST, les services communiquent en utilisant le format JSON/XML. Dans gRPC, le tampon de protocole format est utilisé. Un tampon de protocole est un format binaire et peut avoir une taille nettement inférieure à JSON ou XML.
- Le serveur d’API REST partage les points de terminaison avec les clients. Dans gRPC, le serveur partage des fonctions avec les clients.
- Avec l’API REST, les erreurs sont signalées par des codes d’état HTTP. gRPC utilise des codes d’erreur.
Avec une compréhension de base, voyons le gRPC en action en créant une application.
Vous apprendrez à utiliser gRPC avec Typescript dans une application Node en créant un service d’authentification de base.
Le serveur aura une méthode appelée login
utilisé pour connecter l’utilisateur. Il en résultera le statut et le jeton d’autorisation en cas de succès.
Le client pourra appeler cette fonction et obtenir la réponse du serveur.
Avant de commencer à écrire le code, vous avez besoin d’une certaine préparation.
- Installer le compilateur de tampon de protocole (les fenêtres / Linux / Mac).
- Créez une nouvelle application Node avec
npm init -y
. - Ajoutez Typescript à l’application en utilisant
npm install typescript --save-dev
et puisnpx tsc --init
. - Installez les bibliothèques suivantes :
@grpc/grpc-js
etts-proto
.
Pour communiquer entre le serveur et le client, nous avons besoin d’un contrat. Lors de l’utilisation de REST, aucun contrat fortement typé n’est nécessaire – nous pouvons envoyer tout ce que nous voulons en JSON tant que le serveur et le client le comprennent.
Avec gRPC, nous n’avons pas cette liberté. Nous devons définir un contrat fortement typé à l’aide d’un tampon de protocole. Les contrats sont rédigés dans une langue spéciale et mis en .proto
des dossiers.
Créons un dossier pour les contrats appelé protos
et y mettre le contrat. Nous appellerons le contrat auth.proto
.
Si vous écrivez en VS Code, il est utile d’installer l’addon pour mettre en évidence la syntaxe des fichiers proto appelée vscode-proto3.
Code
À quoi ressemble cette langue mystérieuse ? Si vous connaissez des langues typées, vous les saisirez rapidement.
Au début, nous devons fournir la syntaxe et les noms des packages :
syntax = "proto3";
package authPackage;
Au moment de la rédaction du proto3
La syntaxe est la plus récente. Le nom du paquet peut être ce que vous voulez.
Nous allons maintenant définir les messages (types de contrat) qui peuvent être envoyés. Nous avons besoin d’un message séparé pour demander la connexion et le résultat au serveur. Nous définirons également un code indiquant si la tentative de connexion a réussi.
enum LoginCode {
SUCCESS = 0;
FAIL = 1;
};message LoginResult {
LoginCode loginCode = 1;
optional string token = 2;
}
message LoginRequest {
string username = 1;
string password = 2;
}
Comme vous pouvez le voir, chaque champ du message a un numéro unique. Ce sont les identifiants utilisés lors de l’encodage et ne doivent pas être modifiés une fois le format de message utilisé.
Nous pouvons maintenant définir le service qui utilisera ces messages. Définir un service revient à définir une interface dans Typescript. « Interface » sera dans un .proto
fichier et implémentation dans l’application Node. Notre service est très simple et ne contient que la fonction login :
service AuthService {
rpc login(LoginRequest) returns (LoginResult);
}
C’est le plein auth.proto
code:
syntax = "proto3";
package authPackage;service AuthService {
rpc login(LoginRequest) returns (LoginResult);
}
enum LoginCode {
SUCCESS = 0;
FAIL = 1;
};
message LoginResult {
LoginCode loginCode = 1;
optional string token = 2;
}
message LoginRequest {
string username = 1;
string password = 2;
}
Génération de types de texte dactylographié
Maintenant que nous avons la définition du contrat proto, nous devons la convertir en Typescript, afin que nous puissions utiliser la saisie dans notre application Node. Il y a un ts-proto
bibliothèque qui fait exactement cela. Nous utiliserons un porotoc
commande avec un ts-proto
plug-in spécifié :
>> protoc --plugin=protoc-gen-ts_proto=.\node_modules\.bin\protoc-gen-ts_proto.cmd --ts_proto_out=. ./protos/auth.proto --ts_proto_opt=outputServices=grpc-js,env=node,esModuleInterop=true
En conséquence, nous obtiendrons un auth.ts
fichier avec le code de nos messages et service écrit en Typescript.
Nous sommes maintenant prêts à créer le serveur !
Créons un server.ts
dossier. Voici à quoi ressemble la définition du serveur gRPC :
import { Server, ServerCredentials} from "@grpc/grpc-js";
import { AuthServiceService } from "./protos/auth";const server = new Server();
server.addService(AuthServiceService, { login: login }); // login will be defined later
server.bindAsync("localhost:8080", ServerCredentials.createInsecure(), () => {
server.start();
});
Sur le créé Server
exemple, nous définissons les services que nous voulons ajouter. AuthServiceService
a été généré automatiquement avec ts-proto
et c’est l’endroit où nous l’ajoutons. Comme deuxième argument, nous passons les définitions des fonctions utilisées dans le service. Nous définirons le login
fonctionner en un instant.
La dernière chose dont nous avons besoin pour faire fonctionner un serveur est de le lier à une adresse. À titre d’exemple, utilisons un port 8080
sur l’hôte local. Dans bindAsync
fonction, des informations d’identification sont également définies. Ils sont utilisés pour sécuriser la connexion HTTPS. À des fins de test, des informations d’identification «non sécurisées» peuvent être utilisées, mais pour une application de production, vous feriez mieux d’utiliser le vrai certificat.
Regardons l’implémentation de la connexion :
import { LoginCode, LoginRequest, LoginResult } from "./protos/auth";const users = [
{ id: 0, username: "admin", password: "qwerty" }
];
const login = (
call: ServerUnaryCall<LoginRequest, LoginResult>,
callback: sendUnaryData<LoginResult>
) => {
const user = users.find((user) =>
user.username === call.request.username &&
user.password === call.request.password
);
if (user) {
const result: LoginResult = {
loginCode: LoginCode.SUCCESS,
token: "RandomSecretToken",
};
callback(null, result);
} else {
const result: LoginResult = {
loginCode: LoginCode.FAIL,
};
callback(null, result);
}
}
Nous n’utilisons aucune base de données, juste un simple tableau avec tous les utilisateurs de notre système. C’est tout pour rendre cet exemple aussi simple que possible.
Le login
fonction prend deux arguments : call
et callback
. Call
est l’endroit où la demande est définie et nous pouvons obtenir les données envoyées par le client. Callback
est appelé pour renvoyer les données au client. La première callback
l’argument est une erreur de service (pas d’erreur dans cet exemple, donc null
). Le second est un résultat réel.
Vous pouvez voir à quel point tout est fortement typé.
Maintenant que nous avons un serveur, implémentons un client. Nous utiliserons le AuthServiceClient
généré automatiquement avec ts-proto
. Le client a besoin de la même adresse et des mêmes informations d’identification que le serveur. Après avoir créé un client, nous pouvons appeler login
dessus. En cas de succès, il appellera le login
fonction côté serveur !
import { ServiceError, credentials } from "@grpc/grpc-js";
import { AuthServiceClient, LoginRequest, LoginResult } from "./protos/auth";const loginRequest: LoginRequest = {
username: "admin",
password: "qwerty"
};
const client = new AuthServiceClient(
"localhost:8080", credentials.createInsecure()
);
client.login(loginRequest, (err: ServiceError | null, response: LoginResult) => {
console.log(JSON.stringify(response));
})
Courons tous les deux server.ts
et client.ts
en utilisant npx ts-node file
commande.
L’exécution de l’exemple avec des informations d’identification valides entraîne :
Mais lorsque nous les changeons en invalides, nous obtenons :
Nous avons fait communiquer le client et le serveur avec gRPC !
Après avoir lu cet article, vous devriez vous familiariser avec gRPC et voir à quel point il est puissant. En l’utilisant, nous pouvons facilement communiquer entre différents services avec le schéma fortement typé. Le format de données peut être compressé plus efficacement que JSON, la communication est donc plus rapide.
Malheureusement, au moment de la rédaction de cet article, il n’y a pas d’implémentation de gRPC dans les navigateurs actuels, nous ne pouvons donc l’utiliser que pour la communication des services internes.
Il y a bien plus à explorer à propos de gRPC. Essayez-le et voyez s’il correspond à vos cas d’utilisation. Consultez mon GitHub pour le code complet :