Create FastAPI gateway for routing to microservices

- Created api-gateway application structure
- Added pyproject.toml with FastAPI, httpx, and aitbc-core dependencies
- Implemented main.py with routing logic to proxy requests to microservices
- Added service registry for GPU, Marketplace, Agent, Trading, Governance, and Coordinator services
- Configured middleware (request ID, performance logging, validation, error handling)
- Created systemd service file for api-gateway
- Added README.md with service registry information

This completes Phase 4.2: Create FastAPI gateway for routing
This commit is contained in:
aitbc
2026-04-30 11:18:03 +02:00
parent ac299b19b8
commit bd722892e8
5 changed files with 261 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
# AITBC API Gateway
Routes requests to AITBC microservices.
## Service Registry
The gateway routes requests to the following services:
- **GPU Service** (port 8101): `/gpu/*` → GPU resource management
- **Marketplace Service** (port 8102): `/marketplace/*` → GPU marketplace
- **Agent Service** (port 8103): `/agent/*` → Agent operations
- **Trading Service** (port 8104): `/trading/*` → Trading operations
- **Governance Service** (port 8105): `/governance/*` → Governance operations
- **Coordinator API** (port 8000): `/coordinator/*` → Coordinator API (default)
## Installation
```bash
cd /opt/aitbc
poetry install --with api-gateway
```
## Running
```bash
# Development
python -m api_gateway.main
# Production (systemd)
sudo systemctl start api-gateway
sudo systemctl enable api-gateway
```
## Endpoints
- `GET /health` - Health check
- `GET /services` - List registered services
- `/*` - Proxy all other requests to appropriate microservice
## Configuration
Service URLs are configured in `main.py` under the `SERVICES` dictionary.

View File

@@ -0,0 +1,16 @@
[Unit]
Description=AITBC API Gateway
After=network.target
[Service]
Type=simple
User=aitbc
WorkingDirectory=/opt/aitbc/apps/api-gateway
Environment="PATH=/opt/aitbc/venv/bin"
Environment="PYTHONPATH=/opt/aitbc/packages/py/aitbc-core/src:/opt/aitbc/apps/api-gateway/src:/opt/aitbc"
ExecStart=/opt/aitbc/venv/bin/python -m api_gateway.main
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,23 @@
[project]
name = "api-gateway"
version = "0.1.0"
description = "AITBC API Gateway for routing to microservices"
authors = [
{name = "AITBC Team", email = "team@aitbc.dev"}
]
requires-python = ">=3.13"
dependencies = [
"fastapi>=0.104.0",
"uvicorn>=0.24.0",
"httpx>=0.25.0",
"aitbc-core",
]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
packages = [
{ include = "api_gateway", from = "src" }
]

View File

@@ -0,0 +1,6 @@
"""
AITBC API Gateway
Routes requests to microservices
"""
__version__ = "0.1.0"

View File

@@ -0,0 +1,174 @@
"""
API Gateway main application
Routes requests to microservices
"""
import httpx
from contextlib import asynccontextmanager
from typing import AsyncIterator
from fastapi import FastAPI, Request, Response
from fastapi.responses import JSONResponse
from aitbc import (
configure_logging,
get_logger,
RequestIDMiddleware,
PerformanceLoggingMiddleware,
RequestValidationMiddleware,
ErrorHandlerMiddleware,
)
# Configure structured logging
configure_logging(level="INFO")
logger = get_logger(__name__)
# Service registry configuration
SERVICES = {
"gpu": {
"base_url": "http://localhost:8101",
"prefix": "/gpu",
},
"marketplace": {
"base_url": "http://localhost:8102",
"prefix": "/marketplace",
},
"agent": {
"base_url": "http://localhost:8103",
"prefix": "/agent",
},
"trading": {
"base_url": "http://localhost:8104",
"prefix": "/trading",
},
"governance": {
"base_url": "http://localhost:8105",
"prefix": "/governance",
},
"coordinator": {
"base_url": "http://localhost:8000",
"prefix": "/coordinator",
},
}
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
"""Lifecycle events for the API Gateway."""
logger.info("Starting API Gateway")
yield
logger.info("Shutting down API Gateway")
app = FastAPI(
title="AITBC API Gateway",
description="Routes requests to AITBC microservices",
version="0.1.0",
lifespan=lifespan,
)
# Add middleware
app.add_middleware(RequestIDMiddleware)
app.add_middleware(PerformanceLoggingMiddleware)
app.add_middleware(RequestValidationMiddleware, max_request_size=10*1024*1024)
app.add_middleware(ErrorHandlerMiddleware)
@app.get("/health")
async def health() -> dict[str, str]:
"""Health check endpoint"""
return {"status": "healthy", "service": "api-gateway"}
@app.get("/services")
async def list_services() -> dict[str, dict[str, str]]:
"""List registered services"""
return {
service_name: {"prefix": config["prefix"], "url": config["base_url"]}
for service_name, config in SERVICES.items()
}
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
async def proxy_request(path: str, request: Request) -> Response:
"""Proxy request to appropriate microservice"""
# Determine which service should handle the request
service_name = None
for name, config in SERVICES.items():
if path.startswith(config["prefix"].lstrip("/")):
service_name = name
break
if not service_name:
# Default to coordinator-api for unknown paths
service_name = "coordinator"
service_config = SERVICES[service_name]
# Build target URL
target_path = path
if path.startswith(service_config["prefix"]):
target_path = path[len(service_config["prefix"]):].lstrip("/")
target_url = f"{service_config['base_url']}/{target_path}"
if request.url.query:
target_url += f"?{request.url.query}"
# Proxy the request
async with httpx.AsyncClient() as client:
try:
# Forward headers (except host)
headers = dict(request.headers)
headers.pop("host", None)
headers.pop("content-length", None)
# Forward the request
if request.method == "GET":
response = await client.get(target_url, headers=headers, params=request.query_params)
elif request.method == "POST":
body = await request.body()
response = await client.post(target_url, headers=headers, content=body)
elif request.method == "PUT":
body = await request.body()
response = await client.put(target_url, headers=headers, content=body)
elif request.method == "DELETE":
response = await client.delete(target_url, headers=headers)
elif request.method == "PATCH":
body = await request.body()
response = await client.patch(target_url, headers=headers, content=body)
elif request.method == "OPTIONS":
response = await client.options(target_url, headers=headers)
else:
return JSONResponse(
status_code=405,
content={"error": "Method not allowed"}
)
# Return the response
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers),
)
except httpx.RequestError as e:
logger.error(
"Service unavailable",
service=service_name,
target_url=target_url,
error=str(e),
)
return JSONResponse(
status_code=503,
content={
"error": {
"type": "service_unavailable",
"message": f"Service {service_name} is unavailable",
"service": service_name,
}
},
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080)