Mastering Production‑Ready HTTP Microservices with Go and Kratos

This guide walks you through building a production‑grade e‑commerce order service using the Go‑based Kratos microservice framework, covering environment setup, protobuf contract definition, code generation, layered architecture, dependency injection, configuration, deployment, testing, and performance optimizations for cloud‑native backends.

Ray's Galactic Tech
Ray's Galactic Tech
Ray's Galactic Tech
Mastering Production‑Ready HTTP Microservices with Go and Kratos

Introduction

In the cloud‑native era, microservice architecture is the standard for building highly available and scalable backend systems. Kratos, an open‑source Go microservice framework from Bilibili, offers elegant design, a rich middleware ecosystem, and a Protobuf‑first development philosophy.

Why Choose Kratos?

Protobuf First : Generates code from .proto files to enforce strict interface contracts.

Dual‑Protocol Support : Native HTTP and gRPC support with one‑click switching.

Dependency Injection : Uses Wire for clear code structure.

Observability : Built‑in tracing, metrics, and log collection.

Service Governance : Out‑of‑the‑box service registration, circuit breaking, rate limiting, and load balancing.

Typical scenarios include high‑concurrency internet services, multi‑language microservice ecosystems, strict contract requirements, and cloud‑native deployment environments.

Environment Preparation & Project Initialization

Install Required Tools

# Install Kratos CLI
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest
# Install Protobuf tools
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2@latest
# Install Wire for DI
go install github.com/google/wire/cmd/wire@latest

Create a New Project

# Create new project
kratos new order-service
# Enter project directory
cd order-service
# Resolve dependencies
go mod tidy

Project Structure

order-service/
├── api/                 # Protocol definitions
│   └── order/
│       └── v1/
│           ├── order.proto   # Service definition
│           ├── http.proto    # HTTP routing config
│           └── error.proto   # Error codes
├── cmd/                 # Service entry points
│   └── order-service/
│       ├── main.go
│       ├── wire.go        # Wire DI config
│       └── wire_gen.go    # Generated DI code
├── internal/            # Business logic
│   ├── conf/            # Config structs
│   ├── handler/        # Request handling layer
│   ├── service/        # Core service layer
│   ├── model/          # Data models
│   └── data/           # Data access layer
├── configs/            # Configuration files
├── go.mod
└── Makefile

Protocol Definition: From Proto to Generated Code

Order Service Interface

Create api/order/v1/order.proto:

syntax = "proto3";
package order.v1;
import "google/api/annotations.proto";
import "validate/validate.proto";
option go_package = "github.com/yourorg/order-service/api/order/v1;v1";

// Order service definition
service OrderService {
  // Create order
  rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse) {
    option (google.api.http) = {
      post: "/api/v1/orders"
      body: "*"
    };
  }
  // Get order details
  rpc GetOrder(GetOrderRequest) returns (GetOrderResponse) {
    option (google.api.http) = {
      get: "/api/v1/orders/{order_id}"
    };
  }
  // List orders
  rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse) {
    option (google.api.http) = {
      get: "/api/v1/orders"
    };
  }
  // Cancel order
  rpc CancelOrder(CancelOrderRequest) returns (CancelOrderResponse) {
    option (google.api.http) = {
      put: "/api/v1/orders/{order_id}/cancel"
      body: "*"
    };
  }
}

// Message definitions (OrderItem, Order, CreateOrderRequest, etc.) ...

Define Error Codes

syntax = "proto3";
package order.v1;
option go_package = "github.com/yourorg/order-service/api/order/v1;v1";

enum ErrorReason {
  ERROR_REASON_UNSPECIFIED = 0;
  ORDER_NOT_FOUND = 1;
  ORDER_ALREADY_CANCELLED = 2;
  INSUFFICIENT_STOCK = 3;
  INVALID_USER = 4;
}

Generate Code

# Run in project root
kratos proto add api/order/v1/order.proto
kratos proto client api/order/v1/order.proto
kratos proto server api/order/v1/order.proto -t internal/handler

The generated files include order.pb.go, order_http.pb.go, order_grpc.pb.go, and handler stubs.

Business Implementation: Layered Architecture

Data Model ( internal/model/order.go )

package model

import "time"

type OrderStatus string

const (
    OrderStatusPending   OrderStatus = "pending"
    OrderStatusPaid      OrderStatus = "paid"
    OrderStatusShipped   OrderStatus = "shipped"
    OrderStatusCompleted OrderStatus = "completed"
    OrderStatusCancelled OrderStatus = "cancelled"
)

