Skip to content

Architecture

This page is for readers evaluating or extending the codebase rather than using the application.

Four projects, with a strictly one-directional dependency chain:

┌──────────────────┐
│ Docsentry.Web │ MVC controllers, Razor views, Identity pages,
│ │ /api/v1 controllers, background job wiring
└────────┬─────────┘
│ depends on
┌────────v──────────────────┐
│ Docsentry.Infrastructure │ Dapper repositories and query services,
│ │ storage, previews, OCR, antivirus, email,
│ │ workflows, the Identity EF Core context
└────────┬──────────────────┘
│ depends on
┌────────v─────────────────┐
│ Docsentry.Application │ DTOs, service interfaces, validation and
│ │ pagination contracts, exceptions, use cases
└────────┬─────────────────┘
│ depends on
┌────────v─────────────┐
│ Docsentry.Domain │ Enums, value objects, permission masks,
│ │ workflow state rules. No dependencies.
└──────────────────────┘

Nothing points back up the chain. Docsentry.Domain references no other project and no infrastructure package - it holds pure rules, which is why the permission evaluator can be tested without a database.

The most important structural decision in the system, and the one most likely to be violated by accident:

  • Entity Framework Core is used only for ASP.NET Core Identity. A single DbContext owns the AspNet* tables - users, roles, claims, logins - and nothing else.
  • Every other table is reached through Dapper, with repositories for writes and query services for reads.

There is no DbContext for document management tables and no Entity Framework migrations for them. Adding one would break the deployment model described below.

Identity is a solved problem with a supported Entity Framework implementation; re-implementing it would be pure risk. The document management schema is the opposite case: it is the system’s centre of gravity, it carries stored procedures, computed permission tables, full-text indexes, and least-privilege grants, and it must be reviewable and deployable by a database administrator independently of an application release. Expressing that through a code-first migration chain would put the schema’s real complexity somewhere it cannot be reviewed.

HTTP request
v
Controller ----------> never executes SQL, never opens a connection
v
Application service -> use-case logic, validation, authorisation checks
v
Repository (writes) / Query service (reads)
v
Dapper ────----------> parameterised SQL against SQL Server

Controllers translate HTTP to a call and a result back to a response. That is all. A controller that opens a connection or writes SQL is a defect regardless of whether it works.

These are enforced by tests in Docsentry.Application.Tests, so a violation fails the build rather than surviving review.

Convention Rule
Cancellation Every asynchronous controller, service, repository, storage, and SQL operation accepts and forwards a CancellationToken.
API shape API controllers are [ApiController], live under versioned routes (/api/v1/...), and return DTOs via ActionResult<T>.
DTOs Request and response bodies are sealed records. Database row models, Identity entities, and Dapper internals are never exposed.
Time DateTimeOffset throughout.
SQL Always parameterised. No string concatenation with user input.
MVC Page controllers return views or redirects; API controllers return DTOs. The two are not mixed.

The document management schema ships as numbered, immutable, forward-only SQL scripts under Database/deploy/, with a checksum ledger and a runner contract. Applied scripts are never edited; corrections ship as new higher-numbered scripts. There is no rollback.

The application runs as a least-privilege principal that cannot alter schema, verified at startup. Full detail in Database deployment.

Work that does not belong in a request - preview generation, text extraction, notification delivery, retention, integrity verification, permission rebuild - runs on scheduled jobs. There are no hand-rolled timer hosts; everything goes through the job runner, which persists its state in the database and provides an administrator-gated dashboard.

This is why an uploaded document may briefly show no thumbnail: the upload request stores the file and returns, and rendering happens shortly afterwards. See Background jobs.

Path Contents
src/Docsentry.Domain/ Enums, value objects, permission masks, workflow state rules.
src/Docsentry.Application/Abstractions/ Service interfaces, grouped by domain.
src/Docsentry.Application/Options/ Strongly typed options and their validators.
src/Docsentry.Infrastructure/Data/ Connection factory, session and transaction runner, Dapper helpers, keyset paging.
src/Docsentry.Infrastructure/Repositories/ Write-side Dapper implementations.
src/Docsentry.Infrastructure/Queries/ Read-side query services.
src/Docsentry.Infrastructure/Identity/ The one Entity Framework context, plus Identity services.
src/Docsentry.Web/Controllers/ MVC controllers.
src/Docsentry.Web/Controllers/Api/ Versioned JSON API controllers.
src/Docsentry.Web/BackgroundJobs/ Job registration and runners.
Database/deploy/ Numbered forward-only schema scripts.
Database/verification/ Post-deployment contract and integrity checks.
tools/Docsentry.Backup/ The encrypted backup command-line tool.
Project Responsibility
Docsentry.Domain.Tests Pure domain rules.
Docsentry.Application.Tests Contract and architecture assertions - the conventions above.
Docsentry.Infrastructure.Tests Data access, paging, service behaviour, and the “no Entity Framework in DMS data access” invariant.
Docsentry.Web.Tests In-memory host: routing, authorisation gates, localisation, rendering, design-system conventions.
Docsentry.Backup.Tests Backup cryptography, manifests, and destinations.
Docsentry.Database.IntegrationTests Requires a live SQL Server.
Docsentry.Web.AcceptanceTests Browser-driven, opt-in, deliberately outside the solution so a normal build never pulls in browser downloads.