1
0
mirror of https://github.com/go-micro/go-micro.git synced 2025-11-23 21:44:41 +02:00
Files
go-micro/internal/website/docs/examples/realworld/api-gateway.md

391 lines
8.9 KiB
Markdown
Raw Normal View History

2025-11-13 18:34:40 +00:00
---
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/v5"
"go-micro.dev/v5/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(
micro.Name("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/v5"
"go-micro.dev/v5/client"
"go-micro.dev/v5/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(
micro.Name("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/v5"
"go-micro.dev/v5/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(
micro.Name("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.