Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
16 Sep 2025 · 6 min read ·Article 100 / 110
Go

100. Case Study: A Complete Microservices Architecture with gRPC in Go

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

title: 100. Case Study: A Complete Microservices Architecture with gRPC in Go
author: Maya Wijaya
date: 2024-06-20
tags: [microservices, golang, grpc, arsitektur, studi kasus, kode, devops]

Introduction

In the modern era of software development, microservices architecture has become a standard widely adopted by technology companies both small and large. One of the main reasons is the ability of microservices to improve scalability, technology flexibility, and to speed up the deployment of new features. However, implementing microservices often introduces challenges related to inter-service communication, automation, and deployment orchestration. In this 100th article, I’ll walk through a complete case study of a microservices architecture using gRPC in Go, complete with code examples, flow simulations, and a development workflow.

Case Study: An Online Ordering System

As an example, we’ll build a basic online ordering system with three main services:

  • User Service: manages user data
  • Order Service: handles orders
  • Product Service: manages product data

These three services communicate internally using gRPC.

Architecture Overview

Let’s start with the architecture diagram:

MERMAID
flowchart TD
  A[User Service] --gRPC--> B[Order Service]
  C[Product Service] --gRPC--> B[Order Service]
  D[Client (REST/GRPC)] --gRPC--> A[User Service]
  D --gRPC--> B
  D --gRPC--> C

From this diagram, the user, order, and product services all communicate peer-to-peer using gRPC.


Advantages of gRPC in Go Microservices

A few of the main reasons we choose gRPC for Go microservices:

  • High Performance: gRPC uses Protocol Buffers, which are far faster and more efficient than JSON.
  • Strict Typing: Minimizes bugs caused by type mismatches.
  • Cross Language: gRPC is supported by many programming languages.
  • API Contract: Services are clearly defined via .proto files, making them easy to integrate across teams.

1. Defining the API Contract: Protobuf

All communication begins with defining the API contract in a .proto file. Here’s an example order.proto file:

proto
 1syntax = "proto3";
 2
 3package order;
 4
 5service OrderService {
 6  rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
 7  rpc GetOrder (GetOrderRequest) returns (GetOrderResponse);
 8}
 9
10message CreateOrderRequest {
11  string user_id = 1;
12  string product_id = 2;
13  int32 quantity = 3;
14}
15
16message CreateOrderResponse {
17  string order_id = 1;
18  string status = 2;
19}
20
21message GetOrderRequest {
22  string order_id = 1;
23}
24
25message GetOrderResponse {
26  string order_id = 1;
27  string user_id = 2;
28  string product_id = 3;
29  int32 quantity = 4;
30  string status = 5;
31}

Each microservice has its own .proto file to describe its public interface.


2. Implementing the gRPC Service in Go

Once the contract is defined, we can generate the Go code from the .proto file above using a plugin such as:

bash
1protoc --go_out=. --go-grpc_out=. order.proto

Example Order Service Implementation

Let’s look at a simple gRPC server implementation of the OrderService:

go
 1// order_service.go
 2package main
 3
 4import (
 5	"context"
 6	pb "your_project/orderpb"
 7	"log"
 8	"net"
 9
10	"google.golang.org/grpc"
11)
12
13type orderServer struct {
14	pb.UnimplementedOrderServiceServer
15	orders map[string]*pb.GetOrderResponse
16}
17
18func (s *orderServer) CreateOrder(ctx context.Context, req *pb.CreateOrderRequest) (*pb.CreateOrderResponse, error) {
19	orderID := "ORD12345" // simulated auto ID
20	order := &pb.GetOrderResponse{
21		OrderId:   orderID,
22		UserId:    req.UserId,
23		ProductId: req.ProductId,
24		Quantity:  req.Quantity,
25		Status:    "CREATED",
26	}
27	s.orders[orderID] = order
28	return &pb.CreateOrderResponse{
29		OrderId: orderID,
30		Status:  order.Status,
31	}, nil
32}
33
34func (s *orderServer) GetOrder(ctx context.Context, req *pb.GetOrderRequest) (*pb.GetOrderResponse, error) {
35	if order, ok := s.orders[req.OrderId]; ok {
36		return order, nil
37	}
38	return nil, status.Errorf(codes.NotFound, "Order not found")
39}
40
41func main() {
42	lis, err := net.Listen("tcp", ":50052")
43	if err != nil {
44		log.Fatalf("failed to listen: %v", err)
45	}
46	grpcServer := grpc.NewServer()
47	pb.RegisterOrderServiceServer(grpcServer, &orderServer{orders: make(map[string]*pb.GetOrderResponse)})
48	log.Println("Order Service gRPC running on :50052")
49	grpcServer.Serve(lis)
50}

