0

I have a question about what the best design pattern to use would be. I have 2 specific scenarios, the first one fits neatly into a Unit of Work(UoW) pattern. The second is a little bit more fiddly. Basically, in certain business scenarios, I have a need to save some data even if all others information fails to save due (reasons not including database server going down).

For example, we get information about people having paid balances on their account. So the first table we save information to is the Payments table, before moving on to saving other entities including audit rows. However, we consider the audit rows to be less important than having a record inserted about the payment having been made. Using the traditional UOW pattern, the rollback would not allow us to commit the Payment record either.

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 have also considered having the important records be their own UoW and then the audit records as a separate UoW.

What would be considered the better way forward?

Our basic stack is C#, MVC, Entity Framework and Autofac

Any guidance would definitely be appreciated.

  • 11
    Frankly, if you're auditing anything involving money, it should rollback the actual transaction too if the audit fails. – Telastyn Jul 26 '16 at 19:25
  • 2
    Possible duplicate of Choosing the right Design Pattern – gnat Jul 26 '16 at 20:27
  • The better way forward is always to first make it work and then make it better. The business isn't waiting for a Mona Lisa, they just want paint on the walls. – Wim Ombelets Jul 26 '16 at 21:01
  • 1
    Using the traditional UOW pattern, the rollback would not allow us to commit the Payment record either -- It's either one unit of work, in which case this condition is completely valid; or it's two units of work, and you can commit the payment record separately. – Robert Harvey Jul 26 '16 at 22:09
  • 1
    Other than a network/database failure or a user-rights issue, I have trouble seeing why the payment record would succeed and the audit record fail. I would definitely bundle them into one unit of work with a transaction that committed or rolled them back together. This is not just some random database tables. This is the core principles of double-entry accounting. – Mike Jul 27 '16 at 18:19
0

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.

  1. You consider the payment and audit update as a single transaction.
  2. 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.

1
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.

2
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:

FooHandler.Update(myUOW);
BarHandler.Update(myUOW);

myUOW.Commit();

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:

FooHandler.UpdatePayments(myUOW);
BarHandler.UpdatePayments(myUOW);

myUOW.Commit(); //Commit payment update

FooHandler.UpdateAudit(myUOW);
BarHandler.UpdateAudit(myUOW);

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:

FooHandler.Update(myPaymentUOW, myAuditUOW);
BarHandler.Update(myPaymentUOW, myAuditUOW);

myPaymentUOW.Commit();
myAuditUOW.Commit();

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.

-2

If you are using UOW/Generic repository pattern you can include a WCF layer to fire a save and forget when your payment transaction fires on UnitOfWork.SaveChangesAsync(). This pattern worked for my auditing but had to be built into a separate thread as to not affect the application.

  • While the answer you've given is an interesting idea for implementation (and I agree that automatic auditing is nicer than manually having to do it), it is not actually an answer to the question that is asked. – Flater Aug 20 '18 at 9:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.