💻 Développement

dev-grpc-service-designer

Conception de services gRPC, définition de contrats Protobuf, patterns de streaming et intégration dans des architectures microservices.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-grpc-service-designer --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-grpc-service-designer -Launch"

🚀 Déjà installé ?

claude "/dev-grpc-service-designer"

Ou tapez /dev-grpc-service-designer dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

gRPCprotobuffichier protostreaming gRPCservice gRPCcontrat protobuf

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-grpc-service-designer ~/.claude/skills/

Payload du plugin : skills/dev-grpc-service-designer · source éditable : dev-skills/grpc-service-designer

📖 Manuel

Concepteur de Services gRPC

Workflow en étapes

  1. Qualifier la communication — choisir le bon pattern (voir tableau ci-dessous) avant d'ouvrir un éditeur.
  2. Concevoir le contrat .proto — package versionné, messages dédiés par RPC, enums avec valeur 0 UNSPECIFIED.
  3. Générer le codeprotoc ou plugin SDK ; vérifier que le code généré ne doit jamais être édité manuellement.
  4. Implémenter serveur puis client — gestion d'erreurs gRPC status codes, deadlines, idempotence.
  5. Sécuriser et observer — TLS mutuel, intercepteurs logging/métriques, health checks.
  6. Valider la compatibilité — tester la rétrocompatibilité binaire avant tout déploiement.

Critère de choix du pattern de communication

PatternQuand l'utiliserExemple concret
UnaireRequête/réponse atomique, latence faibleCRUD, auth, paiement ponctuel
Server streamingServeur pousse un volume inconnuNotifications, export CSV/JSON
Client streamingClient envoie un flux puis attend le résultatUpload fichiers, ingestion de logs
BidirectionnelDialogue continu, ordre importantChat temps réel, monitoring interactif

Règle heuristique : si la réponse tient dans une seule trame TCP et que le serveur répond immédiatement → unaire. Dès qu'une des parties envoie plusieurs messages → streaming.

Conception du contrat Protobuf

Structure de référence

syntax = "proto3";

package myapp.payments.v1;

option csharp_namespace = "MyApp.Payments.V1";
option go_package = "github.com/myapp/payments/v1;paymentsv1";

import "google/protobuf/timestamp.proto";

service PaymentService {
  // Unaire
  rpc CreatePayment(CreatePaymentRequest) returns (CreatePaymentResponse);
  // Server streaming
  rpc WatchStatus(WatchStatusRequest) returns (stream PaymentStatusEvent);
  // Client streaming
  rpc BatchImport(stream ImportTransactionRequest) returns (BatchImportResponse);
  // Bidirectionnel
  rpc SyncLedger(stream LedgerEntry) returns (stream LedgerAck);
}

message CreatePaymentRequest {
  string idempotency_key = 1;  // UUID v4, obligatoire
  int64  amount_cents    = 2;
  string currency        = 3;  // ISO 4217
  string recipient_id    = 4;
  map<string, string> metadata = 5;
}

message CreatePaymentResponse {
  string payment_id = 1;
  PaymentStatus status = 2;
  google.protobuf.Timestamp created_at = 3;
}

enum PaymentStatus {
  PAYMENT_STATUS_UNSPECIFIED = 0;  // obligatoire
  PAYMENT_STATUS_PENDING     = 1;
  PAYMENT_STATUS_PROCESSING  = 2;
  PAYMENT_STATUS_COMPLETED   = 3;
  PAYMENT_STATUS_FAILED      = 4;
}

// Champs supprimés → reserved, jamais effacés
// reserved 6, 7;
// reserved "old_field_name";

Conventions de nommage

ÉlémentConventionExemple
Packagecompany.service.v1myapp.payments.v1
ServicePascalCase + ServicePaymentService
RPCPascalCase, verbe d'actionCreatePayment
Request/ResponsePropre à chaque RPCCreatePaymentRequest
Champsnake_casepayment_id
EnumSCREAMING_SNAKE_CASE avec préfixe enumPAYMENT_STATUS_PENDING
Enum valeur 0Toujours UNSPECIFIEDPAYMENT_STATUS_UNSPECIFIED

Génération du code

# Installer protoc + plugins
brew install protobuf          # macOS
apt install -y protobuf-compiler   # Debian/Ubuntu

# Go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Générer
protoc --proto_path=proto \
       --go_out=gen --go_opt=paths=source_relative \
       --go-grpc_out=gen --go-grpc_opt=paths=source_relative \
       proto/payments/v1/payment.proto

# C# / .NET : via NuGet Grpc.Tools (MSBuild auto-génère à la build)
# Java : via grpc-java plugin Maven/Gradle