Order Client Simulation

go
 1// order_client.go
 2package main
 3
 4import (
 5	"context"
 6	"log"
 7	"time"
 8
 9	pb "your_project/orderpb"
10
11	"google.golang.org/grpc"
12)
13
14func main() {
15	conn, err := grpc.Dial(":50052", grpc.WithInsecure())
16	if err != nil {
17		log.Fatalf("did not connect: %v", err)
18	}
19	defer conn.Close()
20	client := pb.NewOrderServiceClient(conn)
21	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
22	defer cancel()
23
24	// Create a new order
25	orderResp, err := client.CreateOrder(ctx, &pb.CreateOrderRequest{
26		UserId:    "USR123",
27		ProductId: "PRD789",
28		Quantity:  2,
29	})
30	if err != nil {
31		log.Fatalf("CreateOrder failed: %v", err)
32	}
33	log.Printf("CreateOrder Response: OrderID=%s Status=%s", orderResp.OrderId, orderResp.Status)
34}

3. Orchestrating the Microservices

In real-world practice, the three services (User, Order, Product) will run on different ports and hosts. For a local setup, we can use docker-compose.

Example docker-compose.yml

yaml
 1version: "3.8"
 2services:
 3  user:
 4    build: ./user-service
 5    ports:
 6      - "50051:50051"
 7  order:
 8    build: ./order-service
 9    ports:
10      - "50052:50052"
11  product:
12    build: ./product-service
13    ports:
14      - "50053:50053"

4. Simulating the Ordering Flow

Let’s illustrate the order process from the client all the way to completion using mermaid:

MERMAID
sequenceDiagram
  participant Client
  participant UserService
  participant OrderService
  participant ProductService

  Client->>UserService: Validate User
  UserService-->>Client: OK
  Client->>ProductService: Check Product & Stock
  ProductService-->>Client: OK
  Client->>OrderService: CreateOrder(UserID, ProductID, Qty)
  OrderService-->>Client: OrderID, Status

5. gRPC vs REST Comparison (Table)

FeaturegRPCREST
PayloadProtocol BuffersJSON
SpeedVery fastSlower
ContractStrongly TypedFlexible
StreamingYesDifficult
Binary SupportNativeNo
Language SupportMulti (Auto-gen)All
Backward CompEasierDepends

6. Best Practices

A few tips for applying a gRPC microservices architecture in Go:

  • Use context and timeout for all RPCs.
  • Handle errors clearly in every service.
  • Apply observability: tracing & monitoring (OpenTelemetry, Prometheus).
  • Use a central registry/discovery service in production (e.g., Etcd, Consul).
  • Apply good Protobuf versioning.

Conclusion

In this case study, we’ve seen how to build a microservices architecture with gRPC in Go—from defining the API contract and implementing the services, to orchestration and simulating the workflow between services. With gRPC, we gain better performance, security, and maintainability—making it a great fit for enterprise scale. For further development, feel free to add JWT authentication, a circuit breaker, and monitoring to each of your services!

Happy experimenting with modern microservices architecture!


References


Happy hacking 🍃

Related Articles

💬 Comments