C# .NET 10 library implementing storage algorithms from Kleppmann's Designing Data-Intensive Applications. Clean architecture with Domain (interfaces) and Infrastructure (implementations) layers.
dotnet build # Build all projects
dotnet test # Run all 803 tests (743 infrastructure + 42 generator + 18 architecture)
dotnet test --verbosity normal # Verbose test output
dotnet format --verify-no-changes # Verify code formatting- TreatWarningsAsErrors enabled — zero warnings allowed
- ConfigureAwait(false) on all awaits in library code (src/), ConfigureAwait(true) in tests
- Nullable reference types enabled with strict warnings
- "Why" comments — explain constraints and design decisions, not what the code does
- MinVer for versioning — versions come from git tags (
v0.x.y), not .csproj - Apache 2.0 license with copyright headers on all source files
- TDD workflow — write failing test first, then implement fix, then verify green
- Sorted input validation — SSTable rejects unsorted input with
ArgumentException(fail-fast, not silent sort) - fsync for durability — WriteAheadLog uses
Flush(flushToDisk: true), notFlush() - Explicit endianness — on-disk formats use
BinaryPrimitiveswith little-endian, notBinaryWriter - Phase 0 interface families —
IObjectStore,ICheckpointStore,ISchemaRegistry,IEventLog<TEvent>,IChangeDataCaptureSource<TKey,TValue>(MarketData integration primitives) - Architecture tests in
tests/ArchitectureTests/enforce sealed classes, naming conventions, dependency rules, and namespace purity via NetArchTest - Central package management — Package versions managed centrally via
Directory.Packages.props - Public API tracking —
Microsoft.CodeAnalysis.PublicApiAnalyzerswired forBoutquin.Storage.DomainandBoutquin.Storage.Infrastructure. Each carriesPublicAPI.Shipped.txt(locked at release) andPublicAPI.Unshipped.txt(pending). Build fails (RS0016/RS0017) on any undeclared public API change
When you add, change, or remove public API:
- Build. The analyzer emits RS0016 (new symbol) or RS0017 (removed symbol).
- Run
dotnet format analyzers --diagnostics RS0016 RS0017in the affected project to auto-populatePublicAPI.Unshipped.txt. Removals are written as*REMOVED*Symbol.Name -> typeentries. - Commit the
PublicAPI.Unshipped.txtdelta alongside the code change. - At release time, move every line from
PublicAPI.Unshipped.txtintoPublicAPI.Shipped.txt(sorted, keep#nullable enableheader), then emptyUnshipped.txtback to just the header. This locks the new baseline. Done per packable project:src/Domain/andsrc/Infrastructure/.
Baselines are v1.1.0 (422 Domain symbols, 436 Infrastructure symbols). SourceGenerator is not tracked (not a shipped library — uses AnalyzerReleases.*.md for RS2000-family analyzer release tracking instead).
src/SourceGenerator/— RoslynIIncrementalGenerator(netstandard2.0) emitting serialization, comparison, and equality for[Key]and[StorageSerializable]record structs- Generator discovers attributes by FQN string (
ForAttributeWithMetadataName) — no project reference to Domain (avoids TFM mismatch) - Pipeline types (
TypeToGenerate,PropertyInfo, etc.) are plain value types — no Roslyn types allowed past the transform step (required for incremental caching) TreatWarningsAsErrors=falsein generator .csproj — netstandard2.0 Polyfill types generate CS1591 warnings we can't control- Generator is referenced as analyzer:
OutputItemType="Analyzer" ReferenceOutputAssembly="false" [Conditional("BOUTQUIN_STORAGE_GENERATOR")]on attributes — they vanish from compiled output- Record structs auto-synthesize
Equals/==/!=/IEquatable<T>— generator detectsIsRecordStructand skips these - Diagnostic rules BSSG001–BSSG006 in
DiagnosticDescriptors.cs;StorageDiagnosticSuppressorsuppresses CA1036/S1210
See ARCHITECTURE.md for interface hierarchy, LSM engine composition, data flow diagrams, and component navigation.
Key AI-relevant notes:
- Interface hierarchy splits on serialization (IComparable vs ISerializable) and bulk operations axes
ILsmStorageEngineextendsIBulkStorageEngine(notIBulkKeyValueStore) — usable anywhereIStorageEngineis expectedIBulkKeyValueStoreuses looseIComparableconstraints for in-memory data structures; file-backed engines addISerializableviaIBulkStorageEngine
SerializableWrapper<T>— Generic wrapper implementingISerializable<T>andIComparable, used as TKey/TValue in tests- Record structs for value objects (
FileLocation,SsTableMetadata) SemaphoreSlimfor async concurrency inLsmStorageEngine(non-reentrant — extract internal methods to avoid deadlock)ObjectDisposedException.ThrowIf(_disposed, this)on all public methods of disposable typesInterlocked.Exchangefor thread-safe dispose detection inWriteAheadLogGuard.AgainstNullOrDefaultfor null key/value validation at API boundariesNotSupportedExceptionforRemoveAsyncin RedBlackTree (append-only semantics — deletes are tombstones)ConcurrentKeyValueStore<TKey,TValue>— thread-safe in-memory KV store for caching viaConcurrentDictionary- Record structs for Phase 0 value objects (
SchemaVersion,SchemaField) - Sealed records for Phase 0 envelopes (
ChangeRecord<TKey,TValue>,EventEnvelope<TKey,TValue>,SchemaEnvelope<T>)
pr-verify.yml— Build, test, coverage, format check on PRs to mainpublish.yml— NuGet publish onv*tag push, with MinVer tag verification- NuGet API key stored as GitHub secret
NUGET_API_KEY - Pre-commit hook enforces
dotnet format --verify-no-changes