ExServiceMeshRouter

A BEAM-native service mesh gateway for Phoenix umbrella applications with automatic service discovery. Tenant handling is owned by each Phoenix application.


Architecture Overview

Request Flow

Client Request
→ Gateway Router
→ Service Registry (host → endpoint)
→ Phoenix Endpoint
→ Phoenix App Router
→ Tenant Plug (inside app)
→ Controllers / LiveView


Installation

If available in Hex, the package can be installed by adding ex_service_mesh_router to your list of dependencies in mix.exs:

def deps do
[
{:ex_service_mesh_router, ">= 0.0.0"}
]
end

Configuration

config :ex_service_mesh_router,
port: 4000

Key Design Principles

1. Gateway is service-only

The gateway ONLY routes by host:

No tenant logic exists in the gateway.


2. Phoenix apps own tenants

Each Phoenix app is responsible for:


3. Shared tenant resolver

A shared module provides consistent tenant parsing across apps.


Project Structure

lib/ router/ application.ex router.ex registry.ex

shared/ tenant_resolver.ex


How It Works

1. Service discovery

At startup, the gateway scans loaded umbrella apps and builds a routing table from Phoenix endpoint config:

Example config:

config :auth_web, AuthWeb.Endpoint, url: [host: "auth.local", port: 4001]

Becomes:

auth.local → AuthWeb.Endpoint


2. Routing

Request:

GET http://auth.local/login

Flow:

  1. Extract host header
  2. Lookup endpoint in registry
  3. Forward request to Phoenix endpoint

3. Tenant handling (inside Phoenix apps)

Example:

tenant1.auth.local

Inside Auth app:

conn.assigns.tenant = "tenant1"


Shared Tenant Resolver

Used by all Phoenix apps:

Router.Shared.TenantResolver.resolve("tenant1.auth.local")


Configuration

Example Phoenix app config

config :auth_web, AuthWeb.Endpoint, url: [ host: "auth.local", port: 4001 ]

config :auth_web, endpoint: AuthWeb.Endpoint


Running the system

Install dependencies

mix deps.get mix compile

Start server

mix phx.server


Local DNS (development)

127.0.0.1 auth.local 127.0.0.1 billing.local


Test URLs

http://auth.local:4000
http://billing.local:4000


What this does NOT do

These belong in:


Mental model

A BEAM-native ingress router where: