Blog
Business systems engineering
What we learn building and operating real systems — bugs included. No textbook theory: every note comes from a system in production.
Saying no to a feature (with an alternative)
Sometimes a request is reasonable from the business side and destructive to the model. The instinct is to explain why it's wrong. Separating the request from the solution works better.
Quote, order and invoice are not the same document
The tempting model is one document with a status that advances: quote, then order, then invoice. It collapses the moment the business does something normal.
"How many are in stock?" is a question with several answers
Available to sell, physically present, or committed to someone: they diverge constantly, and treating them as one number is how a salesperson sells something that left the warehouse yesterday.
Reversal is not deletion
Reversing a payment is not deleting a payment. That distinction is most of what separates a business system from a CRUD app — and 'we'll add undo later' is rarely true.
A payment is allocated, not assigned
The natural first model is a payment with a foreign key to an invoice. It survives until the first customer pays a round number that covers two invoices and part of a third.
A cheque is a state machine
Most systems model a cheque as a row with a status field. That works until someone needs to reverse a state — and then you discover the model was never the field.
Twenty minutes watching someone use the system
The highest-value thing I've done for a product I maintain wasn't technical: it was watching someone use it for twenty minutes without saying anything. What you find is never in a ticket.
One table that sees every company, on purpose
In a system where every table is scoped to a company, the membership table can't be. That's not a leak — it's the feature. The documented exceptions to the isolation rule.
A price isn't a number: it's a recorded decision
The field is called 'price' and it holds a decimal. That's the simplification that makes quotes impossible to explain three months later.
Who gets the last cent?
Everyone knows not to store money in a float. Fewer systems have decided what happens to the last cent — which is where the actual arguments come from.
The current account is a ledger, not a number
A balance shouldn't be a number you maintain: it should be a number you can reproduce. The ledger model, and why the stored balance is just a cache.
Estimating changes to code that touches money
The code is usually a day. What surrounds it — understanding current behaviour, pinning it with tests, validating with the business, the deploy window — is the estimate.
Explaining technical debt to a non-technical client
The hardest conversation isn't about price: it's asking for two weeks to change something the client will never see. Explained as risk with a number, it stops being a request for faith.
Invoices are never deleted
A user asks to delete an invoice. The correct answer is that you can't — and understanding why teaches you most of what's different about accounting software.
The tenant filter is a safety net, not a floor
Hibernate's tenant filter is a safety net. Treating it as the mechanism is how you get one company's data on another company's screen.
A lock was held — and I still lost a payment
A SELECT ... FOR UPDATE was doing its job perfectly, and a payment still went missing. The lock wasn't the problem. The isolation level was.