Messages refer to the messages sent by users and by the system.
Commands refer to a specific type of message, a command, which will (when authorized) trigger a change to entities in the system (e.g. /rename room My Room).
Rooms are the message rooms users may join, to which messages are sent.
Memberships represent the membership status history (PendingApproval, Joined, Revoked, etc.) of a user for a given room. This history is used to authorize users using an AuthService.
Messages
The uniquely identifiable entity representing a message in the system is a SentMessage. Before being sent, there are a few value objects which represent how messages are processed and dispatched:
An IncomingMessage represents a new message received by the system. At this point it has not been processed or stored.
A message prefixed with a forward slash (e.g. /help) is considered to be a command, represented by an IncomingCommand.
A newly generated message which has not yet been dispatched is a DraftMessage.
A draft message is sent to the message Dispatcher which will send the message to the appropriate room and store it in the room's history as a SentMessage.
The entrypoint to the messaging pipeline is the MessagesService. A simplified view of this service looks like this:
%%{init:{"theme":"dark"}}%%
stateDiagram
state MessagesService {
direction LR
state if_state <<choice>>
[*] --> if_state
if_state --> IncomingCommand: isCommand
if_state --> IncomingMessage: !isCommand
IncomingMessage --> Dispatcher
Dispatcher --> SentMessage
IncomingCommand --> CommandService
CommandService --> Dispatcher
}
%%{init:{"theme":"default"}}%%
stateDiagram
state MessagesService {
direction LR
state if_state <<choice>>
[*] --> if_state
if_state --> IncomingCommand: isCommand
if_state --> IncomingMessage: !isCommand
IncomingMessage --> Dispatcher
Dispatcher --> SentMessage
IncomingCommand --> CommandService
CommandService --> Dispatcher
}
stateDiagram
state MessagesService {
direction LR
state if_state <<choice>>
[*] --> if_state
if_state --> IncomingCommand: isCommand
if_state --> IncomingMessage: !isCommand
IncomingMessage --> Dispatcher
Dispatcher --> SentMessage
IncomingCommand --> CommandService
CommandService --> Dispatcher
}
Commands
A message identified as an IncomingCommand will be parsed and (if it is a valid command) executed.
%%{init:{"theme":"dark"}}%%
stateDiagram
state CommandService {
direction LR
[*] --> IncomingCommand
IncomingCommand --> TokenizedCommand : tokenize
TokenizedCommand --> ParsedCommand : parse
ParsedCommand --> Dispatcher : execute
Dispatcher
}
%%{init:{"theme":"default"}}%%
stateDiagram
state CommandService {
direction LR
[*] --> IncomingCommand
IncomingCommand --> TokenizedCommand : tokenize
TokenizedCommand --> ParsedCommand : parse
ParsedCommand --> Dispatcher : execute
Dispatcher
}
stateDiagram
state CommandService {
direction LR
[*] --> IncomingCommand
IncomingCommand --> TokenizedCommand : tokenize
TokenizedCommand --> ParsedCommand : parse
ParsedCommand --> Dispatcher : execute
Dispatcher
}
The CommandService applies the following steps to match parsed commands and execute them with the appropriate use case:
The tokenizeCommand function perfoms a lexing role, tokenizing the incoming commmand based on whitespace and returning a TokenizedCommand which allows for easy parsing.
The ParseCommandUseCase class parses tokenized commands, returning a ParsedCommand, a type safe discriminated union of all possible commands.
Once the command is parsed, the CommandService will match the parsed command and execute the corresponding use case.
These use cases will update entities and send messages via the Dispatcher as appropriate.