Go Microservices Tutorial
Microservices architecture in Go enables building scalable, maintainable systems composed of small, independent services. This tutorial covers service design, communication patterns, and deployment strategies.
1. Microservice Fundamentals
1.1 Service Design Principles
Well-designed microservices follow these core principles:
// Good microservice characteristics:
// 1. Single responsibility
type PaymentService struct {
processor PaymentProcessor
}
// 2. Explicit boundaries
type OrderService struct {
paymentClient PaymentClient // Clear dependency
}
// 3. Independent deployability
// Each service has own:
// - Source repo
// - Build pipeline
// - Deployment unit
1.2 Project Structure
Typical microservice project layout:
/order-service
├── cmd/ # Main application
├── internal/ # Private implementation
├── pkg/ # Shared library code
├── api/ # Protocol definitions
├── Dockerfile # Containerization
└── go.mod # Module definition
2. Service Implementation
2.1 HTTP Service
Basic RESTful microservice with Chi router:
package main
import (
"github.com/go-chi/chi/v5"
"net/http"
)
func main() {
r := chi.NewRouter()
// Service endpoints
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
r.Route("/api/v1", func(r chi.Router) {
r.Get("/orders", listOrdersHandler)
r.Post("/orders", createOrderHandler)
})
// Start server
http.ListenAndServe(":8080", r)
}
2.2 Configuration
Environment-aware configuration:
type Config struct {
Port int `env:"PORT" envDefault:"8080"`
DBHost string `env:"DB_HOST" envDefault:"localhost"`
Environment string `env:"ENV" envDefault:"development"`
}
func LoadConfig() (*Config, error) {
var cfg Config
if err := env.Parse(&cfg); err != nil {
return nil, err
}
return &cfg, nil
}
3. Service Communication
3.1 REST Clients
Making HTTP requests between services:
type ProductClient struct {
baseURL string
client *http.Client
}
func (c *ProductClient) GetProduct(id string) (*Product, error) {
resp, err := c.client.Get(fmt.Sprintf("%s/products/%s", c.baseURL, id))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var product Product
if err := json.NewDecoder(resp.Body).Decode(&product); err != nil {
return nil, err
}
return &product, nil
}
3.2 gRPC Services
High-performance RPC communication:
// Protobuf definition (product.proto)
service ProductService {
rpc GetProduct (ProductRequest) returns (ProductResponse);
}
// Go implementation
type productServer struct {
pb.UnimplementedProductServiceServer
}
func (s *productServer) GetProduct(ctx context.Context,
req *pb.ProductRequest) (*pb.ProductResponse, error) {
// Business logic
return &pb.ProductResponse{...}, nil
}
4. Data Management
4.1 Database Patterns
Database access with repository pattern:
type OrderRepository interface {
Create(order *Order) error
GetByID(id string) (*Order, error)
}
type PostgresOrderRepo struct {
db *sql.DB
}
func (r *PostgresOrderRepo) Create(order *Order) error {
_, err := r.db.Exec(
"INSERT INTO orders (...) VALUES (...)",
// fields...
)
return err
}
4.2 Event Sourcing
Using events for inter-service communication:
type OrderCreatedEvent struct {
OrderID string `json:"order_id"`
UserID string `json:"user_id"`
OccurredAt time.Time `json:"occurred_at"`
}
func PublishOrderCreated(event OrderCreatedEvent) error {
eventBytes, err := json.Marshal(event)
if err != nil {
return err
}
return kafkaProducer.Publish("orders.created", eventBytes)
}
5. Deployment & Observability
5.1 Containerization
Dockerfile for Go microservices:
# Build stage
FROM golang:1.19-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o service ./cmd/service
# Runtime stage
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/service .
EXPOSE 8080
CMD ["./service"]
5.2 Monitoring
Adding Prometheus metrics:
func main() {
// Create metrics
requests := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP requests",
},
[]string{"path", "method", "status"},
)
prometheus.MustRegister(requests)
// Instrument handler
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)
}
6. Best Practices
6.1 Service Design
- Domain-driven boundaries: Align services with business capabilities
- Loose coupling: Prefer events over direct HTTP calls
- Circuit breakers: Implement fault tolerance patterns
6.2 Development Workflow
// Recommended tools:
// - go-kit/kit for service scaffolding
// - wire for dependency injection
// - testify for testing
// - docker-compose for local development
// CI/CD Pipeline:
// 1. Run unit tests
// 2. Build container
// 3. Run integration tests
// 4. Deploy to staging
// 5. Canary release
×