Services and domains
Understand the service layer that organizes your app's functions, events, resources, tools, and agents by domain.
A service groups related application behavior and exposes the operations other parts of the application can call. Start with the part of the product
you are building—orders, receipts, accounts—not with the technology you will use to build it. Give that
area a domain folder, keep its behavior and resources together, and expose its public operations through
one service.ts.
Pre-setup
Start with a minimal app and retain its defineApp configuration
and defineEnv contract. A plain service needs no provider profile or Docker service. When
adding a database, bucket, cache, job, event, or agent to the domain, complete that capability’s
provider setup first. Local Docker is the default for
capabilities that require an external service; registering a service does not configure them automatically.
Domain, service, and function
These three terms describe different things:
| Term | Meaning | Example |
|---|---|---|
| Domain | A cohesive area of application responsibility, including its private implementation. | src/services/orders/ owns order creation, pricing, order events, and order-support tools. |
| Service | The domain’s public boundary, assembled in one service.ts. |
orders.createOrder and orders.orderCreated are selected public members. |
| Function | A validated operation that does the work. | create-order.function.ts calculates a total and publishes an event. |
When we say a service contains tools, agents, caches, tasks, or jobs, we mean they belong to its domain.
defineService accepts public functions, events, tasks, and jobs; it is not a registry of every file
or a container that injects resources. A function remains the immediate execution unit, while a task owns
durable execution.
The application architecture
relkit.config.ts application settings and provider bindings
src/
├── platform/ shared environment and infrastructure support
├── services/
│ ├── orders/ an application domain
│ │ ├── service.ts public functions, events, tasks, and jobs
│ │ ├── functions/ business operations and event subscribers
│ │ ├── events/ facts this domain publishes
│ │ ├── tools/ model-callable operations
│ │ ├── agents/ bounded model workflows
│ │ └── cache/ domain-owned cached data
│ └── receipts/ another domain, with its own service.ts
│ ├── service.ts
│ ├── functions/
│ ├── tasks/
│ └── jobs/
└── routes/ URL paths, HTTP methods, and request policy
This is an ownership map, not a checklist of folders to create. Start with a service and one useful
function. Add the other directories only when the domain needs those capabilities. routes and
platform are special layers, not application domains, and do not need a domain service.ts.
The compiler discovers exported descriptors, checks their relationships, and produces an application graph and runtime manifest. The runtime, Inspector, and deployment tooling use that checked model. Application authors use ordinary TypeScript descriptors; no Effect imports are required.
Existing flat src/<name>/service.ts apps remain supported. Scaffolding preserves their detected
layout, while empty projects default to grouped. Keep one boundary shape throughout the app;
src/services/service.ts is not an aggregate boundary. See optional migration.
Follow an operation through the layers
In the commerce example, an HTTP route selects orders.createOrder. The function validates domain input
and reads the Orders price cache. The event examples show how a producing service can
publish a fact and a receiving service can own its reaction; jobs describe durable work.
Each part has a different job:
- The route handles HTTP input and response mapping.
- The service makes selected domain operations available to callers.
- The function owns business behavior and declares the resources it needs.
- The event contract describes what happened; subscriber functions own the reactions.
- Tools and agents reuse domain behavior without relocating it into an AI-specific application layer.
A service does not create an HTTP endpoint automatically. Nor does adding a member grant a user permission to call it. Transport exposure and authorization remain explicit.
Read a real public boundary
This is the larger commerce reference, whose resource/provider packages are already selected. For a lean first app, use Your first service; install a capability from its linked guide before copying that capability’s members into your own domain.
/** Declares the orders service boundary without acquiring native clients. */
import { defineService } from "@relkit/app/services";
import createOrder from "./functions/create-order.function.js";
import deleteOrder from "./functions/delete-order.function.js";
import getOrder from "./functions/get-order.function.js";
import searchOrders from "./functions/search-orders.function.js";
import updateOrder from "./functions/update-order.function.js";
import authorizeOrder from "./functions/authorize-order.function.js";
import streamOrderReport from "./functions/stream-order-report.function.js";
import cleanupOrders from "./jobs/cleanup-orders.job.js";
import exportOrders from "./jobs/export-orders.job.js";
import cleanupOrdersTask from "./tasks/cleanup-orders.task.js";
import exportOrdersTask from "./tasks/export-orders.task.js";
export default defineService({
functions: {
createOrder,
deleteOrder,
getOrder,
searchOrders,
updateOrder,
authorizeOrder,
streamOrderReport,
},
tasks: { cleanupOrdersTask, exportOrdersTask },
jobs: { cleanupOrders, exportOrders },
});
Notice what is absent: the Orders cache, support agent, tools, and event subscribers. They still belong to Orders and are discovered from their own files. They are not public service members.
Choose your next step
Read what a domain is and when to split one, then use where to put your code as the map for each capability. For implementation, follow define a service, expose it through routes, or the first-service example.