ESC

Type to search...

Gateway Pattern

evo init creates a gateway service automatically. It’s the entry point for all external requests. Every other service lives behind it.

What Is the Gateway

The gateway is a regular EVOID service. Nothing special about its code. What makes it a “gateway” is its role: it receives HTTP requests and routes them to the right service via the message bus.

HTTP Request → Gateway → Message Bus → Service
                     ← Response ←

Project Structure

my-project/
├── services/
│   └── gateway/
│       ├── evoid.toml      # port 8000, adapter asgi
│       └── main.py         # routes, middleware, auth
├── shared/
└── pyproject.toml

Gateway starts on port 8000. New services auto-increment: 8001, 8002, 8003.

Default Gateway

evo init generates a gateway with health check only:

from evoid.web.route import Service, get, post, run

app = Service("gateway")

@get("/health")
async def health() -> dict:
    return {"status": "healthy"}

The gateway doesn’t contain business logic. It routes requests to services via the message bus. Each route converts HTTP to an Intent and publishes it.

How Routing Works

The gateway converts HTTP requests to Intents:

from evoid import Intent, Level, publish

@get("/users/{user_id}")
async def get_user(user_id: int) -> dict:
    result = await publish(Intent(
        name="get_user",          # ← Intent name
        level=Level.STANDARD,
        metadata={"user_id": user_id},
    ))
    return result[0].value if result else {"error": "no handler"}

The service subscribes to “get_user” and handles it:

from evoid import Intent, Level
from evoid.core.extend import add_intent

async def handle_get_user(ctx) -> dict:
    user_id = ctx.intent.metadata.get("user_id")
    return {"id": user_id, "name": f"User {user_id}"}

add_intent(Intent(name="get_user", level=Level.STANDARD), handle_get_user)

Gateway and service don’t know about each other. They only share the Intent name.

Configuring the Gateway

evoid.toml

[service]
name = "gateway"

[runtime]
adapter = "asgi"
host = "0.0.0.0"
port = 8000

[pipeline]
processors = ["validate", "authorize"]

Python Config

from evoid.config import config

app = config(
    service={"name": "gateway"},
    runtime={"adapter": "asgi", "port": 8000},
    pipeline={"processors": ["validate", "authorize"]},
)

Adding Routes

Each gateway route converts HTTP to an Intent. The route is thin — it just extracts parameters and publishes:

from evoid import Intent, Level, publish
from evoid.web.route import get, post

@get("/api/menu")
async def list_menu() -> dict:
    result = await publish(Intent(
        name="list_menu",
        level=Level.EPHEMERAL,
    ))
    return result[0].value if result else {"error": "no handler"}

@post("/api/orders")
async def create_order(sandwich: str, qty: int = 1) -> dict:
    result = await publish(Intent(
        name="create_order",
        level=Level.STANDARD,
        metadata={"sandwich": sandwich, "qty": qty},
    ))
    return result[0].value if result else {"error": "no handler"}

Business logic lives in services. The gateway only routes.

Adding Middleware

Add processors that run before every request:

from evoid.web.route import before

# Rate limit all /api/ routes
before("GET:/api/*", "rate_limit")
before("POST:/api/*", "rate_limit")

# Log all requests
before("GET:/health", "log_request")

Adding Authentication

Protect routes with auth processors:

from evoid.web.route import before

# Require auth for all /api/ routes
before("GET:/api/*", "authorize")
before("POST:/api/*", "authorize")

# Health check stays open (no auth)
# /health has no before() — no auth needed

Gateway vs Direct Service

ApproachWhen to Use
Gateway onlySmall app, single service, prototyping
Gateway + servicesMedium app, 2-5 services
Gateway + clusterLarge app, multiple machines

Scaling

Add Services

evo service new api        # port 8001 (auto)
evo service new payments   # port 8002 (auto)
evo service new inventory  # port 8003 (auto)

Run All

evo run                    # starts all services

Run One

evo service run gateway    # just the gateway
evo service run api        # just the api service

Customizing the Gateway

The gateway is just a service. You can:

  • Add any route decorators (@get, @post, @put, @delete)
  • Add middleware via before() / after()
  • Change the pipeline in evoid.toml
  • Add auth, rate limiting, logging
  • Use it as a reverse proxy (forward to other services via message bus)
  • Use it as an API aggregator (combine multiple service responses)