Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7f5f024
docs: add ADR 0035, service-oriented architecture
mike-gorman-bitwarden Sep 10, 2026
210e65d
Update service requirements in architecture document
mike-gorman-bitwarden Sep 10, 2026
fd7e5fd
Fix numbering and formatting in service architecture ADR
mike-gorman-bitwarden Sep 10, 2026
f11b267
Fix prettier formatting in decision 5
mike-gorman-bitwarden Sep 10, 2026
758a177
Clarify API versioning requirement for services
mike-gorman-bitwarden Sep 10, 2026
e691095
Update docs/architecture/adr/0035-service-oriented-architecture.md
mike-gorman-bitwarden Sep 11, 2026
782788d
Update ADR per feedback, externalize the SOA principles, rename serve…
mike-gorman-bitwarden Sep 11, 2026
c606128
Merge remote-tracking branch 'origin/docs/adr-0035-service-oriented-a…
mike-gorman-bitwarden Sep 11, 2026
fee950f
Fix list indentation and typo
mike-gorman-bitwarden Sep 11, 2026
cb54faf
Set ADR 0035 status to Accepted
mike-gorman-bitwarden Sep 11, 2026
228a6fd
Strip out unnecessary context & problem statement.
mike-gorman-bitwarden Sep 11, 2026
06a82c6
Apply prettier to context paragraph
mike-gorman-bitwarden Sep 11, 2026
9937d28
Update the status of the ADR to proposed
mike-gorman-bitwarden Sep 15, 2026
5592b40
Update date in ADR 0035 to 2026-09-22
mike-gorman-bitwarden Sep 15, 2026
64da545
Keep services.md under Server, revert rename
mike-gorman-bitwarden Sep 15, 2026
8a537f0
Remove services.md
mike-gorman-bitwarden Sep 15, 2026
e880d2d
Remove links to deleted services.md
mike-gorman-bitwarden Sep 15, 2026
717b124
Apply review wording suggestions
mike-gorman-bitwarden Sep 15, 2026
15feec7
Update "Versioning" negative consequence
mike-gorman-bitwarden Sep 15, 2026
2eb20b6
Update docs/architecture/adr/0035-service-oriented-architecture.md
mike-gorman-bitwarden Sep 15, 2026
48be7c5
Update positive & negative consequences
mike-gorman-bitwarden Sep 15, 2026
f0aeb05
Merge remote-tracking branch 'origin/docs/adr-0035-service-oriented-a…
mike-gorman-bitwarden Sep 15, 2026
07494bb
Fix grammar
mike-gorman-bitwarden Sep 15, 2026
8bfcfed
Clarify event standards is out of scope of this ADR.
mike-gorman-bitwarden Sep 16, 2026
1d07899
Make it clear that cascade deletes are implemented by listening to ev…
mike-gorman-bitwarden Sep 18, 2026
ecb9f3c
Fix rule reference
mike-gorman-bitwarden Sep 18, 2026
ef09f6e
Loosen the requirements for publishing events for EVERY state change
mike-gorman-bitwarden Sep 21, 2026
a7de27c
Clarify requirements of internal services vs. external
mike-gorman-bitwarden Sep 22, 2026
301a761
Close normative gaps in rules 4, 5, and 10
mike-gorman-bitwarden Sep 22, 2026
eb7b990
Name the standards page consistently
mike-gorman-bitwarden Sep 22, 2026
6fb9dae
Make the resource-deleted obligation unconditional
mike-gorman-bitwarden Sep 22, 2026
3387674
Merge branch 'main' into docs/adr-0035-service-oriented-architecture
mike-gorman-bitwarden Sep 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/architecture/adr/0008-server-CQRS-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ removed over time.

## Further reading

