Best Practices
These recommendations help keep executors readable, testable, and well‑scoped.
1. One executor = one operation
If an executor starts to handle multiple unrelated tasks, split it.
A single responsibility makes the contract obvious and the handler easy to test.
// ✅ clear purpose
export class ValidateDiscountExecutor extends PostboyExecutor<boolean> {
static readonly ID = 'discount.validate';
constructor(public readonly code: string) {
super();
}
}
2. Small, explicit contract
Pass only the data needed to perform the operation.
Avoid adding flags or state that belong elsewhere.
export class RefreshCacheExecutor extends PostboyExecutor<boolean> {
static readonly ID = 'cache.refresh';
constructor(public readonly scope: string) {
super();
}
}
3. Choose the right registration model
Use direct function for thin wrappers.
postboy.exec(new ConnectExecutor(FormatDateExecutor, (executor) => {
return Intl.DateTimeFormat('en-US').format(executor.date);
}));
Use a handler class when the logic grows beyond a few lines.
class RefreshCacheHandler extends PostboyExecutionHandler<boolean, RefreshCacheExecutor> {
handle(executor: RefreshCacheExecutor): boolean {
// complex multi‑step logic
return cacheService.refresh(executor.scope);
}
}
postboy.exec(new ConnectHandler(RefreshCacheExecutor, new RefreshCacheHandler()));
4. Keep the result type meaningful
Prefer explicit result shapes over ambiguous values.
// ✅ caller knows what happened
type RefreshResult = { success: true } | { success: false; reason: string };
export class RefreshCacheExecutor extends PostboyExecutor<RefreshResult> {
static readonly ID = 'cache.refresh';
constructor(public readonly scope: string) {
super();
}
}
5. Do not use executors for domain events
If the operation is a notification that something happened, use a PostboyGenericMessage, not an executor.
// ✅ event — no result expected
export class OrderShippedEvent extends PostboyGenericMessage {
static readonly ID = 'order.shipped';
constructor(public readonly orderId: string) {
super();
}
}
6. Keep errors in application code
Postboy does not own your errors. If the handler can fail, either throw (fast‑fail) or return an error‑shaped result.
try {
const valid = postboy.exec(new ValidateDiscountExecutor(''));
} catch (e) {
console.error(e.message);
}
7. Separate domain executors from infrastructure executors
Infrastructure executors (ConnectMessage, ConnectExecutor, ConnectHandler) wire the bus.
Domain executors carry business operations. Keep this distinction visible in your code.
messages/
executors/
product-to-view-model.executor.ts ← domain
cache-refresh.executor.ts ← domain
bus/
registration.ts ← infrastructure executors called here
8. Test handler logic in isolation
Executor handlers are pure functions — test them without the bus.
test('formats a date', () => {
const handler = new FormatDateHandler();
const result = handler.handle(new FormatDateExecutor(new Date(2026, 3, 23)));
expect(result).toBe('4/23/2026');
});
9. Summary
Keep executors small and focused.
Use the simplest registration model that fits the complexity.
Make results explicit.
Test handlers directly.
Let Postboy coordinate; let your application own the logic.
03 мая 2026