|
| 1 | +--- |
| 2 | +adr: "0032" |
| 3 | +status: "Proposed" |
| 4 | +date: 2026-06-25 |
| 5 | +tags: [server] |
| 6 | +--- |
| 7 | + |
| 8 | +# 0032 - Break up the Core project |
| 9 | + |
| 10 | +<AdrTable frontMatter={frontMatter}></AdrTable> |
| 11 | + |
| 12 | +## Context and problem statement |
| 13 | + |
| 14 | +The `Core` project in the `server` repository has grown into a catch-all library that almost every |
| 15 | +service depends on. This creates two significant problems: |
| 16 | + |
| 17 | +- **Unowned code and unbounded dependencies** — while much of `Core` is owned by specific teams, a |
| 18 | + significant portion has accumulated without clear ownership, making it a dumping ground for shared |
| 19 | + utilities and dependencies that don't have a better home. |
| 20 | +- **Limited independent deployability** — because every service depends on `Core`, any change to |
| 21 | + `Core` technically constitutes a change to every service. This makes it hard to reason about the |
| 22 | + blast radius of a change and undermines the ability to deploy services independently with |
| 23 | + confidence. |
| 24 | + |
| 25 | +`GlobalSettings` compounds these problems. It is a single configuration class that houses settings |
| 26 | +for every feature and service, meaning all of `Core`'s consumers must load and be aware of the |
| 27 | +entire settings surface even when they only need a small slice of it. As features are extracted from |
| 28 | +`Core`, they should define their own strongly-typed options classes rather than growing |
| 29 | +`GlobalSettings` further. |
| 30 | + |
| 31 | +Not all of what currently lives in `Core` needs to move into feature-scoped libraries. A number of |
| 32 | +cross-cutting concerns — feature flag evaluation, version info endpoints, security middleware, and |
| 33 | +caching — are being built into the server SDK. As those SDK packages mature, the corresponding code |
| 34 | +in `Core` can be removed and replaced with an SDK dependency, further shrinking `Core`'s footprint. |
| 35 | + |
| 36 | +Other Bitwarden repositories have already moved toward a feature-scoped library model. The |
| 37 | +`sdk-internal` repository was built on this pattern from the start and follows it fully. The |
| 38 | +`clients` repository has historically had a large `libs/common` package, which is still being |
| 39 | +gradually decomposed into feature-scoped libraries. These precedents validate the approach for |
| 40 | +`server` as well. |
| 41 | + |
| 42 | +## Considered options |
| 43 | + |
| 44 | +- **Keep `Core` as-is** — No structural changes. `Core` continues to grow as a shared monolith. |
| 45 | + Ownership and deployment problems persist. |
| 46 | +- **Break `Core` into feature-scoped libraries** — New code is placed in dedicated, feature-scoped |
| 47 | + projects. Platform-level utilities (push notifications, mailing, database foundations) are |
| 48 | + extracted first as shared dependencies. Existing code is migrated gradually and opportunistically. |
| 49 | + |
| 50 | +## Decision outcome |
| 51 | + |
| 52 | +Chosen option: **Break `Core` into feature-scoped libraries**. |
| 53 | + |
| 54 | +New code belonging to a specific feature or domain should live in its own dedicated project rather |
| 55 | +than in `Core`. Platform-level utilities that many features depend on — such as push notifications, |
| 56 | +mailing, and database foundations — should also be extracted into their own projects, as these |
| 57 | +represent cross-cutting infrastructure rather than feature logic. |
| 58 | + |
| 59 | +In conjunction with [ADR 0031](./0031-adopt-minimal-apis.md), a single library can cover a feature |
| 60 | +end-to-end: repositories, settings, services, and endpoints all in one `.csproj`. There is no |
| 61 | +requirement to separate endpoints into their own project. The goal is feature cohesion, not a |
| 62 | +mandated split between endpoint code and business logic. |
| 63 | + |
| 64 | +Feature libraries live under `src/Libraries/[Feature]`. If a library later graduates into its own |
| 65 | +deployable container, it moves to `src/Services/[Name]`. This extends the `src/Libraries` directory |
| 66 | +structure introduced in [ADR 0031](./0031-adopt-minimal-apis.md). |
| 67 | + |
| 68 | +``` |
| 69 | +src/ |
| 70 | + Libraries/ |
| 71 | + Mailer/ # settings, services, repositories, endpoints for the Mailer feature |
| 72 | + Push/ |
| 73 | + Vault/ |
| 74 | + ... |
| 75 | + Services/ |
| 76 | + Api/ # composes libraries into a deployable service |
| 77 | + Identity/ |
| 78 | + Notifications/ |
| 79 | + ... |
| 80 | +``` |
| 81 | + |
| 82 | +Libraries use the root namespace `Bit.[Feature]` and an assembly name of `[Feature]`. Services use |
| 83 | +the root namespace `Bit.Services.[Name]` and an assembly name of `[Name]`. This applies primarily to |
| 84 | +net new code; when migrating existing code out of `Core`, retaining the existing namespace is |
| 85 | +acceptable if it keeps breaking changes to a minimum. |
| 86 | + |
| 87 | +The `Core` project will remain during the migration period and code should be moved out |
| 88 | +opportunistically, when a team is already working in that area, rather than in a dedicated |
| 89 | +large-scale migration effort. The long-term goal is to eliminate `Core` entirely. As the migration |
| 90 | +progresses, teams will negotiate the boundaries that should exist between them and create the |
| 91 | +libraries needed to share code across those boundaries. |
| 92 | + |
| 93 | +### Positive consequences |
| 94 | + |
| 95 | +- Clear ownership — teams own their feature project's `.csproj` and control their own dependencies |
| 96 | +- A change to a feature library only affects services that actually depend on it, restoring the |
| 97 | + ability to reason about and deploy services independently |
| 98 | +- Aligns `server` with the patterns already established in `clients` and `sdk-internal` |
| 99 | +- Complements [ADR 0031](./0031-adopt-minimal-apis.md), which establishes feature-scoped endpoint |
| 100 | + libraries for minimal API endpoints |
| 101 | + |
| 102 | +### Negative consequences |
| 103 | + |
| 104 | +- `Core` and feature-scoped libraries will coexist for a long time, requiring developers to know |
| 105 | + where to place new code and where to look for existing code |
| 106 | +- Extracting code from `Core` carries a risk of introducing circular dependencies if the dependency |
| 107 | + graph is not carefully considered during a migration |
| 108 | +- Without an aggressive timeline, the migration may stall and the benefits will be slow to |
| 109 | + materialize |
| 110 | + |
| 111 | +### Plan |
| 112 | + |
| 113 | +- New features should not add code to `Core`; they should create or extend a feature-scoped project |
| 114 | +- New libraries should generally sit at a lower level than `Core` and should not depend on it; if a |
| 115 | + new library needs something that currently lives in `Core`, that is a signal that the dependency |
| 116 | + itself should be extracted into its own library first |
| 117 | +- Platform-level utilities (push notifications, mailing, database foundations) should be prioritized |
| 118 | + for extraction as independent projects, since many features will depend on them |
| 119 | +- Cross-cutting concerns already being built into the server SDK — feature flag evaluation, version |
| 120 | + info endpoints, security middleware, and caching — should be adopted from the SDK as those |
| 121 | + packages become available, allowing the corresponding `Core` code to be deleted rather than |
| 122 | + migrated |
| 123 | +- Feature libraries should define their own strongly-typed options classes rather than adding |
| 124 | + properties to `GlobalSettings`; as features are extracted, their `GlobalSettings` entries should |
| 125 | + migrate alongside them |
| 126 | + |
| 127 | + **Before:** |
| 128 | + |
| 129 | + ```csharp |
| 130 | + // Core/Settings/GlobalSettings.cs |
| 131 | + public class GlobalSettings : IGlobalSettings |
| 132 | + { |
| 133 | + public virtual MailerSettings Mailer { get; set; } = new MailerSettings(); |
| 134 | + |
| 135 | + public class MailerSettings |
| 136 | + { |
| 137 | + public string ReplyToEmail { get; set; } |
| 138 | + public string SmtpHost { get; set; } |
| 139 | + // ... |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + // Consuming service |
| 144 | + public class Mailer(GlobalSettings globalSettings) : IMailer |
| 145 | + { |
| 146 | + public void Send() |
| 147 | + { |
| 148 | + var host = globalSettings.Mailer.SmtpHost; |
| 149 | + } |
| 150 | + } |
| 151 | + ``` |
| 152 | + |
| 153 | + **After:** |
| 154 | + |
| 155 | + The feature library declares its options type and consumes it via `IOptions<T>`. It does not bind |
| 156 | + configuration itself — that is the host service's responsibility, since the host owns its service |
| 157 | + settings and knows which configuration sections map to which feature options. |
| 158 | + |
| 159 | + ```csharp |
| 160 | + // Libraries/Mailer/MailerSettings.cs |
| 161 | + public class MailerSettings |
| 162 | + { |
| 163 | + public string ReplyToEmail { get; set; } |
| 164 | + public string SmtpHost { get; set; } |
| 165 | + // ... |
| 166 | + } |
| 167 | + |
| 168 | + // Libraries/Mailer/ServiceCollectionExtensions.cs |
| 169 | + public static IServiceCollection AddMailers(this IServiceCollection services) |
| 170 | + { |
| 171 | + services.TryAddSingleton<IMailer, Mailer>(); |
| 172 | + return services; |
| 173 | + } |
| 174 | + |
| 175 | + // Libraries/Mailer/Mailer.cs |
| 176 | + internal class Mailer(IOptions<MailerSettings> settings) : IMailer |
| 177 | + { |
| 178 | + public void Send() |
| 179 | + { |
| 180 | + var host = settings.Value.SmtpHost; |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + // Services/Api/Program.cs |
| 185 | + builder.Services.Configure<MailerSettings>(builder.Configuration.GetSection("Mailer")); |
| 186 | + builder.Services.AddMailers(); |
| 187 | + ``` |
| 188 | + |
| 189 | +- Existing code in `Core` should be moved out opportunistically when a team is already working in |
| 190 | + that area — not as a standalone task |
| 191 | +- A guide will be written documenting the conventions for creating a new feature library and the |
| 192 | + expected project structure, similar to the `ENDPOINT_LIBRARY.md` described in |
| 193 | + [ADR 0031](./0031-adopt-minimal-apis.md) |
0 commit comments