The four types
The repository is implemented as four collaborating types, each with a single clearly bounded responsibility.| Type | Kind | Responsibility |
|---|---|---|
IStateRepository | Interface | Domain-specific repository contract |
StateRepository | Class | Business logic and domain operations |
StateRepositoryDbContextFactory | Class | Database infrastructure and lifecycle management |
StateRepositoryDbContext | Class | Entity configuration and schema definition |
Relationships
IStateRepository
IStateRepository defines the domain-specific contract for the repository. It exposes operations in terms of Arius domain concepts — hashes, binary properties, and pointer file entries — hiding all persistence details from callers.
Handlers depend on IStateRepository, not on any concrete class, which allows the repository to be replaced with a test double in unit tests.
StateRepository
StateRepository is the concrete implementation of IStateRepository. It focuses entirely on business logic and domain-specific data access patterns. All database infrastructure concerns — connection management, migrations, lifecycle — are delegated to the factory.
Key operations
| Operation | Description |
|---|---|
GetBinaryProperty(hash) | Retrieve stored binary properties for a given hash. Returns null if not yet uploaded. |
UpsertPointerFileEntries(entries) | Insert or update pointer file entry records for a set of files. |
Vacuum | Delegate to the factory to run a SQLite VACUUM, compacting the database file. |
Delete | Delegate to the factory to delete the database file entirely. |
StateRepositoryDbContextFactory
- DbContext creation — constructs
StateRepositoryDbContextinstances with correct options. - Database lifecycle — manages
VacuumandDeleteoperations on the SQLite file. - Connection pool management — controls how SQLite connections are opened and closed.
- Change tracking — maintains a flag (surfaced through the
OnChangescallback) that records whether any write operation has occurred since the factory was created.
StateRepositoryDbContext
StateRepositoryDbContext is the EF Core DbContext. It owns:
- Entity configuration — defines how domain entities map to SQLite tables and columns.
- Value converters — converts domain value objects (like
Hash) to and from their database representations. - Schema definition — applies column constraints, indexes, and relationships.
- Change notification — calls the
onChangescallback wheneverSaveChangesorSaveChangesAsyncis invoked with actual modifications, propagating the signal up to the factory.
Separation of concerns
The three-class split is intentional:Why not put everything in one class?Mixing database infrastructure (connection strings, migrations, vacuuming) with business logic (what data to read and write) makes both harder to test and harder to change. By splitting responsibilities across
StateRepository, StateRepositoryDbContextFactory, and StateRepositoryDbContext, each class can evolve independently. A change to the EF Core configuration does not touch the business logic, and a change to a repository query does not touch connection management.| Concern | Owner |
|---|---|
| Domain queries and commands | StateRepository |
| EF Core and SQLite infrastructure | StateRepositoryDbContextFactory |
| Table schema and entity mapping | StateRepositoryDbContext |
Change tracking via OnChanges
TheOnChanges callback threads through all three types:
Factory registers the callback
When
StateRepositoryDbContextFactory is constructed it records an onChanges delegate and initialises a hasChanges flag to false.Factory passes callback to DbContext
Every time the factory creates a
StateRepositoryDbContext it passes the same onChanges delegate to the context constructor.DbContext fires the callback on save
After
SaveChanges or SaveChangesAsync completes with one or more affected rows, the context calls onChanges().