Query Bus
The Query Bus is the core mechanism in DomusJS for handling read operations.
✅ Separates the query requester from the query handler.
✅ Supports CQRS (Command Query Responsibility Segregation) by splitting read and write pipelines.
✅ Allows adding middleware (e.g., caching, logging) transparently.
What is the Query Bus?
Section titled “What is the Query Bus?”Without a query bus, you would call repository methods or services directly, making the system tightly coupled.
With the query bus:
- You ask a query.
- The bus finds the registered handler.
- The handler returns the result.
Example:
const user = await queryBus.ask(new GetUserByIdQuery(userId));
✅ The requester only cares about what they want, not how it’s fetched.
Query Bus Setup
Section titled “Query Bus Setup”To use the Query Bus, you need to:
✅ Ensure the QueryBus implementation has been initialized (by default, InMemoryQueryBus
).
✅ Register each Query and its associated QueryHandler.
ℹ️ Note:
TheQueryBus
should have been initialized earlier by callingregisterDomusCore()
.
You can then safely resolve it from the container usingcontainer.resolve()
.
Example Use Case: Get User by ID
Section titled “Example Use Case: Get User by ID”Step 1: Define the Query
Section titled “Step 1: Define the Query”import { Query } from '@domusjs/core';
export class GetUserByIdQuery implements Query { static readonly TYPE = 'GET_USER_BY_ID'; readonly type = GetUserByIdQuery.TYPE;
constructor(public readonly userId: string) {}}
Step 2: Create the Query Handler
Section titled “Step 2: Create the Query Handler”import { QueryHandler } from '@domusjs/core';import { GetUserByIdQuery } from './queries/get-user-by-id.query';
interface User { id: string; name: string; email: string;}
export class GetUserByIdHandler implements QueryHandler<GetUserByIdQuery, User> { async execute(query: GetUserByIdQuery): Promise<User> { // Fetch and return the user }}
Step 3: Register and Ask
Section titled “Step 3: Register and Ask”import { container } from 'tsyringe';import { GetUserByIdQuery } from './queries/get-user-by-id.query';import { GetUserByIdHandler } from './handlers/get-user-by-id.handler';
// Resolve the QueryBus (already set up by registerDomusCore)const queryBus = container.resolve<QueryBus>('QueryBus');
// Register the handlerqueryBus.register(GetUserByIdQuery, container.resolve(GetUserByIdHandler));
const user = await queryBus.ask(new GetUserByIdQuery('123'));
✅ The requester focuses only on what they want, not how it’s retrieved.
✅ Middleware and tracing can be added without modifying app logic.
✅ Use tsyringe
for dependency injection.
✅ Register all handlers once at application startup.
Core Interfaces
Section titled “Core Interfaces”DomusJS defines the following interfaces:
export interface Query<R = any> { readonly type: string;}
QueryHandler
Section titled “QueryHandler”export interface QueryHandler<Q extends Query<R>, R = any> { execute(query: Q): Promise<R>;}
QueryBus
Section titled “QueryBus”export interface QueryBus { ask<Q extends Query<R>, R = any>(query: Q): Promise<R>; register<Q extends Query<R>, R = any>(queryClass: { TYPE: string }, handler: QueryHandler<Q, R>): void;}
✅ You can build your own QueryBus by following this interface.
Implement Your Own Query Bus
Section titled “Implement Your Own Query Bus”While DomusJS ships with InMemoryQueryBus
, you can implement custom solutions.
Advanced: Distributed Query Bus
Section titled “Advanced: Distributed Query Bus”In complex architectures—especially in microservices or modular monoliths—you may need to resolve queries across service boundaries. DomusJS is designed to support this use case by allowing you to swap the default in-memory QueryBus with a distributed implementation.
This is useful when:
- Queries must be delegated to external services or subsystems
- You want to isolate bounded contexts with strict read separation
- You’re integrating with legacy systems or public APIs