type Order struct {
    ID          string      `bson:"_id" json:"id"`
    UserID      string      `bson:"user_id" json:"user_id"`
    Items       []OrderItem `bson:"items" json:"items"`
    TotalAmount int64       `bson:"total_amount" json:"total_amount"`
    Status      OrderStatus `bson:"status" json:"status"`
    Address     string      `bson:"address" json:"address"`
    Remark      string      `bson:"remark" json:"remark"`
    CreatedAt   time.Time   `bson:"created_at" json:"created_at"`
    UpdatedAt   time.Time   `bson:"updated_at" json:"updated_at"`
}

type OrderItem struct {
    ProductID   string `bson:"product_id" json:"product_id"`
    ProductName string `bson:"product_name" json:"product_name"`
    Quantity    int32  `bson:"quantity" json:"quantity"`
    UnitPrice   int64  `bson:"unit_price" json:"unit_price"`
}

Repository Layer ( internal/data/order_repo.go )

package data

import (
    "context"
    "time"
    "github.com/yourorg/order-service/internal/model"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
    "github.com/google/uuid"
)

type OrderRepo struct {
    collection *mongo.Collection
}

func NewOrderRepo(db *mongo.Database) *OrderRepo {
    return &OrderRepo{collection: db.Collection("orders")}
}

func (r *OrderRepo) Create(ctx context.Context, order *model.Order) error {
    order.ID = uuid.New().String()
    order.CreatedAt = time.Now()
    order.UpdatedAt = time.Now()
    _, err := r.collection.InsertOne(ctx, order)
    return err
}

func (r *OrderRepo) GetByID(ctx context.Context, id string) (*model.Order, error) {
    var order model.Order
    err := r.collection.FindOne(ctx, bson.M{"_id": id}).Decode(&order)
    if err != nil {
        return nil, err
    }
    return &order, nil
}

func (r *OrderRepo) ListByUserID(ctx context.Context, userID string, page, pageSize int32, status string) ([]*model.Order, int32, error) {
    filter := bson.M{"user_id": userID}
    if status != "" {
        filter["status"] = status
    }
    total, err := r.collection.CountDocuments(ctx, filter)
    if err != nil {
        return nil, 0, err
    }
    opts := &mongo.FindOptions{Skip: int64((page-1)*pageSize), Limit: int64(pageSize), Sort: bson.D{{"created_at", -1}}}
    cursor, err := r.collection.Find(ctx, filter, opts)
    if err != nil {
        return nil, 0, err
    }
    defer cursor.Close(ctx)
    var orders []*model.Order
    if err = cursor.All(ctx, &orders); err != nil {
        return nil, 0, err
    }
    return orders, int32(total), nil
}

func (r *OrderRepo) UpdateStatus(ctx context.Context, id string, status model.OrderStatus) error {
    _, err := r.collection.UpdateOne(ctx, bson.M{"_id": id}, bson.M{"$set": bson.M{"status": status, "updated_at": time.Now()}})
    return err
}

Service Layer ( internal/service/order.go )

package service

import (
    "context"
    "errors"
    pb "github.com/yourorg/order-service/api/order/v1"
    "github.com/yourorg/order-service/internal/model"
    "github.com/yourorg/order-service/internal/data"
    "github.com/go-kratos/kratos/v2/log"
    "go.mongodb.org/mongo-driver/mongo"
)

type OrderService struct {
    pb.UnimplementedOrderServiceServer
    repo *data.OrderRepo
    log  *log.Helper
}

func NewOrderService(repo *data.OrderRepo, logger log.Logger) *OrderService {
    return &OrderService{repo: repo, log: log.NewHelper(logger)}
}

// CreateOrder creates a new order
func (s *OrderService) CreateOrder(ctx context.Context, req *pb.CreateOrderRequest) (*pb.CreateOrderResponse, error) {
    s.log.Infof("CreateOrder request: user_id=%s, items=%d", req.UserId, len(req.Items))
    if err := s.validateUser(ctx, req.UserId); err != nil {
        return nil, pb.ErrorReasonInvalidUser.Error("invalid user")
    }
    if err := s.checkStock(ctx, req.Items); err != nil {
        return nil, pb.ErrorReasonInsufficientStock.Error(err.Error())
    }
    order := &model.Order{UserID: req.UserId, Items: s.toOrderItems(req.Items), TotalAmount: s.calculateTotal(req.Items), Status: model.OrderStatusPending, Address: req.Address, Remark: req.Remark}
    if err := s.repo.Create(ctx, order); err != nil {
        s.log.Errorf("Create order failed: %v", err)
        return nil, err
    }
    s.log.Infof("Order created: order_id=%s", order.ID)
    return &pb.CreateOrderResponse{OrderId: order.ID}, nil
}

