As much as possible, you need to leave a paper trail, i.e. you need to be able to prove to auditors (or your customers) that things are as you said they are. Thus, you must turn everything into a transaction that you can then record. The state of a business should be a function of all the transactions that happen to it. This transformation is not always natural, since some things (like depreciation or shrinkage) happen continuously, and not at discrete points in time like a transaction model implies. Nevertheless, this is standard business practice, and there are work-arounds for the problems that occur.
Therefore: represent the activity of the business by a set of transactions. The transactions correspond to paper documents (or electronic analogues) that you exchange with other people.
An inventory system has transactions like purchases, sales, and shrinkage. A payroll system has transactions like timecards, paychecks, and changes in salary. A bank has transactions like deposits, withdrawals, and interest payments.
You should SeparateTransactionProcessingFromArchival.
BusinessTransactions are transactions that differ from TransactionProcessing in that they don't have to be ACID compliant, they're often long-lived and the only way to 'roll back' is via AdjustingTransactions. In other words we can't undo what just happened to you but we can initiate a new BusinessTransaction that has the effect of cancelling out what we just did.
The notion of a BusinessTransaction has an origin in standard bookkeeping practices. In bookkeeping, the current balance of an account is always reconciled to a series of historical journal entries, each of which represents an adjustment to an account balance. This allows account balances to be audited, as well as tracking information about transactions that is worthwhile in itself.
For example, a common simplistic representation of a bookkeeping account, using an invented-on-the-fly object-oriented pseudocode, is as follows:
Account { balance = 0 adjust(amount) { balance = balance + amount } getBalance() { return balance } }The account balance is updated via a series of adjust() invocations. This works, but maintains no history of how the current balance was achieved. Therefore, in bookkeeping terms the balance is not auditable, and so the above is an unacceptable implementation. Good bookkeeping practice uses a journal to keep a history of adjustments, as follows:
Transaction { account adjustment // More details about the transaction should go here -- e.g., description, timestamp, transaction type, date, user, etc. Transaction(acct, amount) { account = acct adjustment = amount } getAccount() { return account } getAdjustment() { return amount } } journal = new Collection Account { adjust(amount) { journal.add(new Transaction(this, amount)) } getBalance() { balance = 0 for each journal transaction where transaction.getAccount() == this { balance = balance + transaction.getAdjustment() } return balance } }Reversing BusinessTransactions is done by adding AdjustingTransactions to the journal that are an inverse of the original transactions. For example:
someAccount.adjust(10.00) ...the above is determined to be incorrect, therefore... someAccount.adjust(-10.00)Now any account can be audited by examining the BusinessTransactions contained in the journal. Note that Account and Transaction are immutable ValueObjects; dynamic state is updated exclusively by adding entries to the journal. In the above example, there is a single journal. In some cases, a journal for each Account may be more appropriate.
In DoubleEntryBookkeeping, two (or more) accounts must be adjusted at a time. This represents the point where BusinessTransactions meet ACID-compliant TransactionProcessing, because it is necessary to ensure that multiple related adjustments are treated as an atomic, consistent, isolated, and durable unit. For example:
transactionManager.beginTechnicalTransaction() debitAccount.adjust(10.00) creditAccount1.adjust(5.00) creditAccount2.adjust(5.00) transactionManager.commitTechnicalTransaction()TransactionProcessing ensures that the series of adjustments either completes in its entirety, or effectively was never started. Not employing TransactionProcessing could result in only some of the adjustments successfully completing, which would throw the books out of balance. In real-world terms, this could mean a money transfer from my bank account that never appears in your bank account. Alternatively, the journal can be implemented so that each journal entry consists of (say) a single JournalEntry instance that contains multiple related adjustments. -- DaveVoorhis
Rather than put transaction wrappers around every action, it may be easier to use the database as the logger. If the DBMS does not have such built-in features, then one can add triggers to the necessary entities. I would hate to have to clutter up my app code with a bunch of transaction wrappers. Further, doing it in the DB allows multiple apps that share the same DB to have their activities logged without rewriting it for each language. Plus it probably has AcId capabilities. --top
True, though I'm not sure what you mean by "put transaction wrappers around every action," and the above was not intended to be an implementation (hence the pseudocode), but an abstract, conceptual illustration of the idea using syntax that most developers will appreciate, whether used with a database or not. Implement it however you like, as long as there is a distinction between the notions of Account and Transaction.
Also, I'm not clear what you'd gain by using a trigger, unless you're trying to hide transactions behind account adjustments. No proper bookkeeping system would do this. Something I didn't emphasise enough, above, is that Transaction is not merely a ChangeLog! A Transaction is a strong entity, and explicitly records facts about real-world business activities, such as sales, returns, auditable data-entry corrections (which may require human authorisation), issuing credit or debit memos, issuing invoices, etc. As such, I'm not sure how using it clutters the app code with "transaction wrappers," unless you're referring to TransactionProcessing rather than BusinessTransactions. In that case, there are numerous mechanisms to syntactically sweeten the use of a technical transaction. In bookkeeping terms, an accounting ledger is equivalent to Account; an accounting journal is equivalent to Transaction. -- DaveVoorhis
How formal a "transaction" needs to be is generally continous. For example, dealing with money exchanges often requires a high degree of formality. But changing a product description may not. We may just merely want to log the fact that it changed, what it was before, and who changed it. Stuff such as complex roll-back management may be overkill for that.
The above is not about changing product descriptions. It applies specifically to financial transactions or those related to the value of a business entity, which are a particular and unique category of record keeping typically known as "bookkeeping". -- DaveVoorhis
In that case, perhaps the title should be changed to "Financial Transaction". There is sometimes the business need to track changes other than financial info.
*Sigh* Top, quibbling over irrelevant minutiae is agonisingly tedious and utterly pointless. If you really think the title should be changed, feel free to change it, and make sure to fix the backlinks. Otherwise, go amuse yourself by doing something productive. -- DaveVoorhis
See AdjustingTransactions BusinessAccounts TransactionProcessing TransactionsAndAccounts ChangeLog