diff --git a/apps/server/src/logging/contracts/payloadByScope.ts b/apps/server/src/logging/contracts/payloadByScope.ts index 598845b..63b10ac 100644 --- a/apps/server/src/logging/contracts/payloadByScope.ts +++ b/apps/server/src/logging/contracts/payloadByScope.ts @@ -27,7 +27,8 @@ result: | typeof logResults.REJECTED_ROOM_FULL | typeof logResults.REJECTED_DUPLICATE - | typeof logResults.IGNORED_INVALID_PAYLOAD; + | typeof logResults.IGNORED_INVALID_PAYLOAD + | typeof logResults.IGNORED_MISSING_ROOM; socketId: string; roomId?: string; }; @@ -42,21 +43,27 @@ /** NetworkのMOVE不正ペイロードログ契約 */ type NetworkMoveLogPayload = { event: typeof protocol.SocketEvents.MOVE; - result: typeof logResults.IGNORED_INVALID_PAYLOAD; + result: + | typeof logResults.IGNORED_INVALID_PAYLOAD + | typeof logResults.IGNORED_MISSING_ROOM; socketId: string; }; /** NetworkのPLACE_BOMB不正ペイロードログ契約 */ type NetworkPlaceBombLogPayload = { event: typeof protocol.SocketEvents.PLACE_BOMB; - result: typeof logResults.IGNORED_INVALID_PAYLOAD; + result: + | typeof logResults.IGNORED_INVALID_PAYLOAD + | typeof logResults.IGNORED_MISSING_ROOM; socketId: string; }; /** NetworkのBOMB_HIT_REPORT不正ペイロードログ契約 */ type NetworkBombHitReportLogPayload = { event: typeof protocol.SocketEvents.BOMB_HIT_REPORT; - result: typeof logResults.IGNORED_INVALID_PAYLOAD; + result: + | typeof logResults.IGNORED_INVALID_PAYLOAD + | typeof logResults.IGNORED_MISSING_ROOM; socketId: string; }; diff --git a/apps/server/src/network/SocketManager.ts b/apps/server/src/network/SocketManager.ts index 2498bdf..65dec7a 100644 --- a/apps/server/src/network/SocketManager.ts +++ b/apps/server/src/network/SocketManager.ts @@ -8,7 +8,7 @@ SocketConnectionRoomPort, SocketConnectionRuntimePort, } from "./types/connectionPorts"; -import { registerConnectionHandlers } from "./handlers/registerConnectionHandlers"; +import { registerConnectionHandlers } from "./handlers"; /** Socket.IOの接続ハンドラ登録を統括する */ export class SocketManager { diff --git a/apps/server/src/network/handlers/GameHandler.ts b/apps/server/src/network/handlers/GameHandler.ts deleted file mode 100644 index f317903..0000000 --- a/apps/server/src/network/handlers/GameHandler.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * GameHandler - * ゲーム関連ハンドラの公開窓口を提供する再エクスポートファイル - * ネットワーク層の import 経路を統一する - */ -/** ゲームイベント受信ハンドラ登録関数を外部参照向けに再公開 */ -export { registerGameHandlers } from "./game/registerGameHandlers"; diff --git a/apps/server/src/network/handlers/RoomHandler.ts b/apps/server/src/network/handlers/RoomHandler.ts deleted file mode 100644 index d3d8254..0000000 --- a/apps/server/src/network/handlers/RoomHandler.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * RoomHandler - * ルーム関連ハンドラの公開窓口を提供する再エクスポートファイル - * ネットワーク層の import 経路を統一する - */ -/** ルームイベント受信ハンドラ登録関数を外部参照向けに再公開 */ -export { registerRoomHandlers } from "./room/registerRoomHandlers"; diff --git a/apps/server/src/network/handlers/eventDefinitionRegistrar.ts b/apps/server/src/network/handlers/eventDefinitionRegistrar.ts new file mode 100644 index 0000000..4ad728c --- /dev/null +++ b/apps/server/src/network/handlers/eventDefinitionRegistrar.ts @@ -0,0 +1,88 @@ +/** + * eventDefinitionRegistrar + * 宣言的イベント定義の登録処理を共通化する + */ + +/** 受信payloadハンドラをバイバリアントに扱うための型 */ +type BivariantPayloadHandler = { + bivarianceHack: (payload: TPayload) => void | Promise; +}["bivarianceHack"]; + +/** 検証付きイベント定義 */ +export type GuardedEventDefinition = { + event: TEvent; + validator: (value: unknown) => value is TPayload; + orchestrate: BivariantPayloadHandler; +}; + +/** 自前検証イベント定義 */ +export type SelfValidatedEventDefinition = { + event: TEvent; + validator: (value: unknown) => value is TPayload; + orchestrate: BivariantPayloadHandler; +}; + +/** 検証不要イベント定義 */ +export type UnguardedEventDefinition = { + event: TEvent; + orchestrate: () => void | Promise; +}; + +/** 汎用イベント定義 */ +export type EventDefinition = { + event: TEvent; + orchestrate: (payload: TPayload) => void | Promise; +}; + +/** 検証付きイベント定義を登録する */ +export const registerGuardedEvent = ( + subscribe: (event: TEvent, callback: (payload: unknown) => void) => void, + createGuard: ( + event: TEvent, + validator: (value: unknown) => value is TPayload, + ) => (payload: unknown) => payload is TPayload, + definition: GuardedEventDefinition, +): void => { + const guard = createGuard(definition.event, definition.validator); + subscribe(definition.event, (payload) => { + if (!guard(payload)) { + return; + } + + void definition.orchestrate(payload); + }); +}; + +/** 自前検証イベント定義を登録する */ +export const registerSelfValidatedEvent = ( + subscribe: (event: TEvent, callback: (payload: unknown) => void) => void, + definition: SelfValidatedEventDefinition, +): void => { + subscribe(definition.event, (payload) => { + if (!definition.validator(payload)) { + return; + } + + void definition.orchestrate(payload); + }); +}; + +/** 検証不要イベント定義を登録する */ +export const registerUnguardedEvent = ( + subscribe: (event: TEvent, callback: () => void) => void, + definition: UnguardedEventDefinition, +): void => { + subscribe(definition.event, () => { + void definition.orchestrate(); + }); +}; + +/** 汎用イベント定義を登録する */ +export const registerEvent = ( + subscribe: (event: TEvent, callback: (payload: TPayload) => void) => void, + definition: EventDefinition, +): void => { + subscribe(definition.event, (payload) => { + void definition.orchestrate(payload); + }); +}; diff --git a/apps/server/src/network/handlers/game/gameEventOrchestrators.ts b/apps/server/src/network/handlers/game/gameEventOrchestrators.ts index 33d0ee9..a522685 100644 --- a/apps/server/src/network/handlers/game/gameEventOrchestrators.ts +++ b/apps/server/src/network/handlers/game/gameEventOrchestrators.ts @@ -2,6 +2,7 @@ * gameEventOrchestrators * ゲーム受信イベントごとの調停処理を提供する * 受信ハンドラからユースケース実行責務を分離する + * ランタイム未解決時はNetworkスコープでignored_missing_roomを記録する */ import { protocol, type BombHitReportPayload, type PingPayload, type PlaceBombPayload, type playerTypes } from "@repo/shared"; import { readyForGameCoordinator } from "@server/application/coordinators/readyForGameCoordinator"; @@ -11,10 +12,11 @@ import { placeBombUseCase } from "@server/domains/game/application/useCases/placeBombUseCase"; import { reportBombHitUseCase } from "@server/domains/game/application/useCases/reportBombHitUseCase"; import { runWithRuntimeByPlayerId } from "@server/domains/room/application/services/RoomRuntimeResolver"; +import { logIgnoredMissingRoom } from "../orchestratorEventLogger"; import type { GameOutputAdapter } from "./createGameOutputAdapter"; import type { - GameHandlerRoomPort, - GameHandlerRuntimePort, + GameEventRoomUseCasePort, + GameEventRuntimeUseCasePort, } from "@server/network/types/connectionPorts"; /** START_GAMEイベントの入力ペイロード型 */ @@ -22,11 +24,23 @@ targetPlayerCount?: number; }; +/** PINGイベントの入力ペイロード型 */ +export type PingEventPayload = Parameters[1]; + +/** MOVEイベントの入力ペイロード型 */ +export type MoveEventPayload = Parameters[1]; + +/** PLACE_BOMBイベントの入力ペイロード型 */ +export type PlaceBombEventPayload = Parameters[1]; + +/** BOMB_HIT_REPORTイベントの入力ペイロード型 */ +export type BombHitReportEventPayload = Parameters[1]; + /** ゲームイベント調停で利用する依存集合 */ export type GameEventOrchestratorDeps = { socketId: string; - roomManager: GameHandlerRoomPort; - runtimeRegistry: GameHandlerRuntimePort; + roomManager: GameEventRoomUseCasePort; + runtimeRegistry: GameEventRuntimeUseCasePort; output: GameOutputAdapter; }; @@ -72,7 +86,7 @@ deps: GameEventOrchestratorDeps, move: playerTypes.MovePayload, ): void => { - runWithRuntimeByPlayerId( + const resolved = runWithRuntimeByPlayerId( deps.roomManager, deps.runtimeRegistry, deps.socketId, @@ -84,6 +98,9 @@ }); }, ); + if (!resolved) { + logIgnoredMissingRoom(protocol.SocketEvents.MOVE, deps.socketId); + } }; /** PLACE_BOMBイベントを調停して爆弾設置ユースケースを実行する */ @@ -91,7 +108,7 @@ deps: GameEventOrchestratorDeps, payload: PlaceBombPayload, ): void => { - runWithRuntimeByPlayerId( + const resolved = runWithRuntimeByPlayerId( deps.roomManager, deps.runtimeRegistry, deps.socketId, @@ -108,6 +125,9 @@ }); }, ); + if (!resolved) { + logIgnoredMissingRoom(protocol.SocketEvents.PLACE_BOMB, deps.socketId); + } }; /** BOMB_HIT_REPORTイベントを調停して被弾報告ユースケースを実行する */ @@ -115,7 +135,7 @@ deps: GameEventOrchestratorDeps, payload: BombHitReportPayload, ): void => { - runWithRuntimeByPlayerId( + const resolved = runWithRuntimeByPlayerId( deps.roomManager, deps.runtimeRegistry, deps.socketId, @@ -132,4 +152,7 @@ }); }, ); + if (!resolved) { + logIgnoredMissingRoom(protocol.SocketEvents.BOMB_HIT_REPORT, deps.socketId); + } }; diff --git a/apps/server/src/network/handlers/game/registerGameHandlers.ts b/apps/server/src/network/handlers/game/registerGameHandlers.ts index 8bab813..22b807c 100644 --- a/apps/server/src/network/handlers/game/registerGameHandlers.ts +++ b/apps/server/src/network/handlers/game/registerGameHandlers.ts @@ -5,8 +5,8 @@ import { Socket } from "socket.io"; import { protocol } from "@repo/shared"; import type { - GameHandlerRoomPort, - GameHandlerRuntimePort, + GameEventRoomUseCasePort, + GameEventRuntimeUseCasePort, } from "@server/network/types/connectionPorts"; import { isBombHitReportPayload, @@ -15,17 +15,44 @@ isPlaceBombPayload, isStartGamePayload, } from "@server/network/validation/socketPayloadValidators"; -import { createServerSocketOnBridge } from "@server/network/handlers/socketEventBridge"; -import { createPayloadGuard } from "@server/network/handlers/payloadGuard"; +import { createSocketRegistrationContext } from "@server/network/handlers/registration"; import type { GameOutputAdapter } from "./createGameOutputAdapter"; import { + type BombHitReportEventPayload, + type MoveEventPayload, + type PingEventPayload, + type PlaceBombEventPayload, handleBombHitReportEvent, + type GameEventOrchestratorDeps, handleMoveEvent, handlePingEvent, handlePlaceBombEvent, handleReadyForGameEvent, + type StartGamePayload, handleStartGameEvent, } from "./gameEventOrchestrators"; +import { + registerGuardedEvent, + registerSelfValidatedEvent, + registerUnguardedEvent, + type GuardedEventDefinition, + type SelfValidatedEventDefinition, + type UnguardedEventDefinition, +} from "@server/network/handlers/eventDefinitionRegistrar"; + +type PingEventDefinition = GuardedEventDefinition; + +type MoveEventDefinition = GuardedEventDefinition; + +type PlaceBombEventDefinition = GuardedEventDefinition; + +type BombHitReportEventDefinition = GuardedEventDefinition; + +type StartGameEventDefinition = SelfValidatedEventDefinition; + +type ReadyForGameEventDefinition = UnguardedEventDefinition< + typeof protocol.SocketEvents.READY_FOR_GAME +>; /** ゲーム受信イベントごとの入力検証関数を保持するテーブル */ const gamePayloadValidators = { @@ -35,123 +62,132 @@ [protocol.SocketEvents.BOMB_HIT_REPORT]: isBombHitReportPayload, } as const; +/** ゲームイベント調停で利用する依存束を生成する */ +const createGameOrchestratorDeps = ( + socket: Socket, + roomManager: GameEventRoomUseCasePort, + runtimeRegistry: GameEventRuntimeUseCasePort, + gameOutputAdapter: GameOutputAdapter, +): GameEventOrchestratorDeps => { + return { + socketId: socket.id, + roomManager, + runtimeRegistry, + output: gameOutputAdapter, + }; +}; + +/** PINGイベント定義を生成する */ +const createPingEventDefinition = ( + deps: GameEventOrchestratorDeps, +): PingEventDefinition => { + return { + event: protocol.SocketEvents.PING, + validator: gamePayloadValidators[protocol.SocketEvents.PING], + orchestrate: (payload) => { + handlePingEvent(deps, payload); + }, + }; +}; + +/** MOVEイベント定義を生成する */ +const createMoveEventDefinition = ( + deps: GameEventOrchestratorDeps, +): MoveEventDefinition => { + return { + event: protocol.SocketEvents.MOVE, + validator: gamePayloadValidators[protocol.SocketEvents.MOVE], + orchestrate: (payload) => { + handleMoveEvent(deps, payload); + }, + }; +}; + +/** PLACE_BOMBイベント定義を生成する */ +const createPlaceBombEventDefinition = ( + deps: GameEventOrchestratorDeps, +): PlaceBombEventDefinition => { + return { + event: protocol.SocketEvents.PLACE_BOMB, + validator: gamePayloadValidators[protocol.SocketEvents.PLACE_BOMB], + orchestrate: (payload) => { + handlePlaceBombEvent(deps, payload); + }, + }; +}; + +/** BOMB_HIT_REPORTイベント定義を生成する */ +const createBombHitReportEventDefinition = ( + deps: GameEventOrchestratorDeps, +): BombHitReportEventDefinition => { + return { + event: protocol.SocketEvents.BOMB_HIT_REPORT, + validator: gamePayloadValidators[protocol.SocketEvents.BOMB_HIT_REPORT], + orchestrate: (payload) => { + handleBombHitReportEvent(deps, payload); + }, + }; +}; + +/** START_GAMEイベント定義を生成する */ +const createStartGameEventDefinition = ( + deps: GameEventOrchestratorDeps, +): StartGameEventDefinition => { + return { + event: protocol.SocketEvents.START_GAME, + validator: isStartGamePayload, + orchestrate: (payload) => { + handleStartGameEvent(deps, payload); + }, + }; +}; + +/** READY_FOR_GAMEイベント定義を生成する */ +const createReadyForGameEventDefinition = ( + deps: GameEventOrchestratorDeps, +): ReadyForGameEventDefinition => { + return { + event: protocol.SocketEvents.READY_FOR_GAME, + orchestrate: () => { + handleReadyForGameEvent(deps); + }, + }; +}; + /** ゲームイベントの購読とユースケース呼び出しを設定する */ export const registerGameHandlers = ( socket: Socket, - roomManager: GameHandlerRoomPort, - runtimeRegistry: GameHandlerRuntimePort, + roomManager: GameEventRoomUseCasePort, + runtimeRegistry: GameEventRuntimeUseCasePort, gameOutputAdapter: GameOutputAdapter, ) => { - const { onEvent } = createServerSocketOnBridge(socket); - const { guardOnEvent } = createPayloadGuard(socket.id); - const guardPingPayload = guardOnEvent( - protocol.SocketEvents.PING, - gamePayloadValidators[protocol.SocketEvents.PING], + const orchestratorDeps = createGameOrchestratorDeps( + socket, + roomManager, + runtimeRegistry, + gameOutputAdapter, ); - const guardMovePayload = guardOnEvent( - protocol.SocketEvents.MOVE, - gamePayloadValidators[protocol.SocketEvents.MOVE], - ); - const guardPlaceBombPayload = guardOnEvent( - protocol.SocketEvents.PLACE_BOMB, - gamePayloadValidators[protocol.SocketEvents.PLACE_BOMB], - ); - const guardBombHitReportPayload = guardOnEvent( - protocol.SocketEvents.BOMB_HIT_REPORT, - gamePayloadValidators[protocol.SocketEvents.BOMB_HIT_REPORT], - ); - // 遅延計測用のPINGを検証しPONGを返す - onEvent(protocol.SocketEvents.PING, (clientTime) => { - if (!guardPingPayload(clientTime)) { - return; - } + const { onEvent, guardOnEvent } = createSocketRegistrationContext(socket); - handlePingEvent( - { - socketId: socket.id, - roomManager, - runtimeRegistry, - output: gameOutputAdapter, - }, - clientTime, - ); - }); + // 検証が必要なイベントを宣言的に登録する + const pingEventDefinition = createPingEventDefinition(orchestratorDeps); + const moveEventDefinition = createMoveEventDefinition(orchestratorDeps); + const placeBombEventDefinition = createPlaceBombEventDefinition(orchestratorDeps); + const bombHitReportEventDefinition = createBombHitReportEventDefinition(orchestratorDeps); - // オーナー開始要求に応じてゲーム進行ユースケースを起動する - onEvent(protocol.SocketEvents.START_GAME, (data) => { - if (!isStartGamePayload(data)) { - return; - } + registerGuardedEvent(onEvent, guardOnEvent, pingEventDefinition); + registerGuardedEvent(onEvent, guardOnEvent, moveEventDefinition); + registerGuardedEvent(onEvent, guardOnEvent, placeBombEventDefinition); + registerGuardedEvent(onEvent, guardOnEvent, bombHitReportEventDefinition); - handleStartGameEvent( - { - socketId: socket.id, - roomManager, - runtimeRegistry, - output: gameOutputAdapter, - }, - data, - ); - }); + // payloadGuard対象外だが検証が必要なイベントを宣言的に登録する + const startGameEventDefinition = createStartGameEventDefinition(orchestratorDeps); - // 参加者の準備完了通知を受けて現在状態を返す - onEvent(protocol.SocketEvents.READY_FOR_GAME, () => { - handleReadyForGameEvent({ - socketId: socket.id, - roomManager, - runtimeRegistry, - output: gameOutputAdapter, - }); - }); + registerSelfValidatedEvent(onEvent, startGameEventDefinition); - // 移動入力を検証しプレイヤー移動ユースケースへ連携する - onEvent(protocol.SocketEvents.MOVE, (data) => { - if (!guardMovePayload(data)) { - return; - } + // 検証不要イベントを宣言的に登録する + const readyForGameEventDefinition: ReadyForGameEventDefinition = + createReadyForGameEventDefinition(orchestratorDeps); - handleMoveEvent( - { - socketId: socket.id, - roomManager, - runtimeRegistry, - output: gameOutputAdapter, - }, - data, - ); - }); - - // 爆弾設置入力を検証し,所属ルームへ同期配信する - onEvent(protocol.SocketEvents.PLACE_BOMB, (data) => { - if (!guardPlaceBombPayload(data)) { - return; - } - - handlePlaceBombEvent( - { - socketId: socket.id, - roomManager, - runtimeRegistry, - output: gameOutputAdapter, - }, - data, - ); - }); - - // 被弾報告を受信する - onEvent(protocol.SocketEvents.BOMB_HIT_REPORT, (data) => { - if (!guardBombHitReportPayload(data)) { - return; - } - - handleBombHitReportEvent( - { - socketId: socket.id, - roomManager, - runtimeRegistry, - output: gameOutputAdapter, - }, - data, - ); - }); + registerUnguardedEvent(onEvent, readyForGameEventDefinition); }; diff --git a/apps/server/src/network/handlers/index.ts b/apps/server/src/network/handlers/index.ts new file mode 100644 index 0000000..079d360 --- /dev/null +++ b/apps/server/src/network/handlers/index.ts @@ -0,0 +1,13 @@ +/** + * index + * handlers配下の公開APIを集約して再公開する + */ + +/** 接続イベントの共通ハンドラ登録関数を再公開する */ +export { registerConnectionHandlers } from "./registerConnectionHandlers"; + +/** ゲームイベント受信ハンドラ登録関数を再公開する */ +export { registerGameHandlers } from "./game/registerGameHandlers"; + +/** ルームイベント受信ハンドラ登録関数を再公開する */ +export { registerRoomHandlers } from "./room/registerRoomHandlers"; diff --git a/apps/server/src/network/handlers/orchestratorEventLogger.ts b/apps/server/src/network/handlers/orchestratorEventLogger.ts new file mode 100644 index 0000000..b7decd3 --- /dev/null +++ b/apps/server/src/network/handlers/orchestratorEventLogger.ts @@ -0,0 +1,25 @@ +/** + * orchestratorEventLogger + * オーケストレータ層で利用するイベントログ記録を共通化する + */ +import { protocol } from "@repo/shared"; +import { logEvent } from "@server/logging/logger"; +import { logResults, logScopes } from "@server/logging/index"; + +/** オーケストレータで未解決時に記録するイベント型 */ +export type MissingRoomNetworkEvent = + | typeof protocol.SocketEvents.MOVE + | typeof protocol.SocketEvents.PLACE_BOMB + | typeof protocol.SocketEvents.BOMB_HIT_REPORT; + +/** 未解決のルーム関連イベントをNetworkスコープで記録する */ +export const logIgnoredMissingRoom = ( + event: MissingRoomNetworkEvent, + socketId: string, +): void => { + logEvent(logScopes.NETWORK, { + event, + result: logResults.IGNORED_MISSING_ROOM, + socketId, + }); +}; diff --git a/apps/server/src/network/handlers/registerConnectionHandlers.ts b/apps/server/src/network/handlers/registerConnectionHandlers.ts index 518b5d3..aa4b953 100644 --- a/apps/server/src/network/handlers/registerConnectionHandlers.ts +++ b/apps/server/src/network/handlers/registerConnectionHandlers.ts @@ -5,13 +5,21 @@ import { Server, Socket } from "socket.io"; import { protocol } from "@repo/shared"; import { disconnectCoordinator } from "@server/application/coordinators/disconnectCoordinator"; -import { registerGameHandlers } from "./GameHandler"; -import { registerRoomHandlers } from "./RoomHandler"; +import { registerGameHandlers } from "./game/registerGameHandlers"; +import { registerRoomHandlers } from "./room/registerRoomHandlers"; import { createDisconnectOutputAdapters, - createSocketOutputAdapters, } from "./createOutputAdapters"; import { logConnected, logDisconnected } from "./connectionEventLogger"; +import { + createConnectionRegistrationContext, +} from "./registration"; +import { + registerEvent, + registerUnguardedEvent, + type EventDefinition, + type UnguardedEventDefinition, +} from "./eventDefinitionRegistrar"; import type { RegisterConnectionHandlersParams, } from "../types/connectionPorts"; @@ -24,36 +32,72 @@ }: RegisterConnectionHandlersParams) => { const disconnectOutputAdapters = createDisconnectOutputAdapters(io); - io.on(protocol.SocketEvents.CONNECT, (socket: Socket) => { - const socketOutputAdapters = createSocketOutputAdapters(io, socket); + // CONNECTイベントを宣言的に登録する + const onConnectEventDefinition: EventDefinition< + typeof protocol.SocketEvents.CONNECT, + Socket +> = { + event: protocol.SocketEvents.CONNECT, + orchestrate: (socket) => { + const { deps, socketOutputAdapters } = createConnectionRegistrationContext( + { + io, + roomManager, + runtimeRegistry, + }, + socket, + ); - // 接続ログを記録してドメイン別ハンドラを登録する - logConnected(socket.id); + // 接続ログを記録してドメイン別ハンドラを登録する + logConnected(deps.socket.id); - registerRoomHandlers( - socket, - roomManager, - runtimeRegistry, - socketOutputAdapters.room, - ); - registerGameHandlers( - socket, - roomManager, - runtimeRegistry, - socketOutputAdapters.game, - ); + registerRoomHandlers( + deps.socket, + deps.roomManager, + deps.runtimeRegistry, + socketOutputAdapters.room, + ); + registerGameHandlers( + deps.socket, + deps.roomManager, + deps.runtimeRegistry, + socketOutputAdapters.game, + ); - socket.on(protocol.SocketEvents.DISCONNECT, () => { - // 切断ログ記録後にドメイン別の後処理を実行する - logDisconnected(socket.id); + // ソケット単位イベントを宣言的に登録する + const perSocketEventDefinition: UnguardedEventDefinition< + typeof protocol.SocketEvents.DISCONNECT + > = { + event: protocol.SocketEvents.DISCONNECT, + orchestrate: () => { + // 切断ログ記録後にドメイン別の後処理を実行する + logDisconnected(deps.socket.id); - disconnectCoordinator({ - socketId: socket.id, - roomManager, - runtimeRegistry, - gameOutput: disconnectOutputAdapters.game, - roomOutput: disconnectOutputAdapters.room, + disconnectCoordinator({ + socketId: deps.socket.id, + roomManager: deps.roomManager, + runtimeRegistry: deps.runtimeRegistry, + gameOutput: disconnectOutputAdapters.game, + roomOutput: disconnectOutputAdapters.room, + }); + }, + }; + + registerUnguardedEvent( + (event, callback) => { + deps.socket.on(event, callback); + }, + perSocketEventDefinition, + ); + }, + }; + + registerEvent( + (event, callback) => { + io.on(event, (socket) => { + callback(socket); }); - }); - }); + }, + onConnectEventDefinition, + ); }; diff --git a/apps/server/src/network/handlers/registration/createConnectionRegistrationContext.ts b/apps/server/src/network/handlers/registration/createConnectionRegistrationContext.ts new file mode 100644 index 0000000..2a087a6 --- /dev/null +++ b/apps/server/src/network/handlers/registration/createConnectionRegistrationContext.ts @@ -0,0 +1,40 @@ +/** + * createConnectionRegistrationContext + * 接続イベント登録で利用する依存束と送信アダプタを生成する + */ +import type { Server, Socket } from "socket.io"; +import type { RegisterConnectionHandlersParams } from "../../types/connectionPorts"; +import { + createSocketOutputAdapters, + type SocketOutputAdapters, +} from "../createOutputAdapters"; + +/** 接続イベント調停で利用する依存束 */ +export type ConnectionHandlerDeps = { + io: Server; + socket: Socket; +} & Omit; + +/** 接続イベント登録で利用する共通コンテキスト */ +export type ConnectionRegistrationContext = { + deps: ConnectionHandlerDeps; + socketOutputAdapters: SocketOutputAdapters; +}; + +/** 接続イベント登録で利用する共通コンテキストを生成する */ +export const createConnectionRegistrationContext = ( + params: RegisterConnectionHandlersParams, + socket: Socket, +): ConnectionRegistrationContext => { + const deps: ConnectionHandlerDeps = { + io: params.io, + socket, + roomManager: params.roomManager, + runtimeRegistry: params.runtimeRegistry, + }; + + return { + deps, + socketOutputAdapters: createSocketOutputAdapters(deps.io, deps.socket), + }; +}; diff --git a/apps/server/src/network/handlers/registration/createSocketRegistrationContext.ts b/apps/server/src/network/handlers/registration/createSocketRegistrationContext.ts new file mode 100644 index 0000000..1eb2d3e --- /dev/null +++ b/apps/server/src/network/handlers/registration/createSocketRegistrationContext.ts @@ -0,0 +1,26 @@ +/** + * createSocketRegistrationContext + * ソケット受信イベント登録で利用する共通コンテキストを生成する + */ +import type { Socket } from "socket.io"; +import { createPayloadGuard } from "../payloadGuard"; +import { createServerSocketOnBridge } from "../socketEventBridge"; + +/** 受信イベント登録で利用する共通コンテキスト */ +export type SocketRegistrationContext = { + onEvent: ReturnType["onEvent"]; + guardOnEvent: ReturnType["guardOnEvent"]; +}; + +/** ソケット受信イベント登録で利用する共通コンテキストを生成する */ +export const createSocketRegistrationContext = ( + socket: Socket, +): SocketRegistrationContext => { + const { onEvent } = createServerSocketOnBridge(socket); + const { guardOnEvent } = createPayloadGuard(socket.id); + + return { + onEvent, + guardOnEvent, + }; +}; diff --git a/apps/server/src/network/handlers/registration/index.ts b/apps/server/src/network/handlers/registration/index.ts new file mode 100644 index 0000000..d66cde1 --- /dev/null +++ b/apps/server/src/network/handlers/registration/index.ts @@ -0,0 +1,21 @@ +/** + * index + * registration配下の公開APIを集約して再公開する + */ + +/** ソケット受信イベント登録コンテキストを再公開する */ +export { + createSocketRegistrationContext, +} from "./createSocketRegistrationContext"; +export type { + SocketRegistrationContext, +} from "./createSocketRegistrationContext"; + +/** 接続イベント登録コンテキストを再公開する */ +export { + createConnectionRegistrationContext, +} from "./createConnectionRegistrationContext"; +export type { + ConnectionHandlerDeps, + ConnectionRegistrationContext, +} from "./createConnectionRegistrationContext"; diff --git a/apps/server/src/network/handlers/room/registerRoomHandlers.ts b/apps/server/src/network/handlers/room/registerRoomHandlers.ts index a893f3c..7fdd565 100644 --- a/apps/server/src/network/handlers/room/registerRoomHandlers.ts +++ b/apps/server/src/network/handlers/room/registerRoomHandlers.ts @@ -5,51 +5,82 @@ import { Socket } from "socket.io"; import { protocol } from "@repo/shared"; import type { - RoomHandlerRoomPort, - RoomHandlerRuntimePort, + JoinRoomEventRoomUseCasePort, + JoinRoomEventRuntimeUseCasePort, } from "@server/network/types/connectionPorts"; -import { createPayloadGuard } from "@server/network/handlers/payloadGuard"; -import { createServerSocketOnBridge } from "@server/network/handlers/socketEventBridge"; +import { createSocketRegistrationContext } from "@server/network/handlers/registration"; import { isJoinRoomPayload } from "@server/network/validation/socketPayloadValidators"; import type { RoomOutputAdapter } from "./createRoomOutputAdapter"; -import { handleJoinRoomEvent } from "./roomEventOrchestrators"; +import { + handleJoinRoomEvent, + type JoinRoomEventPayload, + type JoinRoomOrchestratorDeps, +} from "./roomEventOrchestrators"; +import { + registerGuardedEvent, + type GuardedEventDefinition, +} from "@server/network/handlers/eventDefinitionRegistrar"; + +type JoinRoomEventDefinition = GuardedEventDefinition< + typeof protocol.SocketEvents.JOIN_ROOM, + JoinRoomEventPayload +>; /** ルーム受信イベントごとの入力検証関数を保持するテーブル */ const roomPayloadValidators = { [protocol.SocketEvents.JOIN_ROOM]: isJoinRoomPayload, } as const; +/** ルームイベント調停で利用する依存束を生成する */ +const createJoinRoomOrchestratorDeps = ( + socket: Socket, + roomManager: JoinRoomEventRoomUseCasePort, + runtimeRegistry: JoinRoomEventRuntimeUseCasePort, + roomOutputAdapter: RoomOutputAdapter, +): JoinRoomOrchestratorDeps => { + return { + socketId: socket.id, + roomManager, + runtimeRegistry, + output: roomOutputAdapter, + joinRoom: async (roomId) => { + await socket.join(roomId); + }, + }; +}; + +/** JOIN_ROOMイベント定義を生成する */ +const createJoinRoomEventDefinition = ( + deps: JoinRoomOrchestratorDeps, +): JoinRoomEventDefinition => { + return { + event: protocol.SocketEvents.JOIN_ROOM, + validator: roomPayloadValidators[protocol.SocketEvents.JOIN_ROOM], + orchestrate: async (payload) => { + await handleJoinRoomEvent(deps, payload); + }, + }; +}; + /** ルーム参加イベントを検証して参加ユースケースへ連携する */ export const registerRoomHandlers = ( socket: Socket, - roomManager: RoomHandlerRoomPort, - runtimeRegistry: RoomHandlerRuntimePort, + roomManager: JoinRoomEventRoomUseCasePort, + runtimeRegistry: JoinRoomEventRuntimeUseCasePort, roomOutputAdapter: RoomOutputAdapter, ) => { - const { onEvent } = createServerSocketOnBridge(socket); - const { guardOnEvent } = createPayloadGuard(socket.id); - const guardJoinRoomPayload = guardOnEvent( - protocol.SocketEvents.JOIN_ROOM, - roomPayloadValidators[protocol.SocketEvents.JOIN_ROOM] + const orchestratorDeps = createJoinRoomOrchestratorDeps( + socket, + roomManager, + runtimeRegistry, + roomOutputAdapter, + ); + const { onEvent, guardOnEvent } = createSocketRegistrationContext(socket); + + // 検証が必要なイベントを宣言的に登録する + const joinRoomEventDefinition = createJoinRoomEventDefinition( + orchestratorDeps, ); - // 参加要求のペイロード検証と参加処理を実行する - onEvent(protocol.SocketEvents.JOIN_ROOM, async (data) => { - if (!guardJoinRoomPayload(data)) { - return; - } - - await handleJoinRoomEvent( - { - socketId: socket.id, - roomManager, - runtimeRegistry, - output: roomOutputAdapter, - joinRoom: async (roomId) => { - await socket.join(roomId); - }, - }, - data, - ); - }); + registerGuardedEvent(onEvent, guardOnEvent, joinRoomEventDefinition); }; diff --git a/apps/server/src/network/handlers/room/roomEventOrchestrators.ts b/apps/server/src/network/handlers/room/roomEventOrchestrators.ts index 7c06a89..3de3c7a 100644 --- a/apps/server/src/network/handlers/room/roomEventOrchestrators.ts +++ b/apps/server/src/network/handlers/room/roomEventOrchestrators.ts @@ -2,26 +2,30 @@ * roomEventOrchestrators * ルーム受信イベントごとの調停処理を提供する * 受信ハンドラからユースケース実行責務を分離する + * 本ファイルではランタイム未解決ログ対象イベントを扱わない */ import type { roomTypes } from "@repo/shared"; import { joinRoomUseCase } from "@server/domains/room/application/useCases/joinRoomUseCase"; import { logEvent } from "@server/logging/logger"; import { logResults, logScopes, roomUseCaseLogEvents } from "@server/logging/index"; import type { - RoomHandlerRoomPort, - RoomHandlerRuntimePort, + JoinRoomEventRoomUseCasePort, + JoinRoomEventRuntimeUseCasePort, } from "@server/network/types/connectionPorts"; import type { RoomOutputAdapter } from "./createRoomOutputAdapter"; /** JOIN_ROOMイベント調停で利用する依存集合 */ export type JoinRoomOrchestratorDeps = { socketId: string; - roomManager: RoomHandlerRoomPort; - runtimeRegistry: RoomHandlerRuntimePort; + roomManager: JoinRoomEventRoomUseCasePort; + runtimeRegistry: JoinRoomEventRuntimeUseCasePort; output: RoomOutputAdapter; joinRoom: (roomId: string) => Promise; }; +/** JOIN_ROOMイベントの入力ペイロード型 */ +export type JoinRoomEventPayload = Parameters[1]; + /** JOIN_ROOMイベントを調停して参加ユースケースを実行する */ export const handleJoinRoomEvent = async ( deps: JoinRoomOrchestratorDeps, diff --git a/apps/server/src/network/types/connectionPorts.ts b/apps/server/src/network/types/connectionPorts.ts index 79a503f..110e570 100644 --- a/apps/server/src/network/types/connectionPorts.ts +++ b/apps/server/src/network/types/connectionPorts.ts @@ -32,23 +32,23 @@ & FindGameByRoomPort & FindGameByPlayerPort; -/** ゲーム受信ハンドラで利用するルーム依存ポート */ -export type GameHandlerRoomPort = Pick< +/** ゲームイベント調停で利用するルーム依存ポート */ +export type GameEventRoomUseCasePort = Pick< ConnectionRoomPort, "getRoomByOwnerId" | "getRoomByPlayerId" | "markRoomPlaying" | "markRoomWaiting" >; -/** ゲーム受信ハンドラで利用するランタイム依存ポート */ -export type GameHandlerRuntimePort = Pick< +/** ゲームイベント調停で利用するランタイム依存ポート */ +export type GameEventRuntimeUseCasePort = Pick< ConnectionRuntimePort, "getGameManagerByRoomId" | "getGameManagerByPlayerId" >; -/** ルーム受信ハンドラで利用するルーム依存ポート */ -export type RoomHandlerRoomPort = Pick; +/** ルーム参加イベント調停で利用するルーム依存ポート */ +export type JoinRoomEventRoomUseCasePort = Pick; -/** ルーム受信ハンドラで利用するランタイム依存ポート */ -export type RoomHandlerRuntimePort = Pick< +/** ルーム参加イベント調停で利用するランタイム依存ポート */ +export type JoinRoomEventRuntimeUseCasePort = Pick< ConnectionRuntimePort, "ensureGameManagerForRoom" >;