Architecture
Strata organizes services around a simple hierarchy: a service contains contexts, and each context exposes operations. Communication flows through a pluggable backend, and scaling happens through service groups.
Service Structure
A Strata application has three levels:
StrataService
The top-level object. It holds configuration, manages the backend connection, coordinates contexts, and runs global middleware. You create one per process.
StrataContext
A logical grouping of related operations -- analogous to an Express Router. A users context might handle get, create, and delete. Contexts are registered with the service under a name, and incoming requests are routed to the matching context.
Operations
The individual units of work within a context. Each operation is an async function that receives a request and returns a response (or calls request.succeed() / request.fail() directly). This is where your business logic lives.
import { StrataService, StrataContext } from '@strata-js/strata';
// Create the service
const service = new StrataService({
service: { serviceGroup: 'UserService' },
backend: { type: 'redis', redis: { host: 'localhost', port: 6379 } },
});
// Create a context
const users = new StrataContext('users');
// Register operations
users.registerOperation('get', async (request) =>
{
const { userId } = request.payload;
const user = await db.findUser(userId);
return { user };
});
users.registerOperation('create', async (request) =>
{
const user = await db.createUser(request.payload);
return { user };
});
// Register the context with the service
service.registerContext(users);
// Start listening for requests
await service.start();That's it. The service listens on its queue, routes incoming requests to the users context, and dispatches to the matching operation.
Pluggable Backends
The backend is the transport layer. It handles sending and receiving messages, managing queues, and coordinating service discovery. Critically, your service code never touches the backend directly -- it's configured once and forgotten.
Built-in Backends
| Backend | Transport | Use Case |
|---|---|---|
redis | Redis lists | Simple, reliable, well-understood |
redis-streams | Redis Streams | Most tested, consumer group support |
null | Nothing | Unit testing |
Swapping Backends
Changing the backend is a configuration change. Your contexts, operations, and middleware don't change at all:
// Redis lists
const service = new StrataService({
service: { serviceGroup: 'MyService' },
backend: { type: 'redis', redis: { host: 'localhost' } },
});
// Redis Streams -- same service code, different config
const service = new StrataService({
service: { serviceGroup: 'MyService' },
backend: { type: 'redis-streams', redis: { host: 'localhost' } },
});Custom Backends
You can implement your own backend by extending BaseStrataBackend and registering it with the service. As long as your backend can send and receive JSON messages, it works. See the Backends reference for built-in options, or the Writing a Custom Backend guide.
Client / Service Model
Strata has two sides: services that process requests and clients that send them.
- A
StrataServicelistens on a queue for incoming requests, routes them through middleware and contexts, and sends responses back. - A
StrataClientsends requests to a service group's queue and listens for responses on its own dedicated queue.
A service can also have an embedded client for making calls to other service groups. This is how services communicate with each other -- service A's client sends a request to service B's queue.
import { StrataClient } from '@strata-js/strata';
const client = new StrataClient({
client: { name: 'MyApp' },
backend: { type: 'redis', redis: { host: 'localhost' } },
});
await client.start();
// Send a request to the UserService
const response = await client.request('UserService', 'users', 'get', {
userId: '12345',
});External Access
The Client/Service model works within the message bus -- both sides need a direct Redis connection. But browsers, mobile apps, CLI tools, and services written in other languages can't connect to Redis directly.
The RPC Bridge is the standard gateway for external consumers. It sits between the outside world and your Strata backend, translating HTTP POST requests and Socket.IO messages into Strata service calls.
External Consumers ──▶ RPC Bridge ──▶ Redis ──▶ Strata Services
(HTTP / WS) (Express + (message (process requests
Socket.IO) bus) as normal)From the service's perspective, nothing changes -- requests arrive through the same queue regardless of whether they came from another Strata client or from a browser through the bridge.
The bridge provides access control (block/allow lists per service), middleware hooks for auth and logging, and supports both HTTP and WebSocket protocols.
- Exposing Services -- setting up the bridge, controlling access, adding middleware
- Building a Web UI -- connecting a browser app to Strata services
- RPC Bridge reference -- complete config types and interface details
Service Groups
A service group is one or more instances of the same service, all reading from the same request queue. This is how Strata handles horizontal scaling.
When you start three instances of UserService, they all listen on the Requests:UserService queue. Each incoming request goes to exactly one instance -- whichever pops it first. No load balancer, no coordinator, no configuration.
/-> UserService (instance 1)
Client -> RPUSH Requests:UserService <envelope> ----------> UserService (instance 2)
\-> UserService (instance 3)Each instance has its own unique ID but shares the service group name. The backend handles the distribution transparently. To scale up, start another instance. To scale down, stop one. The queue absorbs the difference.
Layered Architecture (Optional)
Strata itself doesn't enforce how you organize code inside your operations. However, the framework was designed with iDesign principles in mind, and the recommended architecture follows a layered pattern:
| Layer | Responsibility | Example |
|---|---|---|
| Clients | External consumers (contexts/operations) | Route requests to managers |
| Managers | Business logic orchestration | Coordinate engines and RA |
| Engines | Pure business logic, no I/O | Validation, transformation |
| Resource Access | Data persistence, external APIs | Database queries, HTTP calls |
| Utils | Cross-cutting concerns | Logging, config, helpers |
Each layer only calls the layer directly below it. Managers coordinate between engines and resource access. Engines are pure functions -- no I/O, no state, easily testable. This isn't required, but it works well with Strata's structure.