Skip to content

Commit 9b9cc35

Browse files
committed
Add ADR 32 about breaking up Core project
1 parent aa02024 commit 9b9cc35

1 file changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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

Comments
 (0)