What you're looking for isn't impossible, but the validity of doing so is a bit questionable. I'll first give you a direct answer to your actual question, but I do suggest you reevaluate if this is a desirable solution.
What would be considered the better way forward?
The issue isn't a technical one, but one of figuring out what you want to do.
- You consider the payment and audit update as a single transaction.
- You consider the payment update and the audit update as separate transactions.
(1) is what you have now, your question is asking how to achieve (2).
So I'm a little bit stuck as to how I would implement such a mechanism without having multiple patterns in our domain layer.
I'm not seeing the technical difficulty here. A unit of work pattern does not prohibit having separate transactions. Every transaction has the same workflow: add all necessary changes, then commit the transaction.
There are two approaches here. The better approach depends on your specific scenario.
You can reuse the same unit of work (memory object) to do both the payment update and the audit update. All you need to make sure to do is to separate the two operations in code. Do all the payment update logic, commit, do all the audit update logic, commit.
You can use different units of work (memory objects), one for payments and one for auditing. This costs more overhead to juggle the two objects, but it makes it easier to prevent the payment/audit update logic to add its changes to the wrong transaction by mistake.
There are also cases where the updates can't be separated without creating other problems. For example, suppose this is your update logic:
Each of these handlers generates both payment and audit updates. If you were to reuse the same UOW object, you'd be forced to split the logic:
myUOW.Commit(); //Commit payment update
myUOW.Commit(); //Commit audit update
Not only is this uglier, it may also hinder performance if the methods are resource heavy, possibly causing you to have to repeat part of the work.
Instead, it's better to have two separate objects, as you can avoid needing to separate the logic:
I omitted the try/catches that you'd expect to see around the commits.
As a final mention, and possibly warning, I do want to pull focus to Telastyn's comment:
Frankly, if you're auditing anything involving money, it should rollback the actual transaction too if the audit fails.
Every accountant/financial employee shoudl shudder at the thought of you making changes to the data without registering your changes in a way that it can be retraced if needed. That is exactly what an audit is intended to do.
I highly doubt that anyone customer/business owner will approve of financial software that makes changes to balances without generating the necessary paper trail.
Secondly, I would expect that the audit data is generated automatically by the system whenever it records a change, as opposed to your business logic getting the autonomy to decide if it should add some audit updates.
Thirdly, while this may be more a semantical argument instead of a practical one, but if the audit update were truly optional, you should be referring to it as logging, not auditing.