Technical overviewBackend engineering and financial systems

Daybook

The finance module of an ERP, where the bookkeeping rules hold when requests are retried, raced, or malformed.

Source code

The repository is private. The architecture, engineering decisions, and verified evidence are documented below.

Role
Solo engineer: domain model, API, SQL Server schema and tuning, Angular front end, Kubernetes deployment, tests, CI
Evidence status
Live at daybook.pesanth.com with a demo sign-in for each role; the books are rebuilt every night. Private repository; 222 .NET tests and 72 front-end tests pass in continuous integration.
Verified
2026-10-03

01

Purpose and scope

A double-entry general ledger in C# on .NET 10, with receivables, period close, bank reconciliation, and financial reports on top of it, an Angular front end, and SQL Server underneath. Every entry balances, the journal is append-only, and balances are derived from it. Each guarantee that depends on concurrency has a test that was run with the protecting code removed and seen to fail.

What it is used for

  • Post a journal entry from a client that retries on timeout, without posting it twice.
  • Invoice a customer, take part payments, and see what they owed on any past date.
  • Import a bank statement and have it matched against the books in the background.
  • Close a month so nothing can be posted into it afterwards.
  • C#
  • .NET 10
  • ASP.NET Core
  • EF Core
  • SQL Server
  • Angular
  • Kubernetes
  • GitHub Actions

02

Architecture

Client

Interface

Angular front end

14 screens, signals, no UI library

Entry point

Demo sign-in

One button per role

HTTPS · Cloudflare Tunnel

Edge

Routing

Traefik ingress

Rate limits keyed on the visitor’s address

Web server

nginx

Serves the build, proxies /api, strict CSP

JSON over HTTP · JWT

API

Service

ASP.NET Core API

Minimal endpoints, problem details with stable codes

Business logic

Domain library

Every bookkeeping rule, no dependencies

Background job

Statement worker

Claims statements from a queue table with a lease

EF Core · a login scoped to one database

Data

Database

SQL Server 2022 Express

decimal(19,4), check constraints, filtered unique indexes

Scheduled job

Nightly reset

Drops, migrates and reseeds the demo books

Text equivalent: The Angular front end is served by nginx, which proxies the API on the same hostname behind Traefik and a Cloudflare Tunnel. The API is ASP.NET Core minimal endpoints over a domain library that holds every bookkeeping rule and references nothing outside the base class library. EF Core maps it to SQL Server Express, which enforces the rules a second time with check constraints and filtered unique indexes. A background worker claims uploaded bank statements from a queue table and matches them.

03

Engineering decisions

Pick the concurrency mechanism per case

Idempotency keys race on a unique constraint, posts and a period close meet on a shared lock taken with HOLDLOCK, and invoices carry a rowversion token against double issue and overpayment. Posts never conflict with each other, only with a close, so the lock costs nothing in the common case.

Keep segregation of duties in the domain

Whoever drafted an invoice cannot issue it, even holding both roles. A route policy says what a role may do; only the domain knows who drafted, so the rule lives there and answers 403 segregation_of_duties.

Number invoices from a counter row

The number is taken inside the issuing transaction, so a failed issue gives its number back and the sequence has no gaps. A database sequence would not block, and it would leave gaps an auditor asks about. Issues serialise on that row.

Derive balances, and copy one column to make it cheap

Nothing stores a balance. The entry date is copied onto each line under a foreign key on the pair, so the copy cannot drift, and a balance becomes one index range seek starting from the totals frozen at the last close.

04

Verification evidence

  • Each concurrency test was run with its protection removed. Without the unique-violation handling, 24 simultaneous posts on one key and 16 simultaneous reversals both failed. Without HOLDLOCK, the period race test failed five runs of five. Without the rowversion token, all twelve concurrent payments of 30 were accepted against an invoice of 100.
  • One test calls every mapped route with no token. With the fallback policy removed and one route left unprotected, it failed and named GET /trial-balance.
  • On 2,000,000 journal lines, one account’s balance went from 41.7 ms and 38,810 logical reads to 1.3 ms and 11, median of nine runs on one desktop with a warm cache. An index that cut the trial balance’s reads by a factor of 24 made it no faster, so it was left out.
verified-2026-10-03
$ dotnet test
Ledger.Domain.Tests  113 passed
Ledger.Api.Tests     109 passed · SQL Server 2022 in a container
$ POST /journal-entries  (same Idempotency-Key, twice)
201 Created · then 200 with Idempotent-Replay: true
✓ one entry posted, trial balance in balance

05

Limitations