The Database Is the Message Bus
Five executables, zero queues - how an attendance platform coordinated a web app, three Azure Functions and a console job through one SQL database, and what that choice bought and cost. Part 1 of the series.
In reading order.
Five executables, zero queues - how an attendance platform coordinated a web app, three Azure Functions and a console job through one SQL database, and what that choice bought and cost. Part 1 of the series.
A daily Azure Function mirrors the HR system's employee master into the attendance database - a field-by-field upsert, a clever delegation rule, a vestigial handshake, and the parse calls that can kill the whole run.
The leave sync resumes from wherever its own job log last said Success - one table serving as log, watermark and metrics store at once, with recursive pagination on top and an envelope check in exactly the wrong place.
Leave records arrive late, change and get cancelled - so the sync reaches back into already-approved attendance rows and re-derives them, carefully stepping around human decisions and allowance money.
Badge gates record when you arrived, not which shift you worked - so the platform infers it, scoring every candidate shift by L1 distance in time-of-day space and letting the smallest number win.
The shift calculator writes one report row per employee per day since 2019, inventing blank rows for days nobody badged - a dense date spine built from sentinel values, at the price of one database round-trip per row.
Four magic strings in an audit column decide which rows the batch jobs may overwrite and which belong to humans - provenance, workflow state and inter-job protocol smuggled into a field meant for names.
When one job needs another to redo work, it leaves a row - an action enum, a rewind date and a Done flag standing in for a message broker, plus the delete-and-replay proc that makes reruns possible.
A 22-character job name, a 20-character column, and an error handler that has thrown on every attempt since the day it shipped - plus the four parallel logging systems that still could not catch it.
Every five minutes a function hunts for people still virtually swiped in past ten hours and emails them - magic node numbers, an at-most-once flag with a gap in it, and a local config file pointing straight at production.
UTC timers, IST badge gates, DateTime.Now in one executable and UtcNow in the next, columns that record neither - how a system whose entire product is timestamps got away with never deciding what time it is.
The same database solves the same sortable-paging problem twice - once by concatenating user input straight into EXEC, once with a CASE whitelist, QUOTENAME and sp_executesql. A side-by-side of the wrong and right answer.
Thirty-eight tables, fourteen foreign keys, none on the tables that need them - plus a shipped SSMS template index, auto-tuning suggestions accepted blind, three-state booleans and a demo mode that mutates production master data.
One hierarchy, three implementations - a recursive CTE, a view that calls a scalar function on every row, and a whole family of tree procs - plus a relationship flag that is a decimal number pretending to be binary.
The business month runs the 16th to the 15th, so a year-overview proc carries twelve correlated subqueries, five-day-capped weeks, time averaged through float casts and contractor filters that drift by a digit between copies.
The same dashboard KPI is computed by three different formulas across role-variant procedures, rendered on a gauge with a sibling metric quietly deleted - and the fact table beneath it is an accidental Kimball star nobody designed.
Four pipelines run dotnet test against projects with no tests and pass every time, the shared model ships as committed .nupkg files in a folder, one executable has no pipeline at all, and a DLL with no source hides an algorithm pasted inline elsewhere.
The honest ledger for the whole estate - what coordinating through tables actually cost, what genuinely earned its keep, and what I would reach for now instead of using the database as a message bus.