microservice:create generator wires this up for you: it assigns the new service a free port, declares it in modules/app/app.yml, and adds an entry to the project root .env.yml so you can supply the real URL per environment.
The discovery is env-var-driven. The API never hard-codes where a microservice lives. Instead, app.yml names the environment variable that holds the URL, and the actual value comes from .env.yml for the current environment. Swap the value per environment (local, staging, production) without touching code.
How it works
| Concern | Where it lives | Value |
|---|---|---|
| Main app / API port | The app process | 3000 |
| Microservice port | The microservice’s own .env.yml (app.port) | Next free port from 3001 upward |
| Service declaration | modules/app/app.yml under microservices: | name + the env var name holding the URL |
| URL env var name | app.yml url field | Pattern MICROSERVICE_<SNAKE_UPPER>_URL |
| Actual URL per environment | Root .env.yml under microservices: | You fill in url for each environment |
| Service-to-service transport | Over HTTP at the configured URL | http://... resolved at runtime |
3000. When you scaffold a microservice, the generator scans every modules/*/.env.yml for port: values already in use and picks the next free port starting at 3001 — so each microservice gets its own distinct port and no two services collide. That chosen port is written to the new microservice’s own .env.yml under app.port.
Service declaration in app.yml
So the API can reach a microservice, the generator declares it in modules/app/app.yml under a microservices: list. Each entry pairs a name with the name of the environment variable that holds the service’s URL:
modules/app/app.yml
url value is not a URL — it is the name of the environment variable (pattern MICROSERVICE_<SNAKE_UPPER>_URL) that holds the actual URL. This indirection is what makes discovery environment-aware: the same declaration resolves to a different address in local, staging, and production.
Root .env.yml URL map
The generator also adds the microservice to the project root .env.yml under a microservices: map. This is where you fill in the real URL for each environment:
.env.yml
url to the address where the service is reachable in that environment — for example http://localhost:3001 locally, or the deployed hostname in staging and production. The declaration in app.yml reads this value through its named environment variable.
Per-microservice runtime config
Each microservice carries its own runtime configuration in its own.env.yml — its distinct port plus its own datastores. A service does not share the API’s Redis or Postgres; it gets its own:
modules/billing/.env.yml
app.port here is the port the generator assigned. The Redis URLs (cache, pubsub, rate_limit) and the Postgres database.url are the service’s own backing stores — keep them separate so each service owns its data.
Talking between services
Communication is over HTTP at the configured URL. The API resolves a microservice’s address from itsapp.yml declaration (the named env var) and the value supplied in .env.yml, then issues an HTTP request to that address. Use the framework’s HTTP client utility (@ooneex/fetcher) to make the call — it is the standard way to perform HTTP requests in an Ooneex app.
Because the address always comes from configuration, you never embed a hostname in code. Change the environment, change the URL, and the calling service follows.
Best practices
- Supply URLs per environment. Fill in each
microservices.<name>.urlin.env.ymlfor local, staging, and production — inject production values through your platform’s secrets/env, not committed files. - Never hard-code URLs. Always go through the env-var-named declaration in
app.ymlso the address is resolved from configuration, not baked into source. - Keep datastores separate. Each microservice owns its Redis and Postgres (
cache,pubsub,rate_limit,database); do not point two services at the same database for shared state — talk over HTTP instead. - Let the generator pick ports. It scans
modules/*/.env.ymland assigns the next free port from3001, so distinct ports stay collision-free; reserve3000for the app/API. - Talk over HTTP at the configured URL. Resolve the address from config and call the service with the HTTP client; treat each microservice as a remote process, not a local import.
- Clean up on removal. Removing a service with
microservice:removestrips its entry frommodules/app/app.yml, the root.env.ymlmap, and its docker-compose block — let the generator do it so nothing dangles.
Related
- Microservice overview — what microservices are in an Ooneex project and when to reach for one.
- Microservice structure — how a generated microservice is laid out.
- microservice:create — the generator that assigns the port and writes these config entries.
- Configuration — how
.env.ymlandapp.ymlare loaded and merged per environment. - Fetcher — the HTTP client utility for calling services at their configured URLs.
- Deployment — supplying per-environment URLs and secrets when you ship.