Skip to content

Commit 656dec5

Browse files
authored
Amend ADR 8: Clarify CQS Terminology (#719)
1 parent 7682557 commit 656dec5

File tree

5 files changed

+71
-61
lines changed

5 files changed

+71
-61
lines changed

custom-words.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ codebases
1313
CODEOWNERS
1414
COSE
1515
CQRS
16+
CQS
1617
CTAP2
1718
deinitializer
1819
deinitializers

docs/architecture/adr/0008-server-CQRS-pattern.md

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@ date: 2022-07-15
55
tags: [server]
66
---
77

8-
# 0008 - Server: Adopt CQRS
8+
# 0008 - Server: Adopt CQS
99

1010
<AdrTable frontMatter={frontMatter}></AdrTable>
1111

12+
:::note Terminology clarification
13+
14+
This ADR originally used "CQRS" (Command Query Responsibility Segregation) terminology. However, our
15+
implementation more accurately reflects CQS (Command Query Separation), which is a simpler pattern
16+
focused on breaking up large service classes rather than separating read/write data models. The
17+
content has been updated to use the more accurate CQS terminology.
18+
19+
:::
20+
1221
## Context and problem statement
1322

1423
In Bitwarden Server, we currently use an `<<Entity>>Service` pattern to act on our entities. These
@@ -30,27 +39,26 @@ method parameters, which goes against our typical DI pattern.
3039
- **`<<Entity>>Services`** -- Discussed above
3140
- **Queries and Commands** -- Fundamentally our problem is that the `<<Entity>>Service` name
3241
encapsulates absolutely anything you can do with that entity and excludes any code reuse across
33-
different entities. The CQRS pattern creates classes based on the action being taken on the
34-
entity. This naturally limits the classes scope and allows for reuse should two entities need to
35-
implement the same command behavior.
36-
https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs
42+
different entities. The CQS pattern creates classes based on the action being taken on the entity.
43+
This naturally limits the classes scope and allows for reuse should two entities need to implement
44+
the same command behavior.
3745
- **Small Feature-based services** -- This design would break `<<Entity>>Service` into
3846
`<<Feature>>Service`, but ultimately runs into the same problems. As a feature grows, this service
3947
would become bloated and tightly coupled to other services.
4048

4149
## Decision outcome
4250

43-
Chosen option: **Queries and Commands**
51+
Chosen option: **Queries and Commands (CQS pattern)**
4452

45-
Commands seem all-around the better decision to the incumbent. We gain code reuse and limit class
46-
scope. In addition, we have an iterative path to a full-blown CQRS pipeline, with queued work.
53+
Commands seem all-around the better decision to the incumbent, primarily because it limits class
54+
scope.
4755

4856
Queries are already basically done through repositories and/or services, but would require some
4957
restructuring to be obvious.
5058

5159
## Transition plan
5260

53-
We will gradually transition to a CQRS pattern over time. If a developer is making changes that use
61+
We will gradually transition to a CQS pattern over time. If a developer is making changes that use
5462
or affect a service method, they should consider whether it can be extracted to a query/command, and
5563
include it in their work as tech debt.
5664

@@ -59,3 +67,10 @@ up all at once. It's acceptable to refactor "one level deep" and then leave othe
5967
are. This may result in the new query/command still being somewhat coupled with other service
6068
methods. This is acceptable during the transition phase, but these interdependencies should be
6169
removed over time.
70+
71+
## Further reading
72+
73+
- [Server Architecture](../server/index.md) - Practical guide on implementing CQS in the server
74+
codebase
75+
- [Martin Fowler on CQS](https://martinfowler.com/bliki/CommandQuerySeparation.html) - High-level
76+
summary of the CQS principle

docs/architecture/adr/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ work.
112112
**Example scenarios:**
113113

114114
- A technology choice has been agreed upon (e.g., "Use Jest for testing")
115-
- A pattern has been established as the standard approach (e.g., "Use CQRS for server architecture")
115+
- A pattern has been established as the standard approach (e.g., "Use CQS for server architecture")
116116
- A decision is complete and being followed across teams
117117

118118
### Rejected

docs/architecture/server/index.md

Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,22 @@ sidebar_position: 6
44

55
# Server Architecture
66

7-
## CQRS ([ADR-0008](../adr/0008-server-CQRS-pattern.md))
7+
## Command Query Separation (CQS)
88

9-
Our server architecture uses the the Command and Query Responsibility Segregation (CQRS) pattern.
9+
Our server architecture uses the Command Query Separation (CQS) pattern.
1010

11-
The main goal of this pattern is to break up large services focused on a single entity (e.g.
12-
`CipherService`) and move towards smaller, reusable classes based on actions or tasks (e.g.
13-
`CreateCipher`). In the future, this may enable other benefits such as enqueuing commands for
14-
execution, but for now the focus is on having smaller, reusable chunks of code.
11+
We adopted this pattern in order to break up large services focused on a single entity (e.g.
12+
`CipherService`) into smaller classes based on discrete actions (e.g. `CreateCipherCommand`). This
13+
results in smaller classes with fewer interdependencies that are easier to change and test.
1514

1615
### Commands vs. queries
1716

18-
**Commands** are write operations, e.g. `RotateOrganizationApiKeyCommand`. They should never read
19-
from the database.
17+
**Commands** are write operations, e.g. `RotateOrganizationApiKeyCommand`. They change the state of
18+
the system. They may have no return value, or may return the operation result only (e.g. the updated
19+
object or an error message).
2020

21-
**Queries** are read operations, e.g. `GetOrganizationApiKeyQuery`. They should never write to the
22-
database.
21+
**Queries** are read operations, e.g. `GetOrganizationApiKeyQuery`. They should only return a value
22+
and should never change the state of the system.
2323

2424
The database is the most common data source we deal with, but others are possible. For example, a
2525
query could also get data from a remote server.
@@ -28,18 +28,15 @@ Each query or command should have a single responsibility. For example: delete a
2828
file, rotate an API key. They are designed around verbs or actions (e.g.
2929
`RotateOrganizationApiKeyCommand`), not domains or entities (e.g. `ApiKeyService`).
3030

31-
### Writing commands or queries
31+
Which you use will often follow the HTTP verb: a POST operation will generally call a command,
32+
whereas a GET operation will generally call a query.
3233

33-
A simple query may just be a repository call to fetch data from the database. (We already use
34-
repositories, and this is not what we're concerned about here.) However, more complex queries can
35-
require additional logic around the repository call, which will require their own class. Commands
36-
always need their own class.
34+
### Structure of a command
3735

38-
The class, interface and public method should be named after the action. For example:
36+
A command is just a class. The class, interface and public method should be named after the action.
37+
For example:
3938

4039
```csharp
41-
namespace Bit.Core.OrganizationFeatures.OrganizationApiKeys;
42-
4340
public class RotateOrganizationApiKeyCommand : IRotateOrganizationApiKeyCommand
4441
{
4542
public async Task<OrganizationApiKey> RotateApiKeyAsync(OrganizationApiKey organizationApiKey)
@@ -49,51 +46,43 @@ public class RotateOrganizationApiKeyCommand : IRotateOrganizationApiKeyCommand
4946
}
5047
```
5148

52-
The query/command should only expose public methods that run the complete action. It should not have
49+
The command should only expose public methods that run the complete action. It should not have
5350
public helper methods.
5451

55-
The directory structure and namespaces should be organized by feature. Interfaces should be stored
56-
in a separate sub-folder. For example:
57-
58-
```text
59-
Core/
60-
└── OrganizationFeatures/
61-
└── OrganizationApiKeys/
62-
├── Interfaces/
63-
│ └── IRotateOrganizationApiKeyCommand.cs
64-
└── RotateOrganizationApiKeyCommand.cs
65-
```
52+
A command will usually follow these steps:
6653

67-
### Maintaining the command/query distinction
54+
1. Fetch additional data required to process the request (if required)
55+
2. Validate the request
56+
3. Perform the action (state change)
57+
4. Perform any side effects (e.g. sending emails or push notifications)
58+
5. Return information about the outcome to the user (e.g. an error message or the successfully
59+
created or updated object)
6860

69-
By separating read and write operations, CQRS encourages us to maintain loose coupling between
70-
classes. There are two golden rules to follow when using CQRS in our codebase:
61+
If you have complex validation logic, it can be useful to move it to a separate validator class.
62+
This makes the validator and the command easier to understand, test and maintain.
7163

72-
- **Commands should never read and queries should never write**
73-
- **Commands and queries should never call each other**
64+
Some teams have defined their own request and result objects to pass data to and from commands and
65+
validators. This is optional but can be useful to avoid primitive obsession and have strongly typed
66+
interfaces.
7467

75-
Both of these lead to tight coupling between classes, reduce opportunities for code re-use, and
76-
conflate the command/query distinction.
68+
### Structure of a query
7769

78-
You can generally avoid these problems by:
70+
A simple query may not require its own class if it is appropriately encapsulated by a single
71+
database call. In that case, the "query" is just a repository method.
7972

80-
- writing your commands so that they receive all the data they need in their arguments, rather than
81-
fetching the data themselves
82-
- calling queries and commands sequentially (one after the other), passing the results along the
83-
call chain
73+
However, more complex queries can require additional logic in addition to the repository call. In
74+
this case, it is appropriate to define a separate query class.
8475

85-
For example, if we need to update an API key for an organization, it might be tempting to have an
86-
`UpdateApiKeyCommand` which fetches the current API key and then updates it. However, we can break
87-
this down into two separate queries/commands, which are called separately:
76+
A query is just a class. The class, interface and public method should be named after the data being
77+
queried. For example:
8878

8979
```csharp
90-
var currentApiKey = await _getOrganizationApiKeyQuery.GetOrganizationApiKeyAsync(orgId);
91-
await _rotateOrganizationApiKeyCommand.RotateApiKeyAsync(currentApiKey);
80+
public interface IGetOrganizationApiKeyQuery
81+
{
82+
Task<OrganizationApiKey> GetOrganizationApiKeyAsync(Guid organizationId, OrganizationApiKeyType organizationApiKeyType);
83+
}
9284
```
9385

94-
This has unit testing benefits as well - instead of having lengthy "arrange" phases where you mock
95-
query results, you can simply supply different argument values using the `Autodata` attribute.
96-
9786
### Avoid [primitive obsession](https://refactoring.guru/smells/primitive-obsession)
9887

9988
Where practical, your commands and queries should take and return whole objects (e.g. `User`) rather
@@ -103,3 +92,8 @@ than individual properties (e.g. `userId`).
10392

10493
Lots of optional parameters can quickly become difficult to work with. Instead, consider using
10594
method overloading to provide different entry points into your command or query.
95+
96+
## Further reading
97+
98+
- [ADR-0008: Server CQS Pattern](../adr/0008-server-CQRS-pattern.md) - Architectural decision to
99+
adopt CQS for breaking up large service classes

docs/contributing/code-style/web/angular.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ component "Organization Reports Module" {
212212
@enduml
213213
```
214214
215-
## Reactivity ([ADR-0003](../../../architecture/adr/0003-observable-data-services.md) & ADR-0028)
215+
## Reactivity ([ADR-0003](../../../architecture/adr/0003-observable-data-services.md))
216216
217217
We make heavy use of reactive programming using [Angular Signals][signals] & [RxJS][rxjs]. Generally
218218
components should always derive their state reactively from services whenever possible.

0 commit comments

Comments
 (0)