Skip to content

gRPC Project: Building a Microservice System

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about grpc project: building a microservice system. We cover key concepts, practical examples, and best practices to help you master this topic.

This project brings together everything you have learned about gRPC. You will build a complete microservice system with multiple services, authentication, streaming, error handling, and production deployment.

Project Overview

Build an e-commerce platform backend with three gRPC services: User Service, Product Service, and Order Service. Services communicate via gRPC with mutual TLS, client-side load balancing, and comprehensive error handling.

What You'll Build

  • Protobuf definitions for all services
  • User Service with authentication
  • Product Service with server-side streaming for inventory
  • Order Service with bidirectional streaming for order processing
  • API Gateway with gRPC-Web for browser access
  • Docker deployment with health checking

Why This Project

This project simulates a real-world microservice architecture. You will face the same challenges encountered when building production gRPC systems: service coordination, data consistency, error handling, and deployment complexity.

Flow Chart

flowchart TD
    A[Browser] --> B[Envoy Proxy]
    B --> C[API Gateway]
    C --> D[User Service]
    C --> E[Product Service]
    C --> F[Order Service]
    D --> G[(User DB)]
    E --> H[(Product DB)]
    F --> I[(Order DB)]
    F --> E
    F --> D

Architecture

Service Definitions

syntax = "proto3";

service UserService {
  rpc Register(RegisterRequest) returns (UserResponse);
  rpc Login(LoginRequest) returns (TokenResponse);
  rpc GetUser(GetUserRequest) returns (UserResponse);
  rpc UpdateUser(UpdateUserRequest) returns (UserResponse);
}

service ProductService {
  rpc GetProduct(GetProductRequest) returns (Product);
  rpc ListProducts(ListProductsRequest) returns (stream Product);
  rpc UpdateInventory(UpdateInventoryRequest) returns (InventoryResponse);
}

service OrderService {
  rpc CreateOrder(CreateOrderRequest) returns (Order);
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc ProcessOrders(stream OrderAction) returns (stream OrderStatus);
  rpc CancelOrder(CancelOrderRequest) returns (OrderResponse);
}

Authentication Interceptor

class AuthInterceptor(grpc.ServerInterceptor):
    def __init__(self, jwt_secret):
        self.jwt_secret = jwt_secret

    def intercept_service(self, continuation, handler_call_details):
        method = handler_call_details.method
        public_methods = [
            '/ecommerce.UserService/Register',
            '/ecommerce.UserService/Login',
        ]

        if method in public_methods:
            return continuation(handler_call_details)

        metadata = dict(handler_call_details.invocation_metadata)
        token = metadata.get('authorization', '').replace(
            'Bearer ', '')

        try:
            payload = jwt.decode(
                token, self.jwt_secret,
                algorithms=['HS256'])
            return continuation(handler_call_details)
        except jwt.InvalidTokenError:
            return self._deny_handler()

    def _deny_handler(self):
        return grpc.unary_unary_rpc_method_handler(
            lambda request, context: context.abort(
                grpc.StatusCode.UNAUTHENTICATED,
                'Invalid or expired token'))

Order Processing with Bidirectional Streaming

func (s *orderServer) ProcessOrders(
    stream pb.OrderService_ProcessOrdersServer) error {

    for {
        action, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return err
        }

        switch action.Type {
        case pb.OrderAction_CREATE:
            order, err := s.createOrder(
                action.GetCreateRequest())
            if err != nil {
                stream.Send(&pb.OrderStatus{
                    OrderId: action.OrderId,
                    Status:  pb.OrderStatus_FAILED,
                    Error:   err.Error(),
                })
                continue
            }
            stream.Send(&pb.OrderStatus{
                OrderId: order.Id,
                Status:  pb.OrderStatus_CONFIRMED,
            })

        case pb.OrderAction_CANCEL:
            err := s.cancelOrder(action.OrderId)
            status := pb.OrderStatus_CANCELLED
            errorMsg := ""
            if err != nil {
                status = pb.OrderStatus_FAILED
                errorMsg = err.Error()
            }
            stream.Send(&pb.OrderStatus{
                OrderId: action.OrderId,
                Status:  status,
                Error:   errorMsg,
            })
        }
    }
}

Health Checking

import grpc
from grpc_health.v1 import health_pb2
from grpc_health.v1 import health_pb2_grpc

class HealthServicer(health_pb2_grpc.HealthServicer):
    def Check(self, request, context):
        return health_pb2.HealthCheckResponse(
            status=health_pb2.HealthCheckResponse.SERVING)

    def Watch(self, request, context):
        yield health_pb2.HealthCheckResponse(
            status=health_pb2.HealthCheckResponse.SERVING)

Docker Compose Deployment

version: '3.8'
services:
  envoy:
    image: envoyproxy/envoy:v1.28.0
    ports:
      - "8080:8080"
    volumes:
      - ./envoy.yaml:/etc/envoy/envoy.yaml
    depends_on:
      - api-gateway

  api-gateway:
    build: ./gateway
    ports:
      - "50051:50051"
    depends_on:
      - user-service
      - product-service
      - order-service

  user-service:
    build: ./services/user
    ports:
      - "50052:50051"
    environment:
      - DB_HOST=user-db
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      - user-db

  product-service:
    build: ./services/product
    ports:
      - "50053:50051"
    depends_on:
      - product-db

  order-service:
    build: ./services/order
    ports:
      - "50054:50051"
    depends_on:
      - order-db
      - user-service
      - product-service

  user-db:
    image: postgres:15
    environment:
      POSTGRES_DB: users

  product-db:
    image: postgres:15
    environment:
      POSTGRES_DB: products

  order-db:
    image: postgres:15
    environment:
      POSTGRES_DB: orders

Common Mistakes

Mistake Explanation
Tight coupling between services Services should communicate through defined protobuf interfaces, not direct database access
Ignoring idempotency Order creation and payment processing must be idempotent to handle retries safely
Missing metrics and tracing Add OpenTelemetry instrumentation from the start to debug production issues
Not testing streaming edge cases Test client disconnects, slow consumers, and network interruptions during streams
Overlooking schema evolution Plan for protobuf field deprecation and wire compatibility from day one

FAQ

How do I handle database transactions across gRPC services?

Use the saga pattern with compensating transactions. Each service manages its own database and coordinates via gRPC calls with rollback handlers.

What metrics should I monitor in a gRPC system?

Track request latency (p50/p95/p99), error rates by status code, request throughput, active streams, and connection pool utilization.

How do I test gRPC services end-to-end?

Use gRPC reflection with grpcurl for manual testing, integration tests with test containers for automated testing, and chaos engineering for resilience testing.

Should I use synchronous or asynchronous communication?

Use synchronous gRPC for request-response flows. Use event-driven patterns with message queues for asynchronous workflows like order fulfillment.

How do I handle service discovery in production?

Use Kubernetes DNS for headless services, or deploy a service mesh like Istio that handles service discovery and load balancing for gRPC.

What is the best way to version gRPC services?

Use protobuf package names for major versions (v1, v2). Avoid breaking changes by following protobuf backward compatibility rules.

Mini Project

Complete the full e-commerce platform implementation. Extend it with a payment service, notification service, and admin dashboard. Add OpenTelemetry tracing, Prometheus metrics, and structured logging. Write integration tests in at least two languages.

What's Next

Learn about AsyncAPI for event-driven API documentation

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro