writing-server-code

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Repository Orientation

仓库结构说明

The
server
repo contains:
  • src/Api
    — REST API endpoints
  • src/Identity
    — Authentication/identity service
  • src/Core
    — Business logic, commands, queries, services
  • src/Infrastructure
    — Data access, repositories
server
仓库包含以下内容:
  • src/Api
    — REST API端点
  • src/Identity
    — 身份验证/身份服务
  • src/Core
    — 业务逻辑、命令、查询、服务
  • src/Infrastructure
    — 数据访问、仓储

Architectural Rationale

架构设计原则

Command Query Separation (CQS)

命令查询分离(CQS)

New features should use the CQS pattern — discrete action classes instead of large entity-focused services. See ADR-0008.
Why CQS matters at Bitwarden: The codebase historically grew around entity-focused services (e.g.,
CipherService
) that accumulated hundreds of methods. CQS breaks these into single-responsibility classes (
CreateCipherCommand
,
GetOrganizationApiKeyQuery
), making code easier to test, reason about, and modify without unintended side effects.
Commands = write operations. Change state, may return result. Named after the action:
RotateOrganizationApiKeyCommand
.
Queries = read operations. Return data, never change state.
When NOT to use CQS: When modifying existing service-based code, follow the patterns already in the file. Don't refactor to CQS unless explicitly asked. If asked to refactor, apply the pattern only to the scope requested.
新功能应采用CQS模式——使用独立的操作类替代大型的实体聚焦服务。详情请见ADR-0008
Bitwarden采用CQS的原因: 代码库历史上围绕实体聚焦服务(如
CipherService
)发展,这类服务积累了数百个方法。CQS将其拆分为单一职责类(如
CreateCipherCommand
GetOrganizationApiKeyQuery
),使代码更易于测试、理解和修改,且不会产生意外副作用。
命令 = 写入操作。会更改状态,可能返回结果。命名以动作为准:
RotateOrganizationApiKeyCommand
查询 = 读取操作。返回数据,绝不会更改状态。
无需使用CQS的场景: 修改现有基于服务的代码时,遵循文件中已有的模式。除非明确要求,否则不要重构为CQS模式。若要求重构,仅在指定范围内应用该模式。

Caching

缓存

When caching is needed, follow the conventions in CACHING.md. Use
IFusionCache
instead of
IDistributedCache
.
Don't implement caching unless requested. If a user describes a performance problem where caching might help, suggest it — but don't implement without confirmation.
当需要使用缓存时,请遵循CACHING.md中的规范。使用
IFusionCache
而非
IDistributedCache
未经请求不要实现缓存。 如果用户描述了可能通过缓存解决的性能问题,可以提出建议,但不要未经确认就实现。

GUID Generation

GUID生成

Always use
CoreHelpers.GenerateComb()
for entity IDs — never
Guid.NewGuid()
. Sequential COMBs prevent SQL Server index fragmentation that random GUIDs cause on clustered indexes, which is critical for Bitwarden's database performance at scale.
实体ID请始终使用
CoreHelpers.GenerateComb()
——绝不要使用
Guid.NewGuid()
。顺序COMB可避免随机GUID在聚集索引上导致的SQL Server索引碎片,这对Bitwarden大规模数据库的性能至关重要。

Critical Rules

核心规则

These are the most frequently violated conventions. Claude cannot fetch the linked docs at runtime, so these are inlined here:
  • Use
    TryAdd*
    for DI registration
    (
    TryAddScoped
    ,
    TryAddTransient
    ) — prevents duplicate registrations when multiple modules register the same service
  • File-scoped namespaces
    namespace Bit.Core.Vault;
    not
    namespace Bit.Core.Vault { ... }
  • Nullable reference types are enabled (ADR-0024) — use
    !
    (null-forgiving) when you know a value isn't null; use
    required
    modifier for properties that must be set during construction
  • Async
    suffix on all async methods
    CreateAsync
    , not
    Create
    , when the method returns
    Task
  • Controller actions return
    ActionResult<T>
    — not
    IActionResult
    or bare
    T
  • Testing with xUnit — use
    [Theory, BitAutoData]
    (not
    [AutoData]
    ),
    SutProvider<T>
    for automatic SUT wiring, and
    Substitute.For<T>()
    from NSubstitute for mocking
这些是最常被违反的规范。由于Claude无法在运行时获取链接文档,因此将其内联在此:
  • 依赖注入注册使用
    TryAdd*
    TryAddScoped
    TryAddTransient
    )——防止多个模块注册同一服务时出现重复注册
  • 文件级命名空间 ——使用
    namespace Bit.Core.Vault;
    而非
    namespace Bit.Core.Vault { ... }
  • 启用可空引用类型(ADR-0024)——当确定值不为null时使用
    !
    (空原谅运算符);对构造时必须设置的属性使用
    required
    修饰符
  • 所有异步方法添加
    Async
    后缀
    ——方法返回
    Task
    时使用
    CreateAsync
    而非
    Create
  • 控制器操作返回
    ActionResult<T>
    ——不要返回
    IActionResult
    或裸
    T
  • 使用xUnit进行测试 ——使用
    [Theory, BitAutoData]
    (而非
    [AutoData]
    ),使用
    SutProvider<T>
    自动注入被测系统(SUT),使用NSubstitute的
    Substitute.For<T>()
    进行模拟

Examples

示例

GUID generation

GUID生成

csharp
// CORRECT — sequential COMB prevents index fragmentation
var id = CoreHelpers.GenerateComb();

// WRONG — random GUIDs fragment clustered indexes
var id = Guid.NewGuid();
csharp
// CORRECT — sequential COMB prevents index fragmentation
var id = CoreHelpers.GenerateComb();

// WRONG — random GUIDs fragment clustered indexes
var id = Guid.NewGuid();

DI registration

依赖注入注册

csharp
// CORRECT — idempotent, won't duplicate
services.TryAddScoped<ICipherService, CipherService>();

// WRONG — silently duplicates registration, last-wins causes subtle bugs
services.AddScoped<ICipherService, CipherService>();
csharp
// CORRECT — idempotent, won't duplicate
services.TryAddScoped<ICipherService, CipherService>();

// WRONG — silently duplicates registration, last-wins causes subtle bugs
services.AddScoped<ICipherService, CipherService>();

Namespace style

命名空间风格

csharp
// CORRECT — file-scoped
namespace Bit.Core.Vault.Commands;

// WRONG — block-scoped
namespace Bit.Core.Vault.Commands
{
    // ...
}
csharp
// CORRECT — file-scoped
namespace Bit.Core.Vault.Commands;

// WRONG — block-scoped
namespace Bit.Core.Vault.Commands
{
    // ...
}

Further Reading

扩展阅读