// GetOrder retrieves order details
func (s *OrderService) GetOrder(ctx context.Context, req *pb.GetOrderRequest) (*pb.GetOrderResponse, error) {
    order, err := s.repo.GetByID(ctx, req.OrderId)
    if err != nil {
        if errors.Is(err, mongo.ErrNoDocuments) {
            return nil, pb.ErrorReasonOrderNotFound.Error("order not found")
        }
        return nil, err
    }
    return &pb.GetOrderResponse{Order: s.toPbOrder(order)}, nil
}

// ListOrders returns a paginated list
func (s *OrderService) ListOrders(ctx context.Context, req *pb.ListOrdersRequest) (*pb.ListOrdersResponse, error) {
    page := req.Page
    if page <= 0 { page = 1 }
    pageSize := req.PageSize
    if pageSize <= 0 { pageSize = 20 }
    orders, total, err := s.repo.ListByUserID(ctx, req.UserId, page, pageSize, req.Status)
    if err != nil { return nil, err }
    pbOrders := make([]*pb.Order, 0, len(orders))
    for _, o := range orders { pbOrders = append(pbOrders, s.toPbOrder(o)) }
    return &pb.ListOrdersResponse{Orders: pbOrders, Total: total}, nil
}

// CancelOrder cancels an existing order
func (s *OrderService) CancelOrder(ctx context.Context, req *pb.CancelOrderRequest) (*pb.CancelOrderResponse, error) {
    order, err := s.repo.GetByID(ctx, req.OrderId)
    if err != nil { return nil, pb.ErrorReasonOrderNotFound.Error("order not found") }
    if order.Status == model.OrderStatusCancelled {
        return nil, pb.ErrorReasonOrderAlreadyCancelled.Error("order already cancelled")
    }
    if err := s.repo.UpdateStatus(ctx, req.OrderId, model.OrderStatusCancelled); err != nil { return nil, err }
    s.log.Infof("Order cancelled: order_id=%s, reason=%s", req.OrderId, req.Reason)
    return &pb.CancelOrderResponse{Success: true}, nil
}

// Helper methods (validation, stock check, conversion, total calculation) omitted for brevity

Dependency Injection with Wire ( cmd/order-service/wire.go )

//go:build wireinject
// +build wireinject

package main

import (
    "github.com/google/wire"
    "github.com/yourorg/order-service/internal/data"
    "github.com/yourorg/order-service/internal/service"
    "github.com/go-kratos/kratos/v2"
)

func newApp(*data.OrderRepo, *service.OrderService) (*kratos.App, func(), error) {
    panic(wire.Build(serverSet, appSet))
}

var serverSet = wire.NewSet(
    NewHTTPServer,
    NewGRPCServer,
)

var appSet = wire.NewSet(
    NewApp,
)

Service Startup & Configuration

Main Entry ( cmd/order-service/main.go )

package main

import (
    "flag"
    "os"
    "context"
    "time"
    "github.com/go-kratos/kratos/v2"
    "github.com/go-kratos/kratos/v2/config"
    "github.com/go-kratos/kratos/v2/config/file"
    "github.com/go-kratos/kratos/v2/log"
    "github.com/go-kratos/kratos/v2/transport/grpc"
    "github.com/go-kratos/kratos/v2/transport/http"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    pb "github.com/yourorg/order-service/api/order/v1"
    "github.com/yourorg/order-service/internal/data"
    "github.com/yourorg/order-service/internal/service"
)

var configPath = flag.String("conf", "../../configs", "config path, eg: -conf config.yaml")

func main() {
    flag.Parse()
    logger := log.With(log.NewStdLogger(os.Stdout), "ts", log.DefaultTimestamp, "caller", log.DefaultCaller, "service.id", "order-service", "service.name", "order-service", "service.version", "1.0.0", "trace.id", log.TraceID)
    log.Info("Starting order service")

    // Load configuration
    c := config.New(config.WithSource(file.NewSource(*configPath)))
    defer c.Close()

    // Initialize MongoDB
    mongoClient, err := initMongoDB(c)
    if err != nil { log.Fatal("Failed to connect MongoDB: ", err) }
    defer mongoClient.Disconnect(context.Background())
    db := mongoClient.Database("order_db")
    orderRepo := data.NewOrderRepo(db)
    orderService := service.NewOrderService(orderRepo, logger)

    // HTTP server
    httpSrv := http.NewServer(http.Address(":8000"))
    pb.RegisterOrderServiceHTTPServer(httpSrv, orderService)

    // gRPC server
    grpcSrv := grpc.NewServer(grpc.Address(":9000"))
    pb.RegisterOrderServiceServer(grpcSrv, orderService)

    // Kratos application
    app := kratos.New(
        kratos.Name("order-service"),
        kratos.Version("1.0.0"),
        kratos.Logger(logger),
        kratos.Server(httpSrv, grpcSrv),
    )
    if err := app.Run(); err != nil { log.Fatal("Failed to run app: ", err) }
}