- [Command Query Separation](../server/command-query-separation.md) - Practical guide on
implementing CQS in the server codebase
- [Command Query Separation](../service-oriented-architecture/command-query-separation.md) -
Practical guide on implementing CQS in the server codebase
- [Martin Fowler on CQS](https://martinfowler.com/bliki/CommandQuerySeparation.html) - High-level
summary of the CQS principle
170 changes: 170 additions & 0 deletions docs/architecture/adr/0035-service-oriented-architecture.md
Comment thread
theMickster marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---
adr: "0035"
status: Proposed
date: 2026-09-22
tags: [server, server-sdk]
---

# 0035 - Service-oriented architecture

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

## Notation

This ADR uses [RFC 2119](https://www.rfc-editor.org/info/rfc2119/) keywords (`MUST`, `MUST NOT`,
`SHOULD`, `SHOULD NOT`, `MAY`) deliberately. Anything marked `MUST` or `MUST NOT` is not negotiable
at team level; a team that needs an exception brings the case to the architecture group. The terms
_service_, _consumer_, _service client_, and _row-level security_ are defined in the
[service-oriented architecture standard](../service-oriented-architecture/services.md).

## Context and problem statement
Comment thread
withinfocus marked this conversation as resolved.

The server is one monolithic application over one monolithic database. Any code path can join across
Comment thread
theMickster marked this conversation as resolved.
any domain, so a domain's data has no enforceable owner: the database is the integration contract,
and every team is coupled to every other team's tables. Three consequences follow:

1. A schema change cannot be reasoned about locally.
2. Organization scoping is applied by convention at each call site.
3. No team can deploy on its own cadence.

## Considered options

- **Status quo:** one monolithic application over one monolithic shared database, with logical
separation by convention.
- **Modular monolith:** enforce boundaries in code through module ownership and architecture tests,
keeping the single store and single deployment.
- **Event-first services with local read models:** each service owns a store, and cross-boundary
reads are served from a local projection kept current by an event stream.
- **Service-oriented architecture:** each service owns a store, cross-boundary access goes through
the owner's published service client, and events propagate facts.

### Status quo

**Pros**

- No migration cost, no version skew, no new operational surface.

**Cons**

- Does not deliver independent deployment, which is the requirement driving the work.
- Leaves organization scoping as a convention applied at each call site.

### Modular monolith

**Pros**

- Real boundary enforcement at compile time, at a fraction of the cost of extraction.
- Introduces no distributed-systems failure modes.

**Cons**

- An architecture test cannot see SQL, so a module boundary does not stop a cross-domain join.
- Still one deployment, so cadence stays coupled.

### Event-first services with local read models

**Pros**

- A consumer answers reads without depending on the owner being reachable.
- No synchronous call path to authenticate, authorize, or operate between services.

**Cons**

- A projection is a second implementation of the owner's read logic, including its row-level
security, and the two can diverge silently.
- Correctness depends on event delivery, including on the tiers where the message transport is
weakest.
- The owner cannot enumerate, reach, or repair copies of its own data, so a representation defect
cannot be fixed centrally.

### Service-oriented architecture

**Pros**

- One implementation of each read, owned by the team that owns the rules it enforces.
- Organization scoping is enforced once, by the owner.
- Local copies remain available where they are genuinely warranted, as a recorded exception.

**Cons**
Comment thread
withinfocus marked this conversation as resolved.

- Introduces a synchronous dependency between services, which must be authenticated, authorized,
cached, and operated.
- Independently deployable services are independently versioned services, which means compatibility
matrices on customer installations.

## Decision outcome

Chosen option: **Service-Oriented Architecture**
Comment thread
mike-gorman-bitwarden marked this conversation as resolved.
Outdated
Comment thread
mike-gorman-bitwarden marked this conversation as resolved.
Outdated

The rules are published as the
[service-oriented architecture standard](../service-oriented-architecture/services.md). That page is
the living reference: its rules evolve by pull request without superseding this decision, and this
ADR is superseded only if the model itself changes. The rules at adoption:

1. Service boundaries `MUST` derive from data ownership, not from team structure.
2. Every resource `MUST` have exactly one owning service, and that service is the only process that
reads or writes its data store.
3. Services `MUST` be built on the `Bitwarden.Server.Sdk` package.
4. Services `MUST` document their APIs in [OpenAPI format](https://www.openapis.org/) and conform to
**API Standards**.
5. Services `MUST NOT` make breaking changes. Changes that _would_ be breaking `MUST` follow the API
versioning process as outlined by API Standards.
6. Services `MUST` provide a **service client** for consumers.
7. Service clients `SHOULD` make use of a network cache to mitigate performance issues.
- Any cache used `MUST` be owned and invalidated by the owning service.
- Serving results from cache `MUST NOT` bypass authorization the owning service would otherwise
enforce.
8. Services that need to read, write, or validate data owned by another service `SHOULD` do so via
Comment thread
theMickster marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: This feels like a must to me. I don't want to get in a world where we have the option to directly hit APIs to bypass guards built into a client or scatter implementation details to far-flung API call sites.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mike and I actually talked about this one and softened the language so as to not rule this out.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there something you foresee that being beneficial for? I think it's likely to produce unspoken contracts and action at a distance effects

the service's published service client.
9. A service `MAY` hold a local copy of another service's data only with a recorded justification
(e.g. a measured hot-path volume, a stated availability requirement, etc.).
Comment thread
mike-gorman-bitwarden marked this conversation as resolved.
- Any service holding a local copy `MUST` enforce the owner's row-level security on that copy and
Comment thread
theMickster marked this conversation as resolved.
Outdated
document the security ramifications of a stale copy (due to messaging lag, event processing
failures, etc.).
10. Services `MUST` publish events for every state change using the "transactional outbox" pattern,
Comment thread
quexten marked this conversation as resolved.
Outdated
regardless of whether there are any known consumers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there value in limiting this scope initially? I may be missing things, but my read of this architecture is that only deletes are needed to propagate cascades.

Additionally, this rule binds us to broad-scoped state change statements, rather than tightly typed and defined messages. The latter requires knowledge of consumer needs, but the former creates significant challenges in potentially needing to combine data streams.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here is that if every service broadcasts every state change "as a matter of course", when there is a need to build this integration or that, the team that needs to do so has everything they need. There is never a situation where, in order to make something work, team A has to talk to team B to get them to start doing something. It is a pattern I found to be a very effective way of working.

Having said that, I think that we actually do have a consumer for all of these events - and that is our audit log (a.k.a. Events Service) which, of course, wants a permanent record of everything that's happened.

In addition to that, are the consumers you mentioned - those that want to make sure they delete related data when parent resources are deleted.

So the requirement is for those three reasons:

  1. To facilitate the audit log.
  2. To facilitate cascade deletes.
  3. To facilitate the next thing we haven't thought of so that, when it does come up, it doesn't require the "upstream" team to make changes to their code.

@MGibson1 MGibson1 Sep 16, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event logs are very far from an audit trail of everything that happens in the Bitwarden database. What's more is there's a layer of authorization that needs to occur on them and it's not obvious to me where that belongs.

I also want to point out that

To facilitate the next thing we haven't thought of so that, when it does come up, it doesn't require the "upstream" team to make changes to their code.

and

but the exact details of what is included in the event is up to the service to decide
are statements in conflict. You're trying to preempt what events will be needed without knowledge of what that may be.

My push back here is that this rule will result in generic, all-encompassing change events that are difficult to consume, annoying to maintain, and mostly ignored.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably best discussed in person but I am curious to know what makes you think they would be difficult to consume? Presumably, all events share some common top-level "shape" like category, occurredAt, description, and severity which would go a long way towards making them pretty straight-forward to consume.

As for maintenance, I would expect that most transactions go through some common framework that, in addition to persisting the changes, publishes the corresponding event in which case these events all originate from one place, which shouldn't be too hard to maintain.

And if every change is something that we need to record for auditing, then, none of these events are ignored.

As for authorization, I agree that's something that needs to be worked out.


### Positive consequences

- A resource has one owner, so a schema change is reasoned about locally.
- A single owning process makes organization scoping enforceable in one place.
- Teams deploy on their own cadence against a published contract.
- Consumers write the same code on every deployment tier; the service client resolves how a call is
made.
- Audit and future integrations read one event stream that already exists.
- Local copies stay available where warranted, with the justification and the staleness consequences
recorded where the copy is introduced.

### Negative consequences

- **Version skew becomes a supported condition.** Independently deployed services mean compatibility
matrices on customer installations, with no rollback available on a customer's own hardware. Rule
5 keeps this tractable, and it is a permanent obligation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Does this allow for eventual deprecation and removal of a version ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great question. The ADR doesn't explicitly say how new services will be versioned going forward but I will strongly advocate services living in repos of their own, with semantic version numbers evolving independently, as well, as new features are added and bugs are fixed. However, upcoming standards for internal APIs mandates services never make breaking changes which means, "the latest version of everything" is supposed to always work together; that is our obligation. (This same ADR formalizes how APIs do get formally deprecated and eventually removed.)

But, as far as versions of the services goes, a self-host customer initial install is "everything" and, to get any bug fix, the process is also the same: upgrade to the latest version of everything. In this view, every bug is forward-fix only; there are no "major versions" or release branches to maintain: just a never-ending stream of enhancements and bug fixes.

Now to your question: if you are talking about deprecating and, eventually, removing some version of some service, I would say we can do that but I am not sure it is necessary. We just continuously add features and fix bugs and, each time we do, we are publishing new versions of the services and customers can just sit on whatever version they are on or they can upgrade to the latest (of everything) to get all of the latest bug fixes across all services. From this point of view, every version of every service that is not the latest is deprecated (because it contains known bugs that have since been fixed).

Having said that, this is my "I'm new here" view of how this should work without the first conversation with Product Management so they may take a different view of how versioning this federation of services should work and may have reasons to retain the current YYYY.XX.ZZ versioning scheme.

Does that help at all?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks mike! So to clarify, you are saying that within a version there are no breaking changes to be made, but old versions can be deprecated and removed, so they do not have to be indefinitely supported?

I've added comments on the PR you link, but one of the interesting questions here is: What about security issues? Do they classify under the same category as bugs (for which breaking changes are allowed as per your linked PR)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - it's definitely a different way of thinking - because even sentences like this cease to have any meaning in a world of continuous deployment:

... so they do not have to be indefinitely supported?

This sentence has in mind something like release branches where a bug that is found has to be fixed in N number of releases. But if services are just continuously receiving updates, and every one results in a new version of that service, with a new semantic version number, then there is no concept of "supporting" anything other than the current version. If a bug is found in the current version or any previous version, it is just fixed on the current version - and a new version produced.

It is our job to fix any bug that is found. It is the self-host customer's job to upgrade to the latest to get whatever bugs we have fixed.

Yeah, but customers want to be able to upgrade on their own timetable. And they do not like to get new features and bug fixes at the same time. They want to just get bug fixes.

Well I guess that is a Product Management decision but, in my view, customers do not get to dictate how they get bug fixes. As long as the process to get them requires nothing more than a "update everything" script, I think that is not unreasonable.

The only scenario I can think of in a world of continuous deployment where there is a notion of "supporting" older versions of the software is if we came out with "Bitwarden - The Next Generation" which was like a completely new implementation based on a completely new code base. In this case, it would be our responsibility to fix any bug found in both code bases.

What about security issues? Do they classify under the same category as bugs (for which breaking changes are allowed as per your linked PR)?

Right. I updated the docs to clarify that any bug - security or otherwise - SHOULD be fixed "in place" - even if doing so would technically be considered a breaking change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the middle quote is from some other thread. I'm not sure where it came from?

Thanks for the response though!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but customers want to be able to upgrade on their own timetable. And they do not like to get new features and bug fixes at the same time. They want to just get bug fixes.

Ha! That was me trying to anticipate the objection. ;-)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I can see why one would have that objection, but I don't hold that view at all.

It just happens to be that my team has to make a fair amount of changes to the cryptography layer, so I don't view the API as stable enough to be declared stable / am concerned what impact it will have / what mechanisms we will have to use to help with that. That's all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, if an API if a team is still actively iterating on an API (i.e. still under active development), I don't think "no breaking changes" rule applies to such an API. But, in either event, this will be more relevant to the upcoming Internal API Standards.

- **A synchronous dependency now exists where none did.** It has to be authenticated, authorized,
Comment thread
theMickster marked this conversation as resolved.
observed, and operated, and a dependency's unavailability becomes a caller's failure mode.
- **Service-to-service authentication has to be built for cloud.** The existing internal grant has
only ever been registered for self-hosted deployments.
- **Every extracted service is another process on the smallest tier.** Bitwarden Lite already runs
nine processes on one box against an operator-supplied database, so service count is priced there
first.
- **Row-level security in the data layer is not yet portable.** The current implementation composes
T-SQL and has no Entity Framework path, so the enforcement this ADR relies on is available on SQL
Server only until that gap is closed.
- **Caching is not uniformly available.** Neither full self-host nor Bitwarden Lite ships a shared
Comment thread
withinfocus marked this conversation as resolved.
Outdated
cache today, so rule 7's cache is cloud-only until that is addressed.

### Plan

- Publish **API Standards**. Rules 4 and 5 reference it normatively and it does not exist yet on

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ This will be edited / amended once in place, or its development will be clearly indicated.

this site.
Comment thread
mike-gorman-bitwarden marked this conversation as resolved.
Outdated
- Publish a client strategy page covering how a service client is generated, wrapped, versioned, and
cached.
- Publish a service identity and context page covering service-to-service authentication,
authorization by scope, and context propagation, and build the cloud path it describes.
- Provide an Entity Framework path for organization scoping, so rule 9 holds on all supported
database providers.
- Provide a transactional outbox and a broker-free event transport, so rule 10 holds on deployments
that ship no broker.
- Decide the shared cache posture for full self-host and Bitwarden Lite. The cache implementation is
settled by [ADR-0028](./0028-adopt-fusion-cache.md).
- Apply the standard to the next service extraction as the reference implementation.
2 changes: 0 additions & 2 deletions docs/architecture/server/_category_.yml

This file was deleted.

24 changes: 0 additions & 24 deletions docs/architecture/server/index.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
label: "Service-Oriented Architecture"
Comment thread
theMickster marked this conversation as resolved.
Outdated
position: 6
36 changes: 36 additions & 0 deletions docs/architecture/service-oriented-architecture/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
sidebar_position: 0
---

# Service-Oriented Architecture

**Audience:** Bitwarden engineers and AI agents building, extracting, or reviewing a service.

This section is the living standard for how Bitwarden's services are built and how they relate to
one another: what a service owns, how a boundary is crossed, what travels on a call, and what every
service publishes. The service model was adopted in
[ADR-0035](../adr/0035-service-oriented-architecture.md), and the pages here evolve by pull request
without superseding that decision.

## Key patterns

### Services

Service boundaries derive from data ownership. Every resource has exactly one owning service, and
consumers cross a boundary through the client that service publishes.

See [Services](./services.md) for the full standard.

### Command Query Separation (CQS)

We use the CQS pattern to break up large service classes into smaller, focused commands and queries.
This results in classes with fewer interdependencies that are easier to change and test.

See [Command Query Separation](./command-query-separation.md) for details.

### Model separation of concerns

API contracts (request/response models) are kept separate from internal data models. This allows
APIs to evolve independently from internal data structures and business logic.

See [Model separation of concerns](./model-separation-of-concerns.md) for details.
59 changes: 59 additions & 0 deletions docs/architecture/service-oriented-architecture/services.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
sidebar_position: 3
---

# Services

**Audience:** Bitwarden engineers and AI agents building, extracting, or reviewing a server-side
service.

This page is the living standard for how Bitwarden's services relate to one another: what a service
owns, how a boundary is crossed, and what every service publishes. It was adopted in
[ADR-0035](../adr/0035-service-oriented-architecture.md). The rules below evolve by pull request
without superseding that decision.

[RFC 2119](https://www.rfc-editor.org/info/rfc2119/) keywords (`MUST`, `MUST NOT`, `SHOULD`,
`SHOULD NOT`, `MAY`) are used deliberately. A `MUST` or `MUST NOT` is not negotiable at team level;
a team that needs an exception brings the case to the architecture group.

## Terms

| Term | Definition |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Consumer** | Anything that calls a service: another service, a user interface, or an external integration. |
| **Row-level security** | The rules an owning service applies to decide which rows a caller may see, covering both organization scoping and any narrower per-user visibility. |
| **Service** | An independently deployable process that owns a set of resources and publishes a contract over them. |
| **Service client** | The package a service publishes so consumers can call it without hand-writing transport. |

## Principles

1. Service boundaries `MUST` derive from data ownership, not from team structure.
2. Every resource `MUST` have exactly one owning service, and that service is the only process that
reads or writes its data store.
3. Services `MUST` be built on the `Bitwarden.Server.Sdk` package.
4. Services `MUST` document their APIs in [OpenAPI format](https://www.openapis.org/) and conform to
**API Standards**.
5. Services `MUST NOT` make breaking changes. Changes that _would_ be breaking `MUST` follow the API
versioning process as outlined by API Standards.
6. Services `MUST` provide a **service client** for consumers.
7. Service clients `SHOULD` make use of a network cache to mitigate performance issues.
- Any cache used `MUST` be owned and invalidated by the owning service.
- Serving results from cache `MUST NOT` bypass authorization the owning service would otherwise
enforce.
8. Services that need to read, write, or validate data owned by another service `SHOULD` do so via
the service's published service client.
9. A service `MAY` hold a local copy of another service's data only with a recorded justification
(e.g. a measured hot-path volume, a stated availability requirement, etc.).
- Any service holding a local copy `MUST` enforce the owner's row-level security on that copy and
document the security ramifications of a stale copy (due to messaging lag, event processing
failures, etc.).
10. Services `MUST` publish events for every state change using the "transactional outbox" pattern,
regardless of whether there are any known consumers.

## Related standards

- [ADR-0008 Server: Adopt CQS](../adr/0008-server-CQRS-pattern.md)
- [ADR-0028 Adopt FusionCache](../adr/0028-adopt-fusion-cache.md)
- [ADR-0031 Adopt Minimal APIs](../adr/0031-adopt-minimal-apis.md)
- [ADR-0032 Break up the Core project](../adr/0032-break-up-core.md)
- [Command Query Separation (CQS)](./command-query-separation.md)
Loading