chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Has been cancelled
Lint / golangci-lint (push) Has been cancelled
Run Tests / Unit Tests (push) Has been cancelled
Run Tests / Etcd Integration Tests (push) Has been cancelled
Harness (E2E) / Harnesses (mock LLM) (push) Has been cancelled
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:33 +08:00
commit e071084ebe
982 changed files with 160368 additions and 0 deletions
@@ -0,0 +1,387 @@
---
layout: default
---
# API Gateway with Backend Services
A complete example showing an API gateway routing to multiple backend microservices.
## Architecture
```
┌─────────────┐
Client ───────>│ API Gateway │
└──────┬──────┘
┌──────────────┼──────────────┐
│ │ │
┌─────▼────┐ ┌────▼─────┐ ┌────▼─────┐
│ Users │ │ Orders │ │ Products │
│ Service │ │ Service │ │ Service │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└──────────────┼──────────────┘
┌──────▼──────┐
│ PostgreSQL │
└─────────────┘
```
## Services
### 1. Users Service
```go
// services/users/main.go
package main
import (
"context"
"database/sql"
"go-micro.dev/v6"
"go-micro.dev/v6/server"
_ "github.com/lib/pq"
)
type User struct {
ID int64 `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
}
type UsersService struct {
db *sql.DB
}
type GetUserRequest struct {
ID int64 `json:"id"`
}
type GetUserResponse struct {
User *User `json:"user"`
}
func (s *UsersService) Get(ctx context.Context, req *GetUserRequest, rsp *GetUserResponse) error {
var u User
err := s.db.QueryRow("SELECT id, email, name FROM users WHERE id = $1", req.ID).
Scan(&u.ID, &u.Email, &u.Name)
if err != nil {
return err
}
rsp.User = &u
return nil
}
func main() {
db, err := sql.Open("postgres", "postgres://user:pass@localhost/users?sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
svc := micro.NewService("users",
micro.Version("1.0.0"),
)
svc.Init()
server.RegisterHandler(svc.Server(), &UsersService{db: db})
if err := svc.Run(); err != nil {
panic(err)
}
}
```
### 2. Orders Service
```go
// services/orders/main.go
package main
import (
"context"
"database/sql"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/client"
"go-micro.dev/v6/server"
)
type Order struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
ProductID int64 `json:"product_id"`
Amount float64 `json:"amount"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
type OrdersService struct {
db *sql.DB
client client.Client
}
type CreateOrderRequest struct {
UserID int64 `json:"user_id"`
ProductID int64 `json:"product_id"`
Amount float64 `json:"amount"`
}
type CreateOrderResponse struct {
Order *Order `json:"order"`
}
func (s *OrdersService) Create(ctx context.Context, req *CreateOrderRequest, rsp *CreateOrderResponse) error {
// Verify user exists
userReq := s.client.NewRequest("users", "UsersService.Get", &struct{ ID int64 }{ID: req.UserID})
userRsp := &struct{ User interface{} }{}
if err := s.client.Call(ctx, userReq, userRsp); err != nil {
return err
}
// Verify product exists
prodReq := s.client.NewRequest("products", "ProductsService.Get", &struct{ ID int64 }{ID: req.ProductID})
prodRsp := &struct{ Product interface{} }{}
if err := s.client.Call(ctx, prodReq, prodRsp); err != nil {
return err
}
// Create order
var o Order
err := s.db.QueryRow(`
INSERT INTO orders (user_id, product_id, amount, status, created_at)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, user_id, product_id, amount, status, created_at
`, req.UserID, req.ProductID, req.Amount, "pending", time.Now()).
Scan(&o.ID, &o.UserID, &o.ProductID, &o.Amount, &o.Status, &o.CreatedAt)
if err != nil {
return err
}
rsp.Order = &o
return nil
}
func main() {
db, err := sql.Open("postgres", "postgres://user:pass@localhost/orders?sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
svc := micro.NewService("orders",
micro.Version("1.0.0"),
)
svc.Init()
server.RegisterHandler(svc.Server(), &OrdersService{
db: db,
client: svc.Client(),
})
if err := svc.Run(); err != nil {
panic(err)
}
}
```
### 3. API Gateway
```go
// gateway/main.go
package main
import (
"encoding/json"
"net/http"
"strconv"
"go-micro.dev/v6"
"go-micro.dev/v6/client"
)
type Gateway struct {
client client.Client
}
func (g *Gateway) GetUser(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Query().Get("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
req := g.client.NewRequest("users", "UsersService.Get", &struct{ ID int64 }{ID: id})
rsp := &struct{ User interface{} }{}
if err := g.client.Call(r.Context(), req, rsp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(rsp)
}
func (g *Gateway) CreateOrder(w http.ResponseWriter, r *http.Request) {
var body struct {
UserID int64 `json:"user_id"`
ProductID int64 `json:"product_id"`
Amount float64 `json:"amount"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "invalid request", http.StatusBadRequest)
return
}
req := g.client.NewRequest("orders", "OrdersService.Create", body)
rsp := &struct{ Order interface{} }{}
if err := g.client.Call(r.Context(), req, rsp); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(rsp)
}
func main() {
svc := micro.NewService("api.gateway",
)
svc.Init()
gw := &Gateway{client: svc.Client()}
http.HandleFunc("/users", gw.GetUser)
http.HandleFunc("/orders", gw.CreateOrder)
http.ListenAndServe(":8080", nil)
}
```
## Running the Example
### Development (Local)
```bash
# Terminal 1: Users service
cd services/users
go run main.go
# Terminal 2: Products service
cd services/products
go run main.go
# Terminal 3: Orders service
cd services/orders
go run main.go
# Terminal 4: API Gateway
cd gateway
go run main.go
```
### Testing
```bash
# Get user
curl http://localhost:8080/users?id=1
# Create order
curl -X POST http://localhost:8080/orders \
-H 'Content-Type: application/json' \
-d '{"user_id": 1, "product_id": 100, "amount": 99.99}'
```
### Docker Compose
```yaml
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
users:
build: ./services/users
environment:
MICRO_REGISTRY: nats
MICRO_REGISTRY_ADDRESS: nats://nats:4222
DATABASE_URL: postgres://postgres:secret@postgres/users
depends_on:
- postgres
- nats
products:
build: ./services/products
environment:
MICRO_REGISTRY: nats
MICRO_REGISTRY_ADDRESS: nats://nats:4222
DATABASE_URL: postgres://postgres:secret@postgres/products
depends_on:
- postgres
- nats
orders:
build: ./services/orders
environment:
MICRO_REGISTRY: nats
MICRO_REGISTRY_ADDRESS: nats://nats:4222
DATABASE_URL: postgres://postgres:secret@postgres/orders
depends_on:
- postgres
- nats
gateway:
build: ./gateway
ports:
- "8080:8080"
environment:
MICRO_REGISTRY: nats
MICRO_REGISTRY_ADDRESS: nats://nats:4222
depends_on:
- users
- products
- orders
nats:
image: nats:latest
ports:
- "4222:4222"
```
Run with:
```bash
docker-compose up
```
## Key Patterns
1. **Service isolation**: Each service owns its database
2. **Service communication**: Via Go Micro client
3. **Gateway pattern**: Single entry point for clients
4. **Error handling**: Proper HTTP status codes
5. **Registry**: mDNS for local, NATS for Docker
## Production Considerations
- Add authentication/authorization
- Implement request tracing
- Add circuit breakers for service calls
- Use connection pooling
- Add rate limiting
- Implement proper logging
- Use health checks
- Add metrics collection
See [Production Patterns](../realworld/) for more details.
@@ -0,0 +1,365 @@
---
layout: default
---
# Graceful Shutdown
Properly shutting down services to avoid dropped requests and data loss.
## The Problem
Without graceful shutdown:
- In-flight requests are dropped
- Database connections leak
- Resources aren't cleaned up
- Load balancers don't know service is down
## Solution
Go Micro handles SIGTERM/SIGINT by default, but you need to implement cleanup logic.
## Basic Pattern
```go
package main
import (
"context"
"os"
"os/signal"
"syscall"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/logger"
)
func main() {
svc := micro.NewService("myservice",
micro.BeforeStop(func() error {
logger.Info("Service stopping, running cleanup...")
return cleanup()
}),
)
svc.Init()
// Your service logic
if err := svc.Handle(new(Handler)); err != nil {
logger.Fatal(err)
}
// Run with graceful shutdown
if err := svc.Run(); err != nil {
logger.Fatal(err)
}
logger.Info("Service stopped gracefully")
}
func cleanup() error {
// Close database connections
// Flush logs
// Stop background workers
// etc.
return nil
}
```
## Database Cleanup
```go
type Service struct {
db *sql.DB
}
func (s *Service) Shutdown(ctx context.Context) error {
logger.Info("Closing database connections...")
// Stop accepting new requests
s.db.SetMaxOpenConns(0)
// Wait for existing connections to finish (with timeout)
done := make(chan struct{})
go func() {
s.db.Close()
close(done)
}()
select {
case <-done:
logger.Info("Database closed gracefully")
return nil
case <-ctx.Done():
logger.Warn("Database close timeout, forcing")
return ctx.Err()
}
}
```
## Background Workers
```go
type Worker struct {
quit chan struct{}
done chan struct{}
}
func (w *Worker) Start() {
w.quit = make(chan struct{})
w.done = make(chan struct{})
go func() {
defer close(w.done)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
w.doWork()
case <-w.quit:
logger.Info("Worker stopping...")
return
}
}
}()
}
func (w *Worker) Stop(timeout time.Duration) error {
close(w.quit)
select {
case <-w.done:
logger.Info("Worker stopped gracefully")
return nil
case <-time.After(timeout):
return fmt.Errorf("worker shutdown timeout")
}
}
```
## Complete Example
```go
package main
import (
"context"
"database/sql"
"fmt"
"os"
"os/signal"
"sync"
"syscall"
"time"
"go-micro.dev/v6"
"go-micro.dev/v6/logger"
)
type Application struct {
db *sql.DB
workers []*Worker
wg sync.WaitGroup
mu sync.RWMutex
closing bool
}
func NewApplication(db *sql.DB) *Application {
return &Application{
db: db,
workers: make([]*Worker, 0),
}
}
func (app *Application) AddWorker(w *Worker) {
app.workers = append(app.workers, w)
w.Start()
}
func (app *Application) Shutdown(ctx context.Context) error {
app.mu.Lock()
if app.closing {
app.mu.Unlock()
return nil
}
app.closing = true
app.mu.Unlock()
logger.Info("Starting graceful shutdown...")
// Stop accepting new work
logger.Info("Stopping workers...")
for _, w := range app.workers {
if err := w.Stop(5 * time.Second); err != nil {
logger.Warnf("Worker failed to stop: %v", err)
}
}
// Wait for in-flight requests (with timeout)
shutdownComplete := make(chan struct{})
go func() {
app.wg.Wait()
close(shutdownComplete)
}()
select {
case <-shutdownComplete:
logger.Info("All requests completed")
case <-ctx.Done():
logger.Warn("Shutdown timeout, forcing...")
}
// Close resources
logger.Info("Closing database...")
if err := app.db.Close(); err != nil {
logger.Errorf("Database close error: %v", err)
}
logger.Info("Shutdown complete")
return nil
}
func main() {
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
logger.Fatal(err)
}
app := NewApplication(db)
// Add background workers
app.AddWorker(&Worker{name: "cleanup"})
app.AddWorker(&Worker{name: "metrics"})
svc := micro.NewService("myservice",
micro.BeforeStop(func() error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return app.Shutdown(ctx)
}),
)
svc.Init()
handler := &Handler{app: app}
if err := svc.Handle(handler); err != nil {
logger.Fatal(err)
}
// Run service
if err := svc.Run(); err != nil {
logger.Fatal(err)
}
}
```
## Kubernetes Integration
### Liveness and Readiness Probes
```go
func (h *Handler) Health(ctx context.Context, req *struct{}, rsp *HealthResponse) error {
// Liveness: is the service alive?
rsp.Status = "ok"
return nil
}
func (h *Handler) Ready(ctx context.Context, req *struct{}, rsp *ReadyResponse) error {
h.app.mu.RLock()
closing := h.app.closing
h.app.mu.RUnlock()
if closing {
// Stop receiving traffic during shutdown
return fmt.Errorf("shutting down")
}
// Check dependencies
if err := h.app.db.Ping(); err != nil {
return fmt.Errorf("database unhealthy: %w", err)
}
rsp.Status = "ready"
return nil
}
```
### Kubernetes Manifest
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice
spec:
replicas: 3
template:
spec:
containers:
- name: myservice
image: myservice:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
lifecycle:
preStop:
exec:
# Give service time to drain before SIGTERM
command: ["/bin/sh", "-c", "sleep 10"]
terminationGracePeriodSeconds: 40
```
## Best Practices
1. **Set timeouts**: Don't wait forever for shutdown
2. **Stop accepting work early**: Set readiness to false
3. **Drain in-flight requests**: Let current work finish
4. **Close resources properly**: Databases, file handles, etc.
5. **Log shutdown progress**: Help debugging
6. **Handle SIGTERM and SIGINT**: Kubernetes sends SIGTERM
7. **Coordinate with load balancer**: Use readiness probes
8. **Test shutdown**: Regularly test graceful shutdown works
## Testing Shutdown
```bash
# Start service
go run main.go &
PID=$!
# Send some requests
for i in {1..10}; do
curl http://localhost:8080/endpoint &
done
# Trigger graceful shutdown
kill -TERM $PID
# Verify all requests completed
wait
```
## Common Pitfalls
- **No timeout**: Service hangs during shutdown
- **Not stopping workers**: Background jobs continue
- **Database leaks**: Connections not closed
- **Ignored signals**: Service killed forcefully
- **No readiness probe**: Traffic during shutdown
## Related
- [API Gateway Example](api-gateway.md) - Multi-service architecture
- [Getting Started Guide](../../getting-started.md) - Basic service setup
@@ -0,0 +1,54 @@
---
layout: default
---
# Real-World Examples
Production-ready patterns and complete application examples.
## Available Examples
- [API Gateway with Backend Services](api-gateway.md) - Complete multi-service architecture with users, orders, and products services
- [Graceful Shutdown](graceful-shutdown.md) - Production-ready shutdown patterns with Kubernetes integration
## Coming Soon
We're actively working on additional real-world examples. Contributions are welcome!
**Complete Applications**
- Event-Driven Microservices - Pub/sub patterns
- CQRS Pattern - Command Query Responsibility Segregation
- Saga Pattern - Distributed transactions
**Production Patterns**
- Health Checks and Readiness
- Retry and Circuit Breaking
- Distributed Tracing with OpenTelemetry
- Structured Logging
- Metrics and Monitoring
**Testing Strategies**
- Unit Testing Services
- Integration Testing
- Contract Testing
- Load Testing
**Deployment**
- Kubernetes Deployment
- Docker Compose Setup
- CI/CD Pipeline Examples
- Blue-Green Deployment
**Integration Examples**
- PostgreSQL with Transactions
- Redis Caching Strategies
- Message Queue Integration
- External API Integration
Each example will include:
- Complete, runnable code
- Configuration for development and production
- Testing approach
- Common pitfalls and solutions
Want to contribute? See our [Contributing Guide](../../contributing.md).