chore: import upstream snapshot with attribution
govulncheck / govulncheck (push) Waiting to run
Harness (E2E) / Harnesses (mock LLM) (push) Waiting to run
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Waiting to run
Lint / golangci-lint (push) Waiting to run
Run Tests / Unit Tests (push) Waiting to run
Run Tests / Etcd Integration Tests (push) Waiting to run
govulncheck / govulncheck (push) Waiting to run
Harness (E2E) / Harnesses (mock LLM) (push) Waiting to run
Harness (E2E) / Provider harnesses (live LLM conformance) (push) Waiting to run
Lint / golangci-lint (push) Waiting to run
Run Tests / Unit Tests (push) Waiting to run
Run Tests / Etcd Integration Tests (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Hello Service
|
||||
|
||||
A minimal HTTP service using Go Micro, with a single endpoint.
|
||||
|
||||
## Service
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go-micro.dev/v6"
|
||||
)
|
||||
|
||||
type Request struct { Name string `json:"name"` }
|
||||
|
||||
type Response struct { Message string `json:"message"` }
|
||||
|
||||
type Say struct{}
|
||||
|
||||
func (h *Say) Hello(ctx context.Context, req *Request, rsp *Response) error {
|
||||
rsp.Message = "Hello " + req.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
svc := micro.NewService("helloworld")
|
||||
svc.Init()
|
||||
svc.Handle(new(Say))
|
||||
svc.Run()
|
||||
}
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
|
||||
Call it:
|
||||
|
||||
```bash
|
||||
curl -XPOST \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Micro-Endpoint: Say.Hello' \
|
||||
-d '{"name": "Alice"}' \
|
||||
http://127.0.0.1:8080
|
||||
```
|
||||
|
||||
Set a fixed address:
|
||||
|
||||
```go
|
||||
svc := micro.NewService("helloworld",
|
||||
micro.Address(":8080"),
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Learn by Example
|
||||
|
||||
Runnable examples are the fastest way to move from reading the guides to changing
|
||||
one thing. Start with the path that matches where you are in the services →
|
||||
agents → workflows lifecycle.
|
||||
|
||||
## Start here
|
||||
|
||||
For the provider-free first-agent route, run [`examples/first-agent`](https://github.com/micro/go-micro/tree/master/examples/first-agent), then follow [No-secret First Agent](../guides/no-secret-first-agent.html), [Your First Agent](../guides/your-first-agent.html), [Debugging your agent](../guides/debugging-agents.html), and the [0→hero Reference](../guides/zero-to-hero.html).
|
||||
|
||||
| Goal | Runnable example | Why it is useful |
|
||||
| --- | --- | --- |
|
||||
| 0→1 service | [`examples/hello-world`](https://github.com/micro/go-micro/tree/master/examples/hello-world) | Smallest RPC service with a client call and health checks. |
|
||||
| Provider-free first agent | [`examples/first-agent`](https://github.com/micro/go-micro/tree/master/examples/first-agent) | Smallest service-backed agent with a deterministic mock model; no provider key required. |
|
||||
| First service-backed agent | [`examples/agent-demo`](https://github.com/micro/go-micro/tree/master/examples/agent-demo) | Multi-service project/task/team app with agent playground integration. |
|
||||
| 0→hero lifecycle | [`examples/support`](https://github.com/micro/go-micro/tree/master/examples/support) | No-secret support-desk story: typed services, an agent, an event-driven flow, and a guardrail. |
|
||||
| Planning and delegation | [`examples/agent-plan-delegate`](https://github.com/micro/go-micro/tree/master/examples/agent-plan-delegate) | Two agents collaborate through `plan` and `delegate` over normal Go Micro RPC. |
|
||||
| Durable agent runs | [`examples/agent-durable`](https://github.com/micro/go-micro/tree/master/examples/agent-durable) | Checkpoint and resume a model-directed run without replaying completed tool side effects. |
|
||||
| Durable workflows | [`examples/flow-durable`](https://github.com/micro/go-micro/tree/master/examples/flow-durable) | Ordered, checkpointed flow steps resume without duplicating completed side effects. |
|
||||
| AI-callable services | [`examples/mcp`](https://github.com/micro/go-micro/tree/master/examples/mcp) | MCP examples that expose service endpoints as model tools. |
|
||||
|
||||
## Guide-to-example map
|
||||
|
||||
- [Getting Started](../getting-started.html) → run
|
||||
[`examples/support`](https://github.com/micro/go-micro/tree/master/examples/support)
|
||||
to see the full lifecycle before generating your own service.
|
||||
- [No-secret First Agent](../guides/no-secret-first-agent.html) → run
|
||||
[`examples/first-agent`](https://github.com/micro/go-micro/tree/master/examples/first-agent)
|
||||
first for the smallest provider-free agent transcript.
|
||||
- [Your First Agent](../guides/your-first-agent.html) → run
|
||||
[`examples/agent-demo`](https://github.com/micro/go-micro/tree/master/examples/agent-demo)
|
||||
or [`examples/support`](https://github.com/micro/go-micro/tree/master/examples/support)
|
||||
when you want a complete service-backed agent to inspect.
|
||||
- [Debugging your agent](../guides/debugging-agents.html) → keep
|
||||
[`examples/first-agent`](https://github.com/micro/go-micro/tree/master/examples/first-agent)
|
||||
nearby as the smallest mock-model reproduction before inspecting richer runs.
|
||||
- [0→hero Reference](../guides/zero-to-hero.html) → run
|
||||
[`examples/support`](https://github.com/micro/go-micro/tree/master/examples/support)
|
||||
for the human-readable scenario, then `make harness` for the full CI contract.
|
||||
- [Plan & Delegate](../guides/plan-delegate.html) → run
|
||||
[`examples/agent-plan-delegate`](https://github.com/micro/go-micro/tree/master/examples/agent-plan-delegate).
|
||||
- [Agents and Workflows](../guides/agents-and-workflows.html) → run
|
||||
[`examples/flow-durable`](https://github.com/micro/go-micro/tree/master/examples/flow-durable)
|
||||
for deterministic checkpointed steps,
|
||||
[`examples/agent-durable`](https://github.com/micro/go-micro/tree/master/examples/agent-durable)
|
||||
for model-directed checkpointed runs, and
|
||||
[`examples/support`](https://github.com/micro/go-micro/tree/master/examples/support)
|
||||
for the full services → agents → workflows lifecycle.
|
||||
|
||||
## Repository examples
|
||||
|
||||
See the repository [examples index](https://github.com/micro/go-micro/tree/master/examples)
|
||||
for the complete runnable list, including deployment, auth, gRPC interop, MCP,
|
||||
agent, and flow examples.
|
||||
|
||||
## More
|
||||
|
||||
- [Real-World Examples](realworld/index.md)
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Pub/Sub with NATS Broker
|
||||
|
||||
Use the NATS broker for pub/sub.
|
||||
|
||||
## In code
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"go-micro.dev/v6"
|
||||
"go-micro.dev/v6/broker"
|
||||
bnats "go-micro.dev/v6/broker/nats"
|
||||
)
|
||||
|
||||
func main() {
|
||||
b := bnats.NewNatsBroker()
|
||||
svc := micro.NewService("nats-pubsub", micro.Broker(b))
|
||||
svc.Init()
|
||||
|
||||
// subscribe
|
||||
_, _ = broker.Subscribe("events", func(e broker.Event) error {
|
||||
log.Printf("received: %s", string(e.Message().Body))
|
||||
return nil
|
||||
})
|
||||
|
||||
// publish
|
||||
_ = broker.Publish("events", &broker.Message{Body: []byte("hello")})
|
||||
|
||||
svc.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Via environment
|
||||
|
||||
Run your service with env vars set:
|
||||
|
||||
```bash
|
||||
MICRO_BROKER=nats MICRO_BROKER_ADDRESS=nats://127.0.0.1:4222 go run main.go
|
||||
```
|
||||
@@ -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).
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# Service Discovery with Consul
|
||||
|
||||
Use Consul as the service registry.
|
||||
|
||||
## In code
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"go-micro.dev/v6"
|
||||
"go-micro.dev/v6/registry/consul"
|
||||
)
|
||||
|
||||
func main() {
|
||||
reg := consul.NewConsulRegistry()
|
||||
svc := micro.NewService("consul-registry", micro.Registry(reg))
|
||||
svc.Init()
|
||||
svc.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Via environment
|
||||
|
||||
Run your service with env vars set:
|
||||
|
||||
```bash
|
||||
MICRO_REGISTRY=consul MICRO_REGISTRY_ADDRESS=127.0.0.1:8500 go run main.go
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# RPC Client
|
||||
|
||||
Call a running service using the Go Micro client.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go-micro.dev/v6"
|
||||
)
|
||||
|
||||
type Request struct { Name string }
|
||||
|
||||
type Response struct { Message string }
|
||||
|
||||
func main() {
|
||||
svc := micro.NewService("caller")
|
||||
svc.Init()
|
||||
|
||||
req := svc.Client().NewRequest("helloworld", "Say.Hello", &Request{Name: "John"})
|
||||
var rsp Response
|
||||
|
||||
if err := svc.Client().Call(context.TODO(), req, &rsp); err != nil {
|
||||
fmt.Println("error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(rsp.Message)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# State with Postgres Store
|
||||
|
||||
Use the Postgres store for persistent key/value state.
|
||||
|
||||
## In code
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"go-micro.dev/v6"
|
||||
"go-micro.dev/v6/store"
|
||||
postgres "go-micro.dev/v6/store/postgres"
|
||||
)
|
||||
|
||||
func main() {
|
||||
st := postgres.NewStore()
|
||||
svc := micro.NewService("postgres-store", micro.Store(st))
|
||||
svc.Init()
|
||||
|
||||
_ = store.Write(&store.Record{Key: "foo", Value: []byte("bar")})
|
||||
recs, _ := store.Read("foo")
|
||||
log.Println("value:", string(recs[0].Value))
|
||||
|
||||
svc.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Via environment
|
||||
|
||||
Run your service with env vars set:
|
||||
|
||||
```bash
|
||||
MICRO_STORE=postgres \
|
||||
MICRO_STORE_ADDRESS=postgres://user:pass@127.0.0.1:5432/postgres \
|
||||
MICRO_STORE_DATABASE=micro \
|
||||
MICRO_STORE_TABLE=micro \
|
||||
go run main.go
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
layout: default
|
||||
---
|
||||
|
||||
# NATS Transport
|
||||
|
||||
Use NATS as the transport between services.
|
||||
|
||||
## In code
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"go-micro.dev/v6"
|
||||
tnats "go-micro.dev/v6/transport/nats"
|
||||
)
|
||||
|
||||
func main() {
|
||||
t := tnats.NewTransport()
|
||||
svc := micro.NewService("nats-transport", micro.Transport(t))
|
||||
svc.Init()
|
||||
svc.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Via environment
|
||||
|
||||
Run your service with env vars set:
|
||||
|
||||
```bash
|
||||
MICRO_TRANSPORT=nats MICRO_TRANSPORT_ADDRESS=nats://127.0.0.1:4222 go run main.go
|
||||
```
|
||||
Reference in New Issue
Block a user