Domain Event Patterns
Overview
Switchain Services uses an event-driven architecture for cross-boundary communication. When something significant happens in one bounded context, it publishes a Domain Event. Other contexts subscribe to these events via Event Subscribers.
Domain Events
Structure
Extend DomainEvent from @sws/common/domain. Include a static eventName using the format <context>.<subjectAction>:
typescript
// packages/cex-wallets/domain/events/CoinBlockchainCreatedDomainEvent.ts
import { DomainEvent } from '@sws/common/domain'
export class CoinBlockchainCreatedDomainEvent extends DomainEvent {
static eventName = 'cex-wallets.coinBlockchainCreated'
constructor(
public readonly coinId: string,
public readonly blockchain: string,
occurredOn?: Date
) {
super(CoinBlockchainCreatedDomainEvent.eventName, occurredOn)
}
}Location
packages/<context>/domain/events/[Subject][Action]DomainEvent.tsNaming
- Class:
[Subject][Action]DomainEvent(e.g.,CoinBlockchainCreatedDomainEvent) eventName:'<context>.<subjectAction>'(e.g.,'cex-wallets.coinBlockchainCreated')
Publishing Events
Application services publish events after completing a use case:
typescript
import { CommonFactory } from '@sws/common'
import { CoinBlockchainCreatedDomainEvent } from '../../domain/events/CoinBlockchainCreatedDomainEvent'
export class CoinBlockchainCreator {
constructor(
private readonly repository: CoinBlockchainRepository,
private readonly eventPublisher = CommonFactory.eventPublisher()
) {}
async run({ coinId, blockchain }: { coinId: string; blockchain: string }): Promise<void> {
const entity = CoinBlockchain.create({ coinId, blockchain })
await this.repository.save(entity)
await this.eventPublisher.publish(
new CoinBlockchainCreatedDomainEvent(coinId, blockchain)
)
}
}Event Subscribers
Structure
Implement DomainEventSubscriber<T> from @sws/common/domain:
typescript
// packages/cex-wallets/application/eventSubscribers/UpdateCexCoinsBlockchainsOnCoinBlockchainCreated.ts
import type { DomainEventSubscriber } from '@sws/common/domain'
import { LoggerFactory } from '@sws/common/logger'
import { CoinBlockchainCreatedDomainEvent } from '../../domain/events/CoinBlockchainCreatedDomainEvent'
export class UpdateCexCoinsBlockchainsOnCoinBlockchainCreated
implements DomainEventSubscriber<CoinBlockchainCreatedDomainEvent>
{
private logger = LoggerFactory.logger()
subscribedTo() {
return [CoinBlockchainCreatedDomainEvent]
}
async on(event: CoinBlockchainCreatedDomainEvent): Promise<void> {
this.logger.info(
`UpdateCexCoinsBlockchainsOnCoinBlockchainCreated coinId:${event.coinId}, blockchain:${event.blockchain}`
)
try {
// React to the event — call application services or controllers
await CexCoinBlockchainControllers.update.run('binance', event.coinId, event.blockchain)
} catch (error) {
this.logger.error(`Error handling CoinBlockchainCreatedDomainEvent`, {
error: error instanceof Error ? error.message : String(error)
})
}
}
}Location
packages/<context>/application/eventSubscribers/[Action]On[Event].tsNaming
- Class:
[Action]On[Event](e.g.,UpdateCexCoinsBlockchainsOnCoinBlockchainCreated) - Always wrap the handler body in try/catch to avoid crashing the subscriber queue
Registering Subscribers
Subscribers are registered in the consuming app's EventsFactory.ts:
typescript
// apps/common-worker/infrastructure/factories/EventsFactory.ts
import { UpdateCexCoinsBlockchainsOnCoinBlockchainCreated } from '@sws/cex-wallets'
import { DeactivatePairsOnCoinBlockchainDeleted } from '@sws/rates'
import { CommonFactory } from '@sws/common'
import type { DomainEvent, DomainEventSubscriber } from '@sws/common/domain'
import { LoggerFactory } from '@sws/common/logger'
export class EventsFactory {
static async initialize(): Promise<DomainEventSubscriber<DomainEvent>[]> {
const logger = LoggerFactory.logger()
logger.info('EventsFactory.initialize starting...')
const eventSubscriber = CommonFactory.eventSubscriber()
const subscribers = EventsFactory.createSubscribers()
await eventSubscriber.addSubscribers(subscribers)
logger.info('Events initialized', {
subscribers: subscribers.map((s) => s.constructor.name).join(', ')
})
return subscribers
}
private static createSubscribers(): DomainEventSubscriber<DomainEvent>[] {
return [
new UpdateCexCoinsBlockchainsOnCoinBlockchainCreated(),
new DeactivatePairsOnCoinBlockchainDeleted()
// Add new subscribers here
]
}
}Event Flow Example
CoinBlockchain created in admin UI
└─> CoinBlockchainCreator.run()
└─> eventPublisher.publish(CoinBlockchainCreatedDomainEvent)
├─> UpdateCexCoinsBlockchainsOnCoinBlockchainCreated (cex-wallets package)
│ └─> Updates CEX coin blockchains
└─> DeactivatePairsOnCoinBlockchainDeleted (rates package)
└─> Regenerates trading pairsEvents in This Codebase
| Event | Published By | Consumed By |
|---|---|---|
CoinBlockchainCreatedDomainEvent | cex-wallets | cex-wallets (UpdateCexCoinsBlockchains), rates |
CoinBlockchainDeletedDomainEvent | cex-wallets | rates (DeactivatePairs), cex-wallets |
CexAccountBannedDomainEvent | cex-wallets | alerts |
ShiftTradesCompletedDomainEvent | shifts | alerts |
