From 9b9cc359b7bdda2a3f716b6940c7b8efb9ef9fc3 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Thu, 25 Jun 2026 10:57:54 -0400
Subject: [PATCH 1/2] Add ADR 32 about breaking up Core project
---
docs/architecture/adr/0032-break-up-core.md | 193 ++++++++++++++++++++
1 file changed, 193 insertions(+)
create mode 100644 docs/architecture/adr/0032-break-up-core.md
diff --git a/docs/architecture/adr/0032-break-up-core.md b/docs/architecture/adr/0032-break-up-core.md
new file mode 100644
index 000000000..9acd2185d
--- /dev/null
+++ b/docs/architecture/adr/0032-break-up-core.md
@@ -0,0 +1,193 @@
+---
+adr: "0032"
+status: "Proposed"
+date: 2026-06-25
+tags: [server]
+---
+
+# 0032 - Break up the Core project
+
+
+
+## Context and problem statement
+
+The `Core` project in the `server` repository has grown into a catch-all library that almost every
+service depends on. This creates two significant problems:
+
+- **Unowned code and unbounded dependencies** — while much of `Core` is owned by specific teams, a
+ significant portion has accumulated without clear ownership, making it a dumping ground for shared
+ utilities and dependencies that don't have a better home.
+- **Limited independent deployability** — because every service depends on `Core`, any change to
+ `Core` technically constitutes a change to every service. This makes it hard to reason about the
+ blast radius of a change and undermines the ability to deploy services independently with
+ confidence.
+
+`GlobalSettings` compounds these problems. It is a single configuration class that houses settings
+for every feature and service, meaning all of `Core`'s consumers must load and be aware of the
+entire settings surface even when they only need a small slice of it. As features are extracted from
+`Core`, they should define their own strongly-typed options classes rather than growing
+`GlobalSettings` further.
+
+Not all of what currently lives in `Core` needs to move into feature-scoped libraries. A number of
+cross-cutting concerns — feature flag evaluation, version info endpoints, security middleware, and
+caching — are being built into the server SDK. As those SDK packages mature, the corresponding code
+in `Core` can be removed and replaced with an SDK dependency, further shrinking `Core`'s footprint.
+
+Other Bitwarden repositories have already moved toward a feature-scoped library model. The
+`sdk-internal` repository was built on this pattern from the start and follows it fully. The
+`clients` repository has historically had a large `libs/common` package, which is still being
+gradually decomposed into feature-scoped libraries. These precedents validate the approach for
+`server` as well.
+
+## Considered options
+
+- **Keep `Core` as-is** — No structural changes. `Core` continues to grow as a shared monolith.
+ Ownership and deployment problems persist.
+- **Break `Core` into feature-scoped libraries** — New code is placed in dedicated, feature-scoped
+ projects. Platform-level utilities (push notifications, mailing, database foundations) are
+ extracted first as shared dependencies. Existing code is migrated gradually and opportunistically.
+
+## Decision outcome
+
+Chosen option: **Break `Core` into feature-scoped libraries**.
+
+New code belonging to a specific feature or domain should live in its own dedicated project rather
+than in `Core`. Platform-level utilities that many features depend on — such as push notifications,
+mailing, and database foundations — should also be extracted into their own projects, as these
+represent cross-cutting infrastructure rather than feature logic.
+
+In conjunction with [ADR 0031](./0031-adopt-minimal-apis.md), a single library can cover a feature
+end-to-end: repositories, settings, services, and endpoints all in one `.csproj`. There is no
+requirement to separate endpoints into their own project. The goal is feature cohesion, not a
+mandated split between endpoint code and business logic.
+
+Feature libraries live under `src/Libraries/[Feature]`. If a library later graduates into its own
+deployable container, it moves to `src/Services/[Name]`. This extends the `src/Libraries` directory
+structure introduced in [ADR 0031](./0031-adopt-minimal-apis.md).
+
+```
+src/
+ Libraries/
+ Mailer/ # settings, services, repositories, endpoints for the Mailer feature
+ Push/
+ Vault/
+ ...
+ Services/
+ Api/ # composes libraries into a deployable service
+ Identity/
+ Notifications/
+ ...
+```
+
+Libraries use the root namespace `Bit.[Feature]` and an assembly name of `[Feature]`. Services use
+the root namespace `Bit.Services.[Name]` and an assembly name of `[Name]`. This applies primarily to
+net new code; when migrating existing code out of `Core`, retaining the existing namespace is
+acceptable if it keeps breaking changes to a minimum.
+
+The `Core` project will remain during the migration period and code should be moved out
+opportunistically, when a team is already working in that area, rather than in a dedicated
+large-scale migration effort. The long-term goal is to eliminate `Core` entirely. As the migration
+progresses, teams will negotiate the boundaries that should exist between them and create the
+libraries needed to share code across those boundaries.
+
+### Positive consequences
+
+- Clear ownership — teams own their feature project's `.csproj` and control their own dependencies
+- A change to a feature library only affects services that actually depend on it, restoring the
+ ability to reason about and deploy services independently
+- Aligns `server` with the patterns already established in `clients` and `sdk-internal`
+- Complements [ADR 0031](./0031-adopt-minimal-apis.md), which establishes feature-scoped endpoint
+ libraries for minimal API endpoints
+
+### Negative consequences
+
+- `Core` and feature-scoped libraries will coexist for a long time, requiring developers to know
+ where to place new code and where to look for existing code
+- Extracting code from `Core` carries a risk of introducing circular dependencies if the dependency
+ graph is not carefully considered during a migration
+- Without an aggressive timeline, the migration may stall and the benefits will be slow to
+ materialize
+
+### Plan
+
+- New features should not add code to `Core`; they should create or extend a feature-scoped project
+- New libraries should generally sit at a lower level than `Core` and should not depend on it; if a
+ new library needs something that currently lives in `Core`, that is a signal that the dependency
+ itself should be extracted into its own library first
+- Platform-level utilities (push notifications, mailing, database foundations) should be prioritized
+ for extraction as independent projects, since many features will depend on them
+- Cross-cutting concerns already being built into the server SDK — feature flag evaluation, version
+ info endpoints, security middleware, and caching — should be adopted from the SDK as those
+ packages become available, allowing the corresponding `Core` code to be deleted rather than
+ migrated
+- Feature libraries should define their own strongly-typed options classes rather than adding
+ properties to `GlobalSettings`; as features are extracted, their `GlobalSettings` entries should
+ migrate alongside them
+
+ **Before:**
+
+ ```csharp
+ // Core/Settings/GlobalSettings.cs
+ public class GlobalSettings : IGlobalSettings
+ {
+ public virtual MailerSettings Mailer { get; set; } = new MailerSettings();
+
+ public class MailerSettings
+ {
+ public string ReplyToEmail { get; set; }
+ public string SmtpHost { get; set; }
+ // ...
+ }
+ }
+
+ // Consuming service
+ public class Mailer(GlobalSettings globalSettings) : IMailer
+ {
+ public void Send()
+ {
+ var host = globalSettings.Mailer.SmtpHost;
+ }
+ }
+ ```
+
+ **After:**
+
+ The feature library declares its options type and consumes it via `IOptions`. It does not bind
+ configuration itself — that is the host service's responsibility, since the host owns its service
+ settings and knows which configuration sections map to which feature options.
+
+ ```csharp
+ // Libraries/Mailer/MailerSettings.cs
+ public class MailerSettings
+ {
+ public string ReplyToEmail { get; set; }
+ public string SmtpHost { get; set; }
+ // ...
+ }
+
+ // Libraries/Mailer/ServiceCollectionExtensions.cs
+ public static IServiceCollection AddMailers(this IServiceCollection services)
+ {
+ services.TryAddSingleton();
+ return services;
+ }
+
+ // Libraries/Mailer/Mailer.cs
+ internal class Mailer(IOptions settings) : IMailer
+ {
+ public void Send()
+ {
+ var host = settings.Value.SmtpHost;
+ }
+ }
+
+ // Services/Api/Program.cs
+ builder.Services.Configure(builder.Configuration.GetSection("Mailer"));
+ builder.Services.AddMailers();
+ ```
+
+- Existing code in `Core` should be moved out opportunistically when a team is already working in
+ that area — not as a standalone task
+- A guide will be written documenting the conventions for creating a new feature library and the
+ expected project structure, similar to the `ENDPOINT_LIBRARY.md` described in
+ [ADR 0031](./0031-adopt-minimal-apis.md)
From 9597c125ec29f1c629d29e999e085c2275e80acc Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Thu, 25 Jun 2026 14:08:10 -0400
Subject: [PATCH 2/2] Add bitwarden_license example
---
docs/architecture/adr/0032-break-up-core.md | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/docs/architecture/adr/0032-break-up-core.md b/docs/architecture/adr/0032-break-up-core.md
index 9acd2185d..bf6aff711 100644
--- a/docs/architecture/adr/0032-break-up-core.md
+++ b/docs/architecture/adr/0032-break-up-core.md
@@ -79,6 +79,19 @@ src/
...
```
+Libraries and services under `bitwarden_license/` follow the exact same layout, rooted there instead
+of `src/`:
+
+```
+bitwarden_license/
+ Libraries/
+ SecretsManager/ # settings, services, repositories, endpoints for Secrets Manager
+ ...
+ Services/
+ Scim/ # composes libraries into a deployable service
+ ...
+```
+
Libraries use the root namespace `Bit.[Feature]` and an assembly name of `[Feature]`. Services use
the root namespace `Bit.Services.[Name]` and an assembly name of `[Name]`. This applies primarily to
net new code; when migrating existing code out of `Core`, retaining the existing namespace is