Docs: Write technical deep dive for commands and link to it from README

Includes puml diagrams and exported svgs
This commit is contained in:
Lars Simon Winzer
2026-04-04 15:51:29 +02:00
parent 4317b63a87
commit f971e6ad5b
19 changed files with 224 additions and 2 deletions
+2 -1
View File
@@ -13,7 +13,8 @@ The concrete implementations for each command live in `app/commands/` and are wi
*Link to guides*
### Reference
*Link to technical deep dives*
- [Command Infrastructure](./commands-deep-dive.md) -
Technical deep-dive into the `CommandParser`, `CommandParserDispatcher`, `CommandHandler`, `CommandRouter`, `Request`, and the response hierarchy.
## Key Concepts
**Each command is self-contained.**
@@ -0,0 +1,108 @@
# Command Infrastructure
This document describes the generic command infrastructure that lives in the `network/` layer.
It covers the parsing pipeline, the routing pipeline, and the base types that every command builds on.
The infrastructure is intentionally project-agnostic: It has no knowledge of specific commands and is never modified when a new command is added.
## Overview
An incoming request travels through two sequential pipelines: **parsing** and **execution**.
The parsing pipeline converts a stringly-typed `PrimitiveRequest` into a strongly-typed `Request` subclass.
The execution pipeline routes that typed request to the correct handler, which produces a `Response`.
![Sequence diagram of all involved components to process and respond to an incoming request](/documents/images/docs/networking/commands/sequence_diagram.svg)
## Parsing Pipeline
### `CommandParser<T extends Request>`
![Class diagram of the CommandParser](/documents/images/docs/networking/commands/command_parser.svg)
The `CommandParser` is a single-method interface responsible for converting a `PrimitiveRequest` into a concrete, typed `Request` subclass.
Implementations live in `app/commands/<name>/` and are registered by name in `CommandParserDispatcher`.
The parser is the correct place to validate and extract parameters.
If a required parameter is absent, `RequestParameterAccessor.require(...)` throws an `MissingParameterException`, which the `SessionReader` catches and converts into a `MISSING_PARAMETER` error response for the client.
Parsers **do not** perform any domain logic. Their only job is extraction and type conversion.
### `CommandParserDispatcher`
![Class diagram of CommandParserDispatcher](/documents/images/docs/networking/commands/command_parser_dispatcher.svg)
The `CommandParserDispatcher` holds a map from command name strings (e.g. `"PING"`) to their corresponding `CommandParser`.
When the `SessionReader` receives a `PrimitiveRequest`, it calls `dispatcher.parse(...)`, which looks up the parser by the request's command string and delegates parsing.
If no parser is registered for the command name, `parse(...)` throws an `UnknownCommandException`, which `SessionReader` catches and converts into an `UNKNOWN_COMMAND` error response for the client.
### `RequestParameterAccessor`
![Class diagram of RequestParameterAccessor](/documents/images/docs/networking/commands/request_parameter_accessor.svg)
The `RequestParameterAccessor` is a helper provided to parsers for reading typed parameter values from a `PrimitiveRequest`.
It indexes the parameter list by key on construction for O(1) lookups.
```java
// Require a parameter — throws MissingParameterException if absent
String username = accessor.require("USERNAME");
// Require and parse — throws ParameterParseException if conversion fails
int count = accessor.require("COUNT", Integer::parseInt);
// Optional with a default
String mode = accessor.optional("MODE", "default");
```
The `ThrowingParser<T>` functional interface accepted by the typed overloads allows any checked or unchecked exception to propagate from the conversion function.
The `RequestParameterAccessor` wraps it in a `ParameterParseException`.
## Execution Pipeline
### `Request`
![Class diagram of Request](/documents/images/docs/networking/commands/request.svg)
The `Request` is the abstract base class for all typed command requests. It carries a `RequestContext`, an immutable record containing the originating `SessionId` and the numeric `requestId`.
Both of which are later used by the handler to direct the response to the correct session.
Concrete subclasses add command-specific fields, all set via constructor. Requests are immutable value objects. They carry data, not behaviour.
### `CommandHandler<T extends Request>`
![Class diagram of CommandHandler](/documents/images/docs/networking/commands/command_handler.svg)
The `CommandHandler` is a single-method interface responsible for executing a typed request.
The handler contains the domain logic: reading from registries and managers, modifying state, and dispatching a response via the `ResponseDispatcher`.
Handlers receive their dependencies (the `ResponseDispatcher`, registries and managers etc.) through constructor injection.
This keeps them fully testable without the network layer.
### `CommandRouter`
![Class diagram of CommandRouter](/documents/images/docs/networking/commands/command_router.svg)
The `CommandRouter` maps `Request` subclasses to their handlers using the request's runtime class as the key.
The type safety of `register(...)` ensures that a handler can only be registered for the exact type it is parameterised on.
The unchecked cast in `execute(...)` is therefore safe by construction and is documented with a `@SuppressWarnings` comment in the source.
If no handler is registered for the given request type, `execute(...)` throws `UnknownRequestException`.
Unlike `UnknownCommandException` (which covers unknown command strings), this exception indicates a programming error i.e. a parser was registered without a corresponding handler.
## Response Types
![Class diagram of the response interface and built-in implementations](/documents/images/docs/networking/commands/response_types.svg)
The `Response` is the abstract base for all server responses. Its two concrete branches are `SuccessResponse` (prefix `+OK`) and `ErrorResponse` (prefix `-ERR`).
Command-specific responses extend `SuccessResponse` and populate the body using the `ResponseBodyBuilder`.
`OkResponse` is a pre-built convenience subclass of `SuccessResponse` with an empty body, used for commands that need only acknowledge success without returning data (e.g. `PING`).
The body is built with a fluent `ResponseBodyBuilder`:
```java
// Simple key/value parameters
new ResponseBodyBuilder()
.param("STATUS", UsernameAvailability.FREE)
.build();
// Nested block
new ResponseBodyBuilder()
.block("USER", b -> b
.param("ID", user.getId().value())
.param("NAME", user.getName()))
.build();
```
`ResponseEncoder` serialises the body into the wire format (tab-indented, `END`-terminated blocks) and wraps it in a `PrimitiveResponse`.
`ResponseDispatcher` then enqueues this into the target session's bounded response queue.
@@ -0,0 +1,8 @@
@startuml
skinparam backgroundColor transparent
interface CommandHandler<T extends Request> {
+ execute(request: T): void
}
@enduml
@@ -0,0 +1 @@
<?plantuml 1.2026.2?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" data-diagram-type="CLASS" height="85px" preserveAspectRatio="none" style="width:287px;height:85px;" version="1.1" viewBox="0 0 287 85" width="287px" zoomAndPan="magnify"><defs/><g><!--class CommandHandler--><g class="entity" data-qualified-name="CommandHandler" data-source-line="3" id="ent0002"><rect fill="#F1F1F1" height="64.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="266.835" x="7" y="7"/><ellipse cx="22" cy="23" fill="#B4A7E5" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M18.4277,19.2651 L18.4277,17.1069 L25.8071,17.1069 L25.8071,19.2651 L23.3418,19.2651 L23.3418,27.3418 L25.8071,27.3418 L25.8071,29.5 L18.4277,29.5 L18.4277,27.3418 L20.8931,27.3418 L20.8931,19.2651 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="122.7529" x="36" y="28.291">CommandHandler</text><rect fill="#FFFFFF" height="16.1328" style="stroke:#181818;stroke-width:1;stroke-dasharray:2,2;" width="110.082" x="166.7529" y="4"/><text fill="#000000" font-family="sans-serif" font-size="12" font-style="italic" lengthAdjust="spacing" textLength="108.082" x="167.7529" y="16.6016">T extends Request</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="272.835" y1="39" y2="39"/><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="272.835" y1="47" y2="47"/><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="60.7441" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="168.6631" x="27" y="64.5352">execute(request: T): void</text></g><?plantuml-src 9Sb12W8n34RXVKwHfU8D1d4p6zSo5uZjhvIP9fgc8eXtj-23j_hQMlGXBfMlajQxMzSyssREuQ9j43I8YWRy9WayMwsY-JpUuCU5yGveRdp1iwF5YJ_4eyC0f1xO-HycNlOJlwna-j8F?></g></svg>
@@ -0,0 +1,8 @@
@startuml
skinparam backgroundColor transparent
interface CommandParser<T extends Request> {
+ parse(primitiveRequest: PrimitiveRequest): T
}
@enduml
@@ -0,0 +1 @@
<?plantuml 1.2026.2?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" data-diagram-type="CLASS" height="85px" preserveAspectRatio="none" style="width:347px;height:85px;" version="1.1" viewBox="0 0 347 85" width="347px" zoomAndPan="magnify"><defs/><g><!--class CommandParser--><g class="entity" data-qualified-name="CommandParser" data-source-line="3" id="ent0002"><rect fill="#F1F1F1" height="64.4883" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="326.5283" x="7" y="7"/><ellipse cx="54.2124" cy="23" fill="#B4A7E5" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M50.6401,19.2651 L50.6401,17.1069 L58.0195,17.1069 L58.0195,19.2651 L55.5542,19.2651 L55.5542,27.3418 L58.0195,27.3418 L58.0195,29.5 L50.6401,29.5 L50.6401,27.3418 L53.1055,27.3418 L53.1055,19.2651 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="111.5215" x="74.7124" y="28.291">CommandParser</text><rect fill="#FFFFFF" height="16.1328" style="stroke:#181818;stroke-width:1;stroke-dasharray:2,2;" width="110.082" x="226.4463" y="4"/><text fill="#000000" font-family="sans-serif" font-size="12" font-style="italic" lengthAdjust="spacing" textLength="108.082" x="227.4463" y="16.6016">T extends Request</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="332.5283" y1="39" y2="39"/><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="332.5283" y1="47" y2="47"/><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="60.7441" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="300.5283" x="27" y="64.5352">parse(primitiveRequest: PrimitiveRequest): T</text></g><?plantuml-src NOf12i9034NtESLVAjwWY9jUe4Wl43jHXZgPCKcB8DxT2gvyo_UUpraR6nTSU9flLXTDVRrNGnYhRqaqYBA6s9KdGLzBOKq3cuiTHyWpH9FZB8z5F4vu4JOEQDzZrooN77cLN-ym_9bzXv7Uz040?></g></svg>
@@ -0,0 +1,11 @@
@startuml
skinparam backgroundColor transparent
class CommandParserDispatcher {
- parsers: Map<String, CommandParser>
+ register(command: String, parser: CommandParser): void
+ parse(primitiveRequest: PrimitiveRequest): Request
}
@enduml
@@ -0,0 +1 @@
<?plantuml 1.2026.2?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" data-diagram-type="CLASS" height="118px" preserveAspectRatio="none" style="width:434px;height:118px;" version="1.1" viewBox="0 0 434 118" width="434px" zoomAndPan="magnify"><defs/><g><!--class CommandParserDispatcher--><g class="entity" data-qualified-name="CommandParserDispatcher" data-source-line="3" id="ent0002"><rect fill="#F1F1F1" height="97.4648" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="413.0234" x="7" y="7"/><ellipse cx="117.0825" cy="23" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M119.5557,29.1431 Q118.9746,29.4419 118.3354,29.5913 Q117.6963,29.7407 116.9907,29.7407 Q114.4839,29.7407 113.1641,28.0889 Q111.8442,26.437 111.8442,23.3159 Q111.8442,20.1865 113.1641,18.5347 Q114.4839,16.8828 116.9907,16.8828 Q117.6963,16.8828 118.3438,17.0322 Q118.9912,17.1816 119.5557,17.4805 L119.5557,20.2031 Q118.9248,19.6221 118.3313,19.3523 Q117.7378,19.0825 117.1069,19.0825 Q115.7622,19.0825 115.0774,20.1492 Q114.3926,21.2158 114.3926,23.3159 Q114.3926,25.4077 115.0774,26.4744 Q115.7622,27.541 117.1069,27.541 Q117.7378,27.541 118.3313,27.2712 Q118.9248,27.0015 119.5557,26.4204 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="184.3584" x="137.5825" y="28.291">CommandParserDispatcher</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="419.0234" y1="39" y2="39"/><g data-visibility-modifier="PRIVATE_FIELD"><rect fill="none" height="6" style="stroke:#C82930;stroke-width:1;" width="6" x="15" y="49.7441"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="270.1426" x="27" y="56.5352">parsers: Map&lt;String, CommandParser&gt;</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="419.0234" y1="63.4883" y2="63.4883"/><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="77.2324" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="387.0234" x="27" y="81.0234">register(command: String, parser: CommandParser): void</text><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="93.7207" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="346.001" x="27" y="97.5117">parse(primitiveRequest: PrimitiveRequest): Request</text></g><?plantuml-src NOmx3i8m343tdC9Z4N212B6K5Qc24vZKAbQR3tRQ1N5tehOCnPFbzvwrvv1GqCC3NTz974DRno4APC6W1L78nhW1LQ6EtcDe6nGbkN2XsJr9u6sWp17I3DJ25TFfdeL3TzX6PpEhUn3gM3D9vHPiuUSlJ-ossrcO8hThF2jL4lQSUQ8RlKRIRA7vkvHgtSp7V040?></g></svg>
@@ -0,0 +1,10 @@
@startuml
skinparam backgroundColor transparent
class CommandRouter {
- handlers: Map<Class<? extends Request>, CommandHandler<?>>
+ register(requestClass: Class<T>, handler: CommandHandler<T>): void
+ execute(request: Request): void
}
@enduml
@@ -0,0 +1 @@
<?plantuml 1.2026.2?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" data-diagram-type="CLASS" height="118px" preserveAspectRatio="none" style="width:531px;height:118px;" version="1.1" viewBox="0 0 531 118" width="531px" zoomAndPan="magnify"><defs/><g><!--class CommandRouter--><g class="entity" data-qualified-name="CommandRouter" data-source-line="3" id="ent0002"><rect fill="#F1F1F1" height="97.4648" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="510.5996" x="7" y="7"/><ellipse cx="200.7681" cy="23" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M203.2412,29.1431 Q202.6602,29.4419 202.021,29.5913 Q201.3818,29.7407 200.6763,29.7407 Q198.1694,29.7407 196.8496,28.0889 Q195.5298,26.437 195.5298,23.3159 Q195.5298,20.1865 196.8496,18.5347 Q198.1694,16.8828 200.6763,16.8828 Q201.3818,16.8828 202.0293,17.0322 Q202.6768,17.1816 203.2412,17.4805 L203.2412,20.2031 Q202.6104,19.6221 202.0168,19.3523 Q201.4233,19.0825 200.7925,19.0825 Q199.4478,19.0825 198.7629,20.1492 Q198.0781,21.2158 198.0781,23.3159 Q198.0781,25.4077 198.7629,26.4744 Q199.4478,27.541 200.7925,27.541 Q201.4233,27.541 202.0168,27.2712 Q202.6104,27.0015 203.2412,26.4204 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="114.5635" x="221.2681" y="28.291">CommandRouter</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="516.5996" y1="39" y2="39"/><g data-visibility-modifier="PRIVATE_FIELD"><rect fill="none" height="6" style="stroke:#C82930;stroke-width:1;" width="6" x="15" y="49.7441"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="459.2725" x="27" y="56.5352">handlers: Map&lt;Class&lt;? extends Request&gt;, CommandHandler&lt;?&gt;&gt;</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="516.5996" y1="63.4883" y2="63.4883"/><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="77.2324" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="484.5996" x="27" y="81.0234">register(requestClass: Class&lt;T&gt;, handler: CommandHandler&lt;T&gt;): void</text><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="93.7207" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="214.1357" x="27" y="97.5117">execute(request: Request): void</text></g><?plantuml-src NOun3i8m34NtdC8Z2BX0LAL35nQMgXSmZLMgjacn4rG9SNT2sonujDz_tpBqRaR62UxO3Xtxw6pbHyyG69sa4xcWL3kY25H-cj3PsiT036y5QIxmIAkHM3JSS2wg7rcKG4iWPmLgUaQIOCuvVTtmeZHc5Po0gUib6G-yiQj2msPgKd9lqF-AnXmrl7nlTn4jrARNiaVdyanzr1S0?></g></svg>
@@ -0,0 +1,11 @@
@startuml
skinparam backgroundColor transparent
abstract class Request {
# context: RequestContext
+ getContext(): RequestContext
+ getSessionId(): SessionId
+ getRequestId(): int
}
@enduml
@@ -0,0 +1 @@
<?plantuml 1.2026.2?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" data-diagram-type="CLASS" height="134px" preserveAspectRatio="none" style="width:248px;height:134px;" version="1.1" viewBox="0 0 248 134" width="248px" zoomAndPan="magnify"><defs/><g><!--class Request--><g class="entity" data-qualified-name="Request" data-source-line="3" id="ent0002"><rect fill="#F1F1F1" height="113.9531" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="227.7217" x="7" y="7"/><ellipse cx="89.4482" cy="23" fill="#A9DCDF" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M89.3115,18.3481 L88.1577,23.4199 L90.4736,23.4199 Z M87.8174,16.1069 L90.814,16.1069 L94.1592,28.5 L91.7104,28.5 L90.9468,25.437 L87.668,25.437 L86.9209,28.5 L84.4722,28.5 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="54.3252" x="109.9482" y="28.291">Request</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="233.7217" y1="39" y2="39"/><g data-visibility-modifier="PROTECTED_FIELD"><polygon fill="none" points="18,47.7441,22,51.7441,18,55.7441,14,51.7441" style="stroke:#B38D22;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="168.335" x="27" y="56.5352">context: RequestContext</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="233.7217" y1="63.4883" y2="63.4883"/><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="77.2324" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="201.7217" x="27" y="81.0234">getContext(): RequestContext</text><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="93.7207" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="167.3164" x="27" y="97.5117">getSessionId(): SessionId</text><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="110.209" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="124.8721" x="27" y="114">getRequestId(): int</text></g><?plantuml-src AyxEp2j8B4hCLKXAJCvEByelpKjnpi_9Br8eAKhCAmPAfUQLS74b9XK3-Sab2iavYSN52a6fXQMfnIKArLmAGA2ia9oVLv9QKM85at26yC6osWhfgJ013KtSIe9JYuipy_CyKq2Au1o4F5GVH3uJwAXQBW00?></g></svg>
@@ -0,0 +1,14 @@
@startuml
skinparam backgroundColor transparent
class RequestParameterAccessor {
- index: Map<String, String>
+ RequestParameterAccessor(parameters: List<RequestParameters>)
+ require(key: String): String
+ require(key: String, parser: ThrowingParser<T>): T
+ optional(key: String, defaultValue: String): String
+ optional(key: String, defaultValue: T, parser: ThrowingParser<T>): T
}
@enduml
@@ -0,0 +1 @@
<?plantuml 1.2026.2?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" data-diagram-type="CLASS" height="167px" preserveAspectRatio="none" style="width:510px;height:167px;" version="1.1" viewBox="0 0 510 167" width="510px" zoomAndPan="magnify"><defs/><g><!--class RequestParameterAccessor--><g class="entity" data-qualified-name="RequestParameterAccessor" data-source-line="3" id="ent0002"><rect fill="#F1F1F1" height="146.9297" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="489.9072" x="7" y="7"/><ellipse cx="156.0576" cy="23" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1;"/><path d="M158.5308,29.1431 Q157.9497,29.4419 157.3105,29.5913 Q156.6714,29.7407 155.9658,29.7407 Q153.459,29.7407 152.1392,28.0889 Q150.8193,26.437 150.8193,23.3159 Q150.8193,20.1865 152.1392,18.5347 Q153.459,16.8828 155.9658,16.8828 Q156.6714,16.8828 157.3188,17.0322 Q157.9663,17.1816 158.5308,17.4805 L158.5308,20.2031 Q157.8999,19.6221 157.3064,19.3523 Q156.7129,19.0825 156.082,19.0825 Q154.7373,19.0825 154.0525,20.1492 Q153.3677,21.2158 153.3677,23.3159 Q153.3677,25.4077 154.0525,26.4744 Q154.7373,27.541 156.082,27.541 Q156.7129,27.541 157.3064,27.2712 Q157.8999,27.0015 158.5308,26.4204 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="183.292" x="176.5576" y="28.291">RequestParameterAccessor</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="495.9072" y1="39" y2="39"/><g data-visibility-modifier="PRIVATE_FIELD"><rect fill="none" height="6" style="stroke:#C82930;stroke-width:1;" width="6" x="15" y="49.7441"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="186.4502" x="27" y="56.5352">index: Map&lt;String, String&gt;</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="495.9072" y1="63.4883" y2="63.4883"/><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="77.2324" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="454.2002" x="27" y="81.0234">RequestParameterAccessor(parameters: List&lt;RequestParameters&gt;)</text><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="93.7207" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="178.6641" x="27" y="97.5117">require(key: String): String</text><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="110.209" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="345.2969" x="27" y="114">require(key: String, parser: ThrowingParser&lt;T&gt;): T</text><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="126.6973" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="328.3916" x="27" y="130.4883">optional(key: String, defaultValue: String): String</text><g data-visibility-modifier="PUBLIC_METHOD"><ellipse cx="18" cy="143.1855" fill="#84BE84" rx="3" ry="3" style="stroke:#038048;stroke-width:1;"/></g><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="463.9072" x="27" y="146.9766">optional(key: String, defaultValue: T, parser: ThrowingParser&lt;T&gt;): T</text></g><?plantuml-src ZOvD2i9038NtSueiMzGBb58Gjmf5Y_lOHXqwpjHaYefuTyVQBXIAPfE_xxr4QcqRP3p13ilwmAwrrS8Pn-0PhOGLMQzKQL04rdHkINp-uiaJpykIH09xLn1Y1jfMT4rWXKswyQpjOGhldAcEc8nQHCqmaGIMMdpwJKeMznuSDfefgkcMzFxnK8mZKmWdX3Y1uiZk4YPvrwT55jH5BtIDrywY-LHMjCVM-2sQbiPE_gCiVZtnK4y0?></g></svg>
@@ -0,0 +1,23 @@
@startuml
skinparam backgroundColor transparent
abstract class Response {
# context: RequestContext
# body: ResponseBody
+ prefix(): String
+ getSessionId(): SessionId
+ getRequestId(): int
+ getBody(): ResponseBody
}
abstract class SuccessResponse extends Response {
+ prefix(): String (+OK)
}
class OkResponse extends SuccessResponse
class ErrorResponse extends Response {
+ prefix(): String (-ERR)
}
@enduml
File diff suppressed because one or more lines are too long
@@ -0,0 +1,20 @@
@startuml
skinparam backgroundColor transparent
participant SessionReader
participant CommandParserDispatcher as Dispatcher
participant "CommandParser<T>" as Parser
participant CommandRouter as Router
participant "CommandHandler<T>" as Handler
participant ResponseDispatcher
SessionReader -> Dispatcher : parse(primitiveRequest)
Dispatcher -> Parser : parse(primitiveRequest)
Parser --> Dispatcher : ExampleRequest
Dispatcher --> SessionReader : Request
SessionReader -> Router : execute(request)
Router -> Handler : execute(ExampleRequest)
Handler -> ResponseDispatcher : dispatch(ExampleRequest)
@enduml
File diff suppressed because one or more lines are too long