go-micro.dev/v5@v5.12.0/internal/website/docs/examples/realworld/api-gateway.md (about) 1 --- 2 layout: default 3 --- 4 5 # API Gateway with Backend Services 6 7 A complete example showing an API gateway routing to multiple backend microservices. 8 9 ## Architecture 10 11 ``` 12 ┌─────────────┐ 13 Client ───────>│ API Gateway │ 14 └──────┬──────┘ 15 │ 16 ┌──────────────┼──────────────┐ 17 │ │ │ 18 ┌─────▼────┐ ┌────▼─────┐ ┌────▼─────┐ 19 │ Users │ │ Orders │ │ Products │ 20 │ Service │ │ Service │ │ Service │ 21 └──────────┘ └──────────┘ └──────────┘ 22 │ │ │ 23 └──────────────┼──────────────┘ 24 │ 25 ┌──────▼──────┐ 26 │ PostgreSQL │ 27 └─────────────┘ 28 ``` 29 30 ## Services 31 32 ### 1. Users Service 33 34 ```go 35 // services/users/main.go 36 package main 37 38 import ( 39 "context" 40 "database/sql" 41 "go-micro.dev/v5" 42 "go-micro.dev/v5/server" 43 _ "github.com/lib/pq" 44 ) 45 46 type User struct { 47 ID int64 `json:"id"` 48 Email string `json:"email"` 49 Name string `json:"name"` 50 } 51 52 type UsersService struct { 53 db *sql.DB 54 } 55 56 type GetUserRequest struct { 57 ID int64 `json:"id"` 58 } 59 60 type GetUserResponse struct { 61 User *User `json:"user"` 62 } 63 64 func (s *UsersService) Get(ctx context.Context, req *GetUserRequest, rsp *GetUserResponse) error { 65 var u User 66 err := s.db.QueryRow("SELECT id, email, name FROM users WHERE id = $1", req.ID). 67 Scan(&u.ID, &u.Email, &u.Name) 68 if err != nil { 69 return err 70 } 71 rsp.User = &u 72 return nil 73 } 74 75 func main() { 76 db, err := sql.Open("postgres", "postgres://user:pass@localhost/users?sslmode=disable") 77 if err != nil { 78 panic(err) 79 } 80 defer db.Close() 81 82 svc := micro.NewService( 83 micro.Name("users"), 84 micro.Version("1.0.0"), 85 ) 86 87 svc.Init() 88 89 server.RegisterHandler(svc.Server(), &UsersService{db: db}) 90 91 if err := svc.Run(); err != nil { 92 panic(err) 93 } 94 } 95 ``` 96 97 ### 2. Orders Service 98 99 ```go 100 // services/orders/main.go 101 package main 102 103 import ( 104 "context" 105 "database/sql" 106 "time" 107 "go-micro.dev/v5" 108 "go-micro.dev/v5/client" 109 "go-micro.dev/v5/server" 110 ) 111 112 type Order struct { 113 ID int64 `json:"id"` 114 UserID int64 `json:"user_id"` 115 ProductID int64 `json:"product_id"` 116 Amount float64 `json:"amount"` 117 Status string `json:"status"` 118 CreatedAt time.Time `json:"created_at"` 119 } 120 121 type OrdersService struct { 122 db *sql.DB 123 client client.Client 124 } 125 126 type CreateOrderRequest struct { 127 UserID int64 `json:"user_id"` 128 ProductID int64 `json:"product_id"` 129 Amount float64 `json:"amount"` 130 } 131 132 type CreateOrderResponse struct { 133 Order *Order `json:"order"` 134 } 135 136 func (s *OrdersService) Create(ctx context.Context, req *CreateOrderRequest, rsp *CreateOrderResponse) error { 137 // Verify user exists 138 userReq := s.client.NewRequest("users", "UsersService.Get", &struct{ ID int64 }{ID: req.UserID}) 139 userRsp := &struct{ User interface{} }{} 140 if err := s.client.Call(ctx, userReq, userRsp); err != nil { 141 return err 142 } 143 144 // Verify product exists 145 prodReq := s.client.NewRequest("products", "ProductsService.Get", &struct{ ID int64 }{ID: req.ProductID}) 146 prodRsp := &struct{ Product interface{} }{} 147 if err := s.client.Call(ctx, prodReq, prodRsp); err != nil { 148 return err 149 } 150 151 // Create order 152 var o Order 153 err := s.db.QueryRow(` 154 INSERT INTO orders (user_id, product_id, amount, status, created_at) 155 VALUES ($1, $2, $3, $4, $5) 156 RETURNING id, user_id, product_id, amount, status, created_at 157 `, req.UserID, req.ProductID, req.Amount, "pending", time.Now()). 158 Scan(&o.ID, &o.UserID, &o.ProductID, &o.Amount, &o.Status, &o.CreatedAt) 159 160 if err != nil { 161 return err 162 } 163 164 rsp.Order = &o 165 return nil 166 } 167 168 func main() { 169 db, err := sql.Open("postgres", "postgres://user:pass@localhost/orders?sslmode=disable") 170 if err != nil { 171 panic(err) 172 } 173 defer db.Close() 174 175 svc := micro.NewService( 176 micro.Name("orders"), 177 micro.Version("1.0.0"), 178 ) 179 180 svc.Init() 181 182 server.RegisterHandler(svc.Server(), &OrdersService{ 183 db: db, 184 client: svc.Client(), 185 }) 186 187 if err := svc.Run(); err != nil { 188 panic(err) 189 } 190 } 191 ``` 192 193 ### 3. API Gateway 194 195 ```go 196 // gateway/main.go 197 package main 198 199 import ( 200 "encoding/json" 201 "net/http" 202 "strconv" 203 "go-micro.dev/v5" 204 "go-micro.dev/v5/client" 205 ) 206 207 type Gateway struct { 208 client client.Client 209 } 210 211 func (g *Gateway) GetUser(w http.ResponseWriter, r *http.Request) { 212 idStr := r.URL.Query().Get("id") 213 id, err := strconv.ParseInt(idStr, 10, 64) 214 if err != nil { 215 http.Error(w, "invalid id", http.StatusBadRequest) 216 return 217 } 218 219 req := g.client.NewRequest("users", "UsersService.Get", &struct{ ID int64 }{ID: id}) 220 rsp := &struct{ User interface{} }{} 221 222 if err := g.client.Call(r.Context(), req, rsp); err != nil { 223 http.Error(w, err.Error(), http.StatusInternalServerError) 224 return 225 } 226 227 w.Header().Set("Content-Type", "application/json") 228 json.NewEncoder(w).Encode(rsp) 229 } 230 231 func (g *Gateway) CreateOrder(w http.ResponseWriter, r *http.Request) { 232 var body struct { 233 UserID int64 `json:"user_id"` 234 ProductID int64 `json:"product_id"` 235 Amount float64 `json:"amount"` 236 } 237 238 if err := json.NewDecoder(r.Body).Decode(&body); err != nil { 239 http.Error(w, "invalid request", http.StatusBadRequest) 240 return 241 } 242 243 req := g.client.NewRequest("orders", "OrdersService.Create", body) 244 rsp := &struct{ Order interface{} }{} 245 246 if err := g.client.Call(r.Context(), req, rsp); err != nil { 247 http.Error(w, err.Error(), http.StatusInternalServerError) 248 return 249 } 250 251 w.Header().Set("Content-Type", "application/json") 252 w.WriteHeader(http.StatusCreated) 253 json.NewEncoder(w).Encode(rsp) 254 } 255 256 func main() { 257 svc := micro.NewService( 258 micro.Name("api.gateway"), 259 ) 260 svc.Init() 261 262 gw := &Gateway{client: svc.Client()} 263 264 http.HandleFunc("/users", gw.GetUser) 265 http.HandleFunc("/orders", gw.CreateOrder) 266 267 http.ListenAndServe(":8080", nil) 268 } 269 ``` 270 271 ## Running the Example 272 273 ### Development (Local) 274 275 ```bash 276 # Terminal 1: Users service 277 cd services/users 278 go run main.go 279 280 # Terminal 2: Products service 281 cd services/products 282 go run main.go 283 284 # Terminal 3: Orders service 285 cd services/orders 286 go run main.go 287 288 # Terminal 4: API Gateway 289 cd gateway 290 go run main.go 291 ``` 292 293 ### Testing 294 295 ```bash 296 # Get user 297 curl http://localhost:8080/users?id=1 298 299 # Create order 300 curl -X POST http://localhost:8080/orders \ 301 -H 'Content-Type: application/json' \ 302 -d '{"user_id": 1, "product_id": 100, "amount": 99.99}' 303 ``` 304 305 ### Docker Compose 306 307 ```yaml 308 version: '3.8' 309 310 services: 311 postgres: 312 image: postgres:15 313 environment: 314 POSTGRES_PASSWORD: secret 315 ports: 316 - "5432:5432" 317 318 users: 319 build: ./services/users 320 environment: 321 MICRO_REGISTRY: nats 322 MICRO_REGISTRY_ADDRESS: nats://nats:4222 323 DATABASE_URL: postgres://postgres:secret@postgres/users 324 depends_on: 325 - postgres 326 - nats 327 328 products: 329 build: ./services/products 330 environment: 331 MICRO_REGISTRY: nats 332 MICRO_REGISTRY_ADDRESS: nats://nats:4222 333 DATABASE_URL: postgres://postgres:secret@postgres/products 334 depends_on: 335 - postgres 336 - nats 337 338 orders: 339 build: ./services/orders 340 environment: 341 MICRO_REGISTRY: nats 342 MICRO_REGISTRY_ADDRESS: nats://nats:4222 343 DATABASE_URL: postgres://postgres:secret@postgres/orders 344 depends_on: 345 - postgres 346 - nats 347 348 gateway: 349 build: ./gateway 350 ports: 351 - "8080:8080" 352 environment: 353 MICRO_REGISTRY: nats 354 MICRO_REGISTRY_ADDRESS: nats://nats:4222 355 depends_on: 356 - users 357 - products 358 - orders 359 360 nats: 361 image: nats:latest 362 ports: 363 - "4222:4222" 364 ``` 365 366 Run with: 367 ```bash 368 docker-compose up 369 ``` 370 371 ## Key Patterns 372 373 1. **Service isolation**: Each service owns its database 374 2. **Service communication**: Via Go Micro client 375 3. **Gateway pattern**: Single entry point for clients 376 4. **Error handling**: Proper HTTP status codes 377 5. **Registry**: mDNS for local, NATS for Docker 378 379 ## Production Considerations 380 381 - Add authentication/authorization 382 - Implement request tracing 383 - Add circuit breakers for service calls 384 - Use connection pooling 385 - Add rate limiting 386 - Implement proper logging 387 - Use health checks 388 - Add metrics collection 389 390 See [Production Patterns](../realworld/) for more details.