views:

1327

answers:

5

So I've got this generic DAO thing going on and at face value it appears to be ok. It's basically modeled after the CaveatEmptor sample application from the Hibernate guys.

On top of that I have a business layer...the guts of the application. It's completely unaware of any specific DAO implementation.

Everything up to this point seems fine, until I start thinking about transactions. If transactions are left to the client to implement, then how in the world do I maintain the nice separation I've got going on between my layers? That is, I'm using Hibernate at the moment, and I don't really feel much like adding hibernate-specific transactions to my business layer code.

I could create a simple transaction interface with begin, commit, and rollback methods and pass an implementation to my business layer...but...I'm not sure...

So here is the challenge: can you recommend a way for me to do this without using the word Spring (or EJB, or any other additional framework)?

+1  A: 

Your right that the application is a good place to cordinate transactions as this allows composition of more complex actions implemented by various services / managers / or whatever you want to call them.

An easy solution is to define an ITransaction interface, and use some type of factory or DI to hide the actual ITransaction implementor from your application. I rolled my own like this in .net using nHibernate and essentially I have a base class that all my managers (A manager in this case contains business logic for a logical set of entities like Membership, Order which might use one or more repositories). My base class has a ITransaction BeginTransaction(), which dynamically creates a type based on a configuration file.

This class then works with nHibernate's Session to begin and commit transactions.

JoshBerke
+1  A: 

In the past I've put the transaction logic in the root DAO for a hierarchy of DAOs that match to a hierarchy of Objects in your model that represent a single solid entity in the system.

I.e., if you have and X that has many Ys, and you want to store and retrieve Xs and their Ys at the same time as a single compound object, then your DAO for X should also call the DAO for Y. Then you can put a transaction around everything in your add() and update() methods in the DAO for X - and even make the Y DAO package private to hide it from your main business logic. I.e., instead of business logic:

XDAO xDAO = new XDAO(conn);
xDAO.startTransaction();
boolean success = xDAO.add(x);
if (success)
    for (Y y : x.getYs()) {
        success = YDAO.add(y);
        if (!success) break;
    }
if (success)
    xDAO.commit();
else
    xDAO.rollback();

You would just have:

XDAO xDAO = new XDAO(conn);
xDAO.add(x);

(with the success / commit / rollback logic internal to that DAO)

However this doesn't cover every situation, and yours may be different (for example mine works with JDBC, I don't know how Hibernate works or if it is possible there).

JeeBee
+5  A: 

I remember that Martin Fowler advices to keep the control of the transaction in the business layer because transaction is a business problem. (If you design a BankAccount class, a transaction is part of the domain language).

You can try to implement a TransactionScope as in .NET it works something like that

using (TransactionScope ts = new TransactionScope())
{
  ...
}

It's the same thing as (not exactly but if you are a Java guy, it's more explicit to you)

TransactionScope scope = new TransactionScope();
try
{
 ...
 scope.Commit();
}
catch(Exception ex)
{
  scope.Rollback();
  throw;
}

To decouple your business layer from any DAO technologies you can add a TransactionFactory in your domain language, which return a ITransactionScope (an interface) that you have defined with a Commit and Rollback methods. This way your domain layer is not bound to your DAO layer, only a concrete implementation of TransactionFactory is.

ITransactionScope scope = transactionFactory.CreateTransaction();
try
{
 ...
 scope.Commit();
}
catch(Exception ex)
{
  scope.Rollback();
  throw;
}
Nicolas Dorier
Well, this works, but you quickly end up with thousands of duplicate lines of code, for any realistic business app.
Rogerio
@Rogerio, not every method in a business object is necessarily a transaction. If you end up with duplication, then maybe your domain is not well expressed, or you can find a better way to express it.
Nicolas Dorier
A: 

They usually fit outside the DAO, in a transaction demarcation layer.

cherouvim
+2  A: 

In a web app, what I do to demarcate transactions is to take advantage of the HTTP request/response cycle, where each atomic business operation executes in the scope of one of these cycles, in a single dedicated thread.

Whatever web framework is used (Struts, JSF, GWT, etc.), there typically exists a "seam" where transaction demarcation can be performed. In Struts, it can be a base Action class. In GWT, it can be a base RemoteServiceImpl class.

So, use that central point of access to open the transaction (before allowing the application-specific code to execute), and to terminate it with a commit if no exceptions bubbled up or a rollback otherwise (after the application-specific code was executed).

I applied this strategy extensively in a large and complex business web app, and it proved to work very well.

Rogerio