func initMongoDB(c config.Config) (*mongo.Client, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    var mongoURI string
    if err := c.Scan("mongo.uri", &mongoURI); err != nil { mongoURI = "mongodb://localhost:27017" }
    client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
    if err != nil { return nil, err }
    if err := client.Ping(ctx, nil); err != nil { return nil, err }
    log.Info("MongoDB connected successfully")
    return client, nil
}

Configuration File ( configs/config.yaml )

server:
  http:
    addr: 0.0.0.0:8000
    timeout: 5000ms
  grpc:
    addr: 0.0.0.0:9000
    timeout: 5000ms
mongo:
  uri: mongodb://localhost:27017
  database: order_db
registry:
  consul:
    address: 127.0.0.1:8500
    path: /kratos/order-service
tracing:
  endpoint: http://localhost:14268/api/traces
  sample_ratio: 1.0

API Testing & Verification

cURL Examples

# Create order
curl -X POST http://localhost:8000/api/v1/orders \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "user_123",
    "items": [{"product_id": "prod_001", "product_name": "iPhone 15", "quantity": 1, "unit_price": 599900}],
    "address": "Beijing Chaoyang District xxx",
    "remark": "Please ship ASAP"
}'

# Get order details
curl http://localhost:8000/api/v1/orders/{order_id}

# List orders
curl "http://localhost:8000/api/v1/orders?user_id=user_123&page=1&page_size=10"

# Cancel order
curl -X PUT http://localhost:8000/api/v1/orders/{order_id}/cancel \
  -H "Content-Type: application/json" \
  -d '{"reason": "User cancelled"}'

Swagger UI

Kratos automatically generates OpenAPI documentation; after starting the service, visit http://localhost:8000/q/swagger-ui to explore the API.

Production‑Ready Best Practices

Middleware Chain

httpSrv.Use(
    middleware.Logging(logger),
    middleware.Recovery(),
    middleware.Tracing(tracer),
    middleware.Metrics(metrics),
    middleware.RateLimiter(limiter),
)

Error Handling Standard

{
  "code": "ORDER_NOT_FOUND",
  "message": "订单不存在",
  "reason": "ORDER_NOT_FOUND",
  "metadata": {}
}

Tracing Integration (OpenTelemetry + Jaeger)

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
)

exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://jaeger:14268/api/traces")))

Docker Deployment

Dockerfile

FROM golang:1.21 AS builder
WORKDIR /src
COPY . .
RUN go build -ldflags "-X main.Version=1.0.0" -o /app/order-service ./cmd/order-service

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /app
COPY --from=builder /app/order-service .
COPY configs ./configs
EXPOSE 8000 9000
CMD ["./order-service", "-conf", "./configs"]

docker‑compose.yml

version: '3'
services:
  order-service:
    build: .
    ports:
      - "8000:8000"
      - "9000:9000"
    depends_on:
      - mongodb
      - consul
  mongodb:
    image: mongo:6
    ports:
      - "27017:27017"
  consul:
    image: consul:latest
    ports:
      - "8500:8500"

Performance Optimization Suggestions

Connection Pool : Properly size MongoDB and gRPC connection pools.

Cache : Use Redis to cache hot order data.

Asynchronous Processing : Offload non‑critical tasks (e.g., notifications) to a message queue.

Rate Limiting : Implement token‑bucket based API throttling.

Indexes : Create indexes on frequently queried fields such as user_id and status.

Conclusion

By following this end‑to‑end case study, you have learned how to develop a production‑grade HTTP API with Kratos, covering protocol‑driven design, layered architecture, dependency injection, observability, middleware, error handling, Docker deployment, and performance tuning. Kratos demonstrates a robust approach to building high‑availability microservices in Go.

References

Kratos official documentation

Kratos GitHub repository

Protobuf official guide

Wire dependency‑injection guide

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DockerProtobufMongoDBKratosHTTP API
Ray's Galactic Tech
Written by

Ray's Galactic Tech

Practice together, never alone. We cover programming languages, development tools, learning methods, and pitfall notes. We simplify complex topics, guiding you from beginner to advanced. Weekly practical content—let's grow together!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.