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.
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@latestCreate a New Project
# Create new project
kratos new order-service
# Enter project directory
cd order-service
# Resolve dependencies
go mod tidyProject 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
└── MakefileProtocol 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/handlerThe 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 brevityDependency 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.0API 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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
