diff --git a/packages/adapter-tcp/src/tcp.server.test.ts b/packages/adapter-tcp/src/tcp.server.test.ts index 3bde6fb..5ef401c 100644 --- a/packages/adapter-tcp/src/tcp.server.test.ts +++ b/packages/adapter-tcp/src/tcp.server.test.ts @@ -1,11 +1,11 @@ import { imock, instance } from '@johanblumenberg/ts-mockito'; import { TCPServer } from './tcp.server'; -import type { Commands, ConnectionRepository, DeviceRepository } from '@agnoc/domain'; +import type { CommandsOrQueries, ConnectionRepository, DeviceRepository } from '@agnoc/domain'; import type { EventHandlerRegistry, TaskHandlerRegistry } from '@agnoc/toolkit'; describe('TCPServer', function () { let domainEventHandlerRegistry: EventHandlerRegistry; - let commandHandlerRegistry: TaskHandlerRegistry; + let commandHandlerRegistry: TaskHandlerRegistry; let deviceRepository: DeviceRepository; let connectionRepository: ConnectionRepository; let tcpAdapter: TCPServer; diff --git a/packages/adapter-tcp/src/tcp.server.ts b/packages/adapter-tcp/src/tcp.server.ts index d28885c..faafdbb 100644 --- a/packages/adapter-tcp/src/tcp.server.ts +++ b/packages/adapter-tcp/src/tcp.server.ts @@ -43,7 +43,7 @@ import { DeviceVersionUpdateEventHandler } from './packet-event-handlers/device- import { DeviceWlanUpdateEventHandler } from './packet-event-handlers/device-wlan-update.event-handler'; import { PackerServerConnectionHandler } from './packet-server.connection-handler'; import { PacketEventBus } from './packet.event-bus'; -import type { Commands, ConnectionRepository, DeviceRepository } from '@agnoc/domain'; +import type { CommandsOrQueries, ConnectionRepository, DeviceRepository } from '@agnoc/domain'; import type { Server, TaskHandlerRegistry } from '@agnoc/toolkit'; import type { AddressInfo } from 'net'; @@ -56,7 +56,7 @@ export class TCPServer implements Server { private readonly deviceRepository: DeviceRepository, private readonly connectionRepository: ConnectionRepository, private readonly domainEventHandlerRegistry: EventHandlerRegistry, - private readonly commandHandlerRegistry: TaskHandlerRegistry, + private readonly commandQueryHandlerRegistry: TaskHandlerRegistry, ) { // Packet foundation const payloadMapper = new PayloadMapper(new PayloadObjectParserService(getProtobufRoot(), getCustomDecoders())); @@ -142,7 +142,7 @@ export class TCPServer implements Server { ); // Command event handlers - this.commandHandlerRegistry.register(new LocateDeviceEventHandler(connectionRepository)); + this.commandQueryHandlerRegistry.register(new LocateDeviceEventHandler(connectionRepository)); } async listen(options: TCPAdapterListenOptions = listenDefaultOptions): Promise { diff --git a/packages/adapter-tcp/test/integration/tcp.server.test.ts b/packages/adapter-tcp/test/integration/tcp.server.test.ts index fc9bc7c..8e397c9 100644 --- a/packages/adapter-tcp/test/integration/tcp.server.test.ts +++ b/packages/adapter-tcp/test/integration/tcp.server.test.ts @@ -1,6 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies */ import { once } from 'events'; -import { CommandBus, ConnectionRepository, Device, DeviceRepository, DomainEventBus } from '@agnoc/domain'; +import { CommandQueryBus, ConnectionRepository, Device, DeviceRepository, DomainEventBus } from '@agnoc/domain'; import { givenSomeDeviceProps } from '@agnoc/domain/test-support'; import { EventHandlerRegistry, ID, MemoryAdapter, TaskHandlerRegistry } from '@agnoc/toolkit'; import { @@ -15,15 +15,15 @@ import { import { expect } from 'chai'; import { TCPServer } from '@agnoc/adapter-tcp'; import type { TCPAdapterListenOptions } from '@agnoc/adapter-tcp'; -import type { Commands } from '@agnoc/domain'; +import type { CommandsOrQueries } from '@agnoc/domain'; import type { ICLIENT_ONLINE_REQ, IDEVICE_REGISTER_REQ } from '@agnoc/schemas-tcp'; import type { CreatePacketProps, Packet } from '@agnoc/transport-tcp'; describe('Integration', function () { let domainEventBus: DomainEventBus; - let commandBus: CommandBus; + let commandBus: CommandQueryBus; let domainEventHandlerRegistry: EventHandlerRegistry; - let commandHandlerRegistry: TaskHandlerRegistry; + let commandHandlerRegistry: TaskHandlerRegistry; let deviceRepository: DeviceRepository; let connectionRepository: ConnectionRepository; let tcpAdapter: TCPServer; @@ -34,7 +34,7 @@ describe('Integration', function () { beforeEach(function () { // Server blocks domainEventBus = new DomainEventBus(); - commandBus = new CommandBus(); + commandBus = new CommandQueryBus(); domainEventHandlerRegistry = new EventHandlerRegistry(domainEventBus); commandHandlerRegistry = new TaskHandlerRegistry(commandBus); diff --git a/packages/core/src/agnoc.server.test.ts b/packages/core/src/agnoc.server.test.ts index a1946b7..f3b31d1 100644 --- a/packages/core/src/agnoc.server.test.ts +++ b/packages/core/src/agnoc.server.test.ts @@ -21,7 +21,7 @@ describe('AgnocServer', function () { expect(container.deviceRepository).to.be.instanceOf(DeviceRepository); expect(container.connectionRepository).to.be.instanceOf(ConnectionRepository); expect(container.domainEventHandlerRegistry).to.be.instanceOf(EventHandlerRegistry); - expect(container.commandHandlerRegistry).to.be.instanceOf(TaskHandlerRegistry); + expect(container.commandQueryHandlerRegistry).to.be.instanceOf(TaskHandlerRegistry); return instance(server); }); @@ -78,7 +78,7 @@ describe('AgnocServer', function () { when(taskHandler.forName).thenReturn('LocateDeviceCommand'); - agnocServer.buildAdapter(({ commandHandlerRegistry }) => { + agnocServer.buildAdapter(({ commandQueryHandlerRegistry: commandHandlerRegistry }) => { commandHandlerRegistry.register(instance(taskHandler)); return instance(server); diff --git a/packages/core/src/agnoc.server.ts b/packages/core/src/agnoc.server.ts index 16759f9..a7653c0 100644 --- a/packages/core/src/agnoc.server.ts +++ b/packages/core/src/agnoc.server.ts @@ -1,13 +1,14 @@ -import { CommandBus, ConnectionRepository, DeviceRepository, DomainEventBus } from '@agnoc/domain'; +import { CommandQueryBus, ConnectionRepository, DeviceRepository, DomainEventBus } from '@agnoc/domain'; import { EventHandlerRegistry, MemoryAdapter, TaskHandlerRegistry } from '@agnoc/toolkit'; -import type { DomainEventNames, DomainEvents, Commands } from '@agnoc/domain'; +import { FindDeviceQueryHandler } from './query-handlers/find-device.query-handler'; +import type { DomainEventNames, DomainEvents, CommandsOrQueries } from '@agnoc/domain'; import type { Server, TaskOutput } from '@agnoc/toolkit'; export class AgnocServer implements Server { private readonly domainEventBus: DomainEventBus; private readonly domainEventHandlerRegistry: EventHandlerRegistry; - private readonly commandBus: CommandBus; - private readonly commandHandlerRegistry: TaskHandlerRegistry; + private readonly commandQueryBus: CommandQueryBus; + private readonly commandQueryHandlerRegistry: TaskHandlerRegistry; private readonly deviceRepository: DeviceRepository; private readonly connectionRepository: ConnectionRepository; private readonly adapters = new Set(); @@ -15,24 +16,27 @@ export class AgnocServer implements Server { constructor() { this.domainEventBus = new DomainEventBus(); this.domainEventHandlerRegistry = new EventHandlerRegistry(this.domainEventBus); - this.commandBus = new CommandBus(); - this.commandHandlerRegistry = new TaskHandlerRegistry(this.commandBus); + this.commandQueryBus = new CommandQueryBus(); + this.commandQueryHandlerRegistry = new TaskHandlerRegistry(this.commandQueryBus); this.deviceRepository = new DeviceRepository(this.domainEventBus, new MemoryAdapter()); this.connectionRepository = new ConnectionRepository(this.domainEventBus, new MemoryAdapter()); + this.registerHandlers(); } subscribe(eventName: Name, handler: SubscribeHandler): void { this.domainEventBus.on(eventName, handler); } - trigger(command: Command): Promise> { - return this.commandBus.trigger(command); + trigger( + command: CommandOrQuery, + ): Promise> { + return this.commandQueryBus.trigger(command); } buildAdapter(builder: AdapterFactory): void { const adapter = builder({ domainEventHandlerRegistry: this.domainEventHandlerRegistry, - commandHandlerRegistry: this.commandHandlerRegistry, + commandQueryHandlerRegistry: this.commandQueryHandlerRegistry, deviceRepository: this.deviceRepository, connectionRepository: this.connectionRepository, }); @@ -47,6 +51,10 @@ export class AgnocServer implements Server { async close(): Promise { await Promise.all([...this.adapters].map((adapter) => adapter.close())); } + + private registerHandlers(): void { + this.commandQueryHandlerRegistry.register(new FindDeviceQueryHandler(this.deviceRepository)); + } } export interface AgnocServerListenOptions { @@ -57,7 +65,7 @@ type AdapterFactory = (container: Container) => Server; export type Container = { domainEventHandlerRegistry: EventHandlerRegistry; - commandHandlerRegistry: TaskHandlerRegistry; + commandQueryHandlerRegistry: TaskHandlerRegistry; deviceRepository: DeviceRepository; connectionRepository: ConnectionRepository; }; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index cb67867..3fa0af5 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1 +1,2 @@ export * from './agnoc.server'; +export * from './query-handlers/find-device.query-handler'; diff --git a/packages/core/src/query-handlers/find-device.query-handler.ts b/packages/core/src/query-handlers/find-device.query-handler.ts new file mode 100644 index 0000000..34dfd80 --- /dev/null +++ b/packages/core/src/query-handlers/find-device.query-handler.ts @@ -0,0 +1,18 @@ +import { DomainException } from '@agnoc/toolkit'; +import type { DeviceRepository, FindDeviceQuery, FindDeviceQueryOutput, QueryHandler } from '@agnoc/domain'; + +export class FindDeviceQueryHandler implements QueryHandler { + readonly forName = 'FindDeviceQuery'; + + constructor(private readonly deviceRepository: DeviceRepository) {} + + async handle(event: FindDeviceQuery): Promise { + const device = await this.deviceRepository.findOneById(event.deviceId); + + if (!device) { + throw new DomainException(`Unable to find a device with id ${event.deviceId.value}`); + } + + return { device }; + } +} diff --git a/packages/domain/src/event-buses/command.event-bus.test.ts b/packages/domain/src/event-buses/command-query.task-bus.test.ts similarity index 64% rename from packages/domain/src/event-buses/command.event-bus.test.ts rename to packages/domain/src/event-buses/command-query.task-bus.test.ts index b4a47fd..c73eaf3 100644 --- a/packages/domain/src/event-buses/command.event-bus.test.ts +++ b/packages/domain/src/event-buses/command-query.task-bus.test.ts @@ -1,12 +1,12 @@ import { TaskBus } from '@agnoc/toolkit'; import { expect } from 'chai'; -import { CommandBus } from './command.event-bus'; +import { CommandQueryBus } from './command-query.task-bus'; describe('CommandBus', function () { - let commandBus: CommandBus; + let commandBus: CommandQueryBus; beforeEach(function () { - commandBus = new CommandBus(); + commandBus = new CommandQueryBus(); }); it('should be created', function () { diff --git a/packages/domain/src/event-buses/command-query.task-bus.ts b/packages/domain/src/event-buses/command-query.task-bus.ts new file mode 100644 index 0000000..235c613 --- /dev/null +++ b/packages/domain/src/event-buses/command-query.task-bus.ts @@ -0,0 +1,8 @@ +import { TaskBus } from '@agnoc/toolkit'; +import type { Commands } from '../commands/commands'; +import type { Queries } from '../queries/queries'; + +export type CommandsOrQueries = Commands & Queries; +export type CommandOrQueryNames = keyof CommandsOrQueries; + +export class CommandQueryBus extends TaskBus {} diff --git a/packages/domain/src/event-buses/command.event-bus.ts b/packages/domain/src/event-buses/command.event-bus.ts deleted file mode 100644 index 8419aae..0000000 --- a/packages/domain/src/event-buses/command.event-bus.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { TaskBus } from '@agnoc/toolkit'; -import type { Commands } from '../commands/commands'; - -export class CommandBus extends TaskBus {} diff --git a/packages/domain/src/event-handlers/command.event-handler.ts b/packages/domain/src/event-handlers/command.task-handler.ts similarity index 100% rename from packages/domain/src/event-handlers/command.event-handler.ts rename to packages/domain/src/event-handlers/command.task-handler.ts diff --git a/packages/domain/src/event-handlers/query.task-handler.ts b/packages/domain/src/event-handlers/query.task-handler.ts new file mode 100644 index 0000000..d306da8 --- /dev/null +++ b/packages/domain/src/event-handlers/query.task-handler.ts @@ -0,0 +1,7 @@ +import type { Queries, QueryNames } from '../queries/queries'; +import type { TaskHandler } from '@agnoc/toolkit'; + +export abstract class QueryHandler implements TaskHandler { + abstract forName: QueryNames; + abstract handle(event: Queries[this['forName']]): void; +} diff --git a/packages/domain/src/index.ts b/packages/domain/src/index.ts index 5b5d162..8ef919c 100644 --- a/packages/domain/src/index.ts +++ b/packages/domain/src/index.ts @@ -19,9 +19,9 @@ export * from './entities/device-map.entity'; export * from './entities/device-order.entity'; export * from './entities/room.entity'; export * from './entities/zone.entity'; -export * from './event-buses/command.event-bus'; +export * from './event-buses/command-query.task-bus'; export * from './event-buses/domain.event-bus'; -export * from './event-handlers/command.event-handler'; +export * from './event-handlers/command.task-handler'; export * from './event-handlers/domain.event-handler'; export * from './repositories/connection.repository'; export * from './repositories/device.repository'; @@ -38,3 +38,6 @@ export * from './value-objects/map-pixel.value-object'; export * from './value-objects/map-position.value-object'; export * from './value-objects/quiet-hours-setting.value-object'; export * from './value-objects/voice-setting.value-object'; +export * from './queries/find-device.query'; +export * from './queries/queries'; +export * from './event-handlers/query.task-handler'; diff --git a/packages/domain/src/queries/find-device.query.ts b/packages/domain/src/queries/find-device.query.ts new file mode 100644 index 0000000..1b49261 --- /dev/null +++ b/packages/domain/src/queries/find-device.query.ts @@ -0,0 +1,26 @@ +import { ID, Query } from '@agnoc/toolkit'; +import { Device } from '../aggregate-roots/device.aggregate-root'; + +export interface FindDeviceQueryInput { + deviceId: ID; +} + +export interface FindDeviceQueryOutput { + device: Device; +} + +export class FindDeviceQuery extends Query { + get deviceId(): ID { + return this.props.deviceId; + } + + protected validate(props: FindDeviceQueryInput): void { + this.validateDefinedProp(props, 'deviceId'); + this.validateInstanceProp(props, 'deviceId', ID); + } + + override validateOutput(output: FindDeviceQueryOutput): void { + this.validateDefinedProp(output, 'device'); + this.validateInstanceProp(output, 'device', Device); + } +} diff --git a/packages/domain/src/queries/queries.ts b/packages/domain/src/queries/queries.ts new file mode 100644 index 0000000..65bf140 --- /dev/null +++ b/packages/domain/src/queries/queries.ts @@ -0,0 +1,7 @@ +import type { FindDeviceQuery } from './find-device.query'; + +export type Queries = { + FindDeviceQuery: FindDeviceQuery; +}; + +export type QueryNames = keyof Queries;