Implémentation C# / ASP.NET Core

Serveur

public sealed class PaymentServiceImpl : PaymentService.PaymentServiceBase
{
    private readonly IPaymentRepository _repo;
    private readonly ILogger<PaymentServiceImpl> _log;

    public override async Task<CreatePaymentResponse> CreatePayment(
        CreatePaymentRequest req, ServerCallContext ctx)
    {
        // Idempotence
        var existing = await _repo.FindByIdempotencyKeyAsync(req.IdempotencyKey, ctx.CancellationToken);
        if (existing is not null) return Map(existing);

        if (req.AmountCents <= 0)
            throw new RpcException(new Status(StatusCode.InvalidArgument, "amount_cents must be > 0"));

        var payment = await _repo.CreateAsync(req.AmountCents, req.Currency, req.RecipientId, ctx.CancellationToken);
        _log.LogInformation("Payment {Id} created", payment.Id);
        return Map(payment);
    }

    public override async Task WatchStatus(
        WatchStatusRequest req,
        IServerStreamWriter<PaymentStatusEvent> stream,
        ServerCallContext ctx)
    {
        while (!ctx.CancellationToken.IsCancellationRequested)
        {
            var status = await _repo.GetStatusAsync(req.PaymentId, ctx.CancellationToken);
            await stream.WriteAsync(new PaymentStatusEvent { PaymentId = req.PaymentId, Status = status });

            if (status is PaymentStatus.Completed or PaymentStatus.Failed) break;

            await Task.Delay(TimeSpan.FromSeconds(1), ctx.CancellationToken);
        }
    }
}

Enregistrement ASP.NET Core

// Program.cs
builder.Services.AddGrpc(opt =>
{
    opt.MaxReceiveMessageSize = 4 * 1024 * 1024; // 4 MB
    opt.EnableDetailedErrors  = builder.Environment.IsDevelopment();
    opt.Interceptors.Add<ServerLoggingInterceptor>();
    opt.Interceptors.Add<ServerMetricsInterceptor>();
});
builder.Services.AddGrpcHealthChecks();

app.MapGrpcService<PaymentServiceImpl>();
app.MapGrpcHealthChecksService();

Client avec deadline et retry

var channel = GrpcChannel.ForAddress("https://payments.internal:5001", new GrpcChannelOptions
{
    ServiceConfig = new ServiceConfig
    {
        MethodConfigs = { new MethodConfig
        {
            Names = { MethodName.Default },
            RetryPolicy = new RetryPolicy
            {
                MaxAttempts        = 3,
                InitialBackoff     = TimeSpan.FromMilliseconds(100),
                MaxBackoff         = TimeSpan.FromSeconds(2),
                BackoffMultiplier  = 2,
                RetryableStatusCodes = { StatusCode.Unavailable }
            }
        }}
    }
});

var client = new PaymentService.PaymentServiceClient(channel);

var reply = await client.CreatePaymentAsync(
    new CreatePaymentRequest { IdempotencyKey = Guid.NewGuid().ToString(), AmountCents = 1500, Currency = "TND" },
    deadline: DateTime.UtcNow.AddSeconds(5));

Gestion des erreurs — Status codes à utiliser

SituationStatus code gRPC
Champ manquant / invalideINVALID_ARGUMENT
Ressource introuvableNOT_FOUND
Conflit (doublon)ALREADY_EXISTS
Non authentifiéUNAUTHENTICATED
Accès refuséPERMISSION_DENIED
Timeout / deadline dépasséeDEADLINE_EXCEEDED
Service indisponibleUNAVAILABLE
Erreur interneINTERNAL

Toujours lever RpcException côté serveur — ne jamais laisser remonter une exception .NET brute.

Versionning et compatibilité

Garde-fous et anti-patterns

Anti-patternProblèmeCorrection
Réutiliser Request entre plusieurs RPCsCouplage fort, évolution impossibleUn Request/Response par RPC
Champ string pour les montants monétairesArrondi, parsingint64 amount_cents
Pas de deadline côté clientAppels pendants indéfinisToujours passer deadline:
Écrire dans le code généréPerdu à la prochaine générationNe toucher qu'aux fichiers .proto
Enum sans valeur 0Decode incohérent protobuf3Toujours FOO_UNSPECIFIED = 0
Message google.protobuf.Empty en réponsePas d'évolution possibleToujours un message dédié XxxResponse
Streaming pour des requêtes unitaires simplesComplexité inutileUnaire si un message suffit
TLS désactivé en prodDonnées en clairmTLS obligatoire hors cluster privé

Checklist avant livraison