Announcement Announcement Module
Collapse
No announcement yet.
Declarative Transaction Management & Exception Handling Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Declarative Transaction Management & Exception Handling

    I would like to discuss an issue I find it could be relevant to the way applications are designed, in terms of layers.

    It's already assumed that the best and simplest layer architecture for a web application (and in general) should be made of three layers: MVC, business and data access layer. Also nearly everyone agrees that the transactional issues should affect to the business layer, and not the data access layer (due, for example, to the fact that many times several data access objects, or even business objects, are involved within a transaction).

    From this point of view, if we use Spring's declarative transaction manager to inject transactionality in our application using Hibernate, we have to do something similar to:

    <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor .TransactionProxyFactoryBean">
    <property name="transactionManager">
    <ref local="transactionManager" />
    </property>
    <property name="target">
    <ref local="myBOTarget"></ref>
    </property>
    <property name="transactionAttributeSource">
    <value>
    com.deltar.common.bo.BO.load*=PROPAGATION_REQUIRED ,readOnly
    com.deltar.common.bo.BO.save*=PROPAGATION_REQUIRED
    com.deltar.common.bo.BO.delete*=PROPAGATION_REQUIR ED
    </value>
    </property>
    </bean>

    As you can see, this fragment of code indicates that whenever a method called "load*" in MyBOTarget class may be executed by the application, transactionality must be applied through a Dynamic Proxy.

    This works great, you must know for sure, but I have come with an inconvenience I will like to discuss next.

    The inconvenience is about exception handling. As declarative transactionality should be applied to business objects (BO) in the business layer, the following phases are executed:

    1) A method call is invoked on MyBOTarget
    2) The proxy receives the method call and then (we suppose it is a transactional method):
    2.1) Starts a transaction (we suposse no transaction was active yet)
    2.2) Delegates the method call to MyBOTarget
    2.3) Ends the transaction (usually the transaction manager does a commit)
    3) The method call ends

    If you take a look at this step-by-step list, if an exception is thrown on step 2.3), which occurs out of the scope of the business object, i.e. the exception cannot be captured by the MyBOTarget object, then the exception will be propagated to the upper layer, i.e. the MVC layer.

    So let's suppose that the commit operation throws an exception due to required fields that were NULL. If so, then we will end up with a DataAccessException leaving the data access layer, bypassing the business layer (as we cannot capture it due to the proxy), and finally getting to the MVC layer. In my opinion, this situation breaks completely with the architecture and is not desirable at all.

    And here comes my question:

    How can we solve this inconvenience? Should we add one more layer between the business layer and the data access layer, so the transactionality be applied in this new layer, and let the old business layer handle this "special" exceptions in something similar as a fašade?

    Looking forward to hearing your feedback about this issue.

    ==========
    Enrique Medina

  • #2
    1) A method call is invoked on MyBOTarget
    2) The proxy receives the method call and then (we suppose it is a transactional method):
    2.1) Starts a transaction (we suposse no transaction was active yet)
    2.2) Delegates the method call to MyBOTarget
    2.3) Ends the transaction (usually the transaction manager does a commit)
    3) The method call ends
    The proxy purpose is to wrap the method declared inside a transaction so to be accurate the phases are executed in this order:
    1), 2.1), 2), 2.2), 3), 2.3)
    How can we solve this inconvenience? Should we add one more layer between the business layer and the data access layer, so the transactionality be applied in this new layer, and let the old business layer handle this "special" exceptions in something similar as a fašade?
    Proxies do not "blind" your code - if you want to handle an exception from the DAO layer you can very well put a try/catch block when calling it. Have you tried that?
    You can also specify which exceptions cause the rollback of your transactions so you can still commit the transaction if some 'small' exceptions are thrown.
    The exception thrown by the dao layer in the standard manner are RuntimeException so that you aren't forced to use a try/catch block - ofc, if you want to you are allowed to do it (and not add any additional layer).

    Comment


    • #3
      Thanks for your comments costin.

      But anyway I think my question was in another direction. I'll try to explain it further.

      The problem is with the scope of the exception handling, i.e. who (which layer) should catch the exceptions thrown by the transactional proxy. From my point of view, following the well-known three layered architecture (MVC - BO - DAO), business objects (BOs) just can't catch those exceptions, because it is the proxy that wraps the BOs the one who throws them, so the BOs just don't get to know about them at all!

      The same situation happens regarding to the DAOs. As they are invoked by the BOs, and the BOs are wrapped by the transactional proxy, neither the BOs nor the DAOs will ever be able to catch exceptions thrown by the proxy.

      So I came up to the fact that the solution would be to create fašades that would delegate calls to the BOs (really to the proxy), and would serve as a central point to catch the exceptions thrown by the transactional proxy.

      What do you think of this architecture? Does it make sense?

      Comment


      • #4
        Can you pls detail on the exceptions that are thrown by the transaction proxy? Transactions are done with AOP - your code should not be aware that there is a transaction going on or not.
        If you have exceptions related to the database then because these are fatal you can simply bail out and display a big red message on the screen (UI tier). Regarding facades, there is always a need to have some piece of code that is spread across different domain objects and usually you place that inside a facade.

        I personally don't like to divide the application right from the beginning - the line between facades, BOs and DAOs depends a lot on your application - there are many times where code accessing the db will be inside the facade (like my case) because we are trying to squeeze out performance in our intensive computations. Thus we cannot clearly separate the facades from BO . We can refactor the code to go into the DAO but we'll make our algorithm chuncky and less optimize.

        Comment


        • #5
          The exact problem is about commiting an object with null values whereas in the database there are not-null constraints for the correspondent columns.

          Calling the HibernateTemplate does not produce any exception, since the object is fine (Hibernate does not check for nulls when updating an object). But then, I realized from my JUnit class that an exception was being thrown somewhere out of the scope of my business logic. I then discovered that it was the proxy calling the Hibernate's commit method the responsible for this exception being thrown. More precisely, it is wrapped in a HibernateSystemException.

          Comment


          • #6
            I think in your case the problem occurs because the transaction is commited after the method ends (IIRC, the transaction manager sets the HB session on FlushMode.COMMIT and after the method has been executed it does a session.flush()).
            If as you say, you are calling the BO from the UI then the exception is caught inside the UI tier. Having an intermediate layer might help here - it depends on your application and architecture.
            In my case the UI has to do the validation so saving an object with null values on a db with non-null restriction represents a workflow/validation problem which should not be in production - we display a nasty error message and send an email back to us - it's a bug.
            What I'm trying to say is that you should consider your application and the types of exceptions that might be thrown - there are unrecoverable exceptions (like this one) and recoverable ones (which you expect and can handle gracefully).
            Hope it helps.

            Comment


            • #7
              the exception cannot be captured by the MyBOTarget object, then the exception will be propagated to the upper layer, i.e. the MVC layer
              This is almost right - you can always do a flush in your DAO to force any DB exception to be thrown.

              This is something to be aware of which I've posted here.

              Comment

              Working...
              X