Sunday, February 8, 2009

uNHAddins Persistence Conversation – Part 2: Configuring the conversation

In the first post I showed how to configure the uNHAddins conversation. Now I will show how to use the PersistenceConversation aspects to manage a uNHAddins conversation.

A class which will rule a conversation should be marked as [PersistenceConversational] every public method that is part of a conversation should be marked as [PersistenceConversation].

In the example code we have the interface

public interface IModifyOrderModel
{
  PurchaseOrder FindOrderOrCreateNew(string number, DateTime dateTime);
  void Persist(PurchaseOrder order);
  void AbortConversation();
}
which is implemented by the ModifyOrderModel class:
[PersistenceConversational]
public class ModifyOrderModel : IModifyOrderModel
{

private readonly IOrderRepository orderRepository;

public ModifyOrderModel(IOrderRepository orderRepository)

{

this.orderRepository = orderRepository;

}

[PersistenceConversation(ConversationEndMode = EndMode.Abort)]

public void AbortConversation()

{ // Rollback the use case }

[PersistenceConversation]

public PurchaseOrder FindOrderOrCreateNew(string number, DateTime dateTime)

{

var order = orderRepository.GetOrderByNumberAndDate(number, dateTime.Date);

if (order == null)

{ order = new PurchaseOrder { Date = dateTime, Number = number }; }

return order;

}

   [PersistenceConversation(ConversationEndMode = EndMode.End)]
   public void Persist(PurchaseOrder order)
   {

orderRepository.MakePersistent(order);

   }
}

This class manages the order modification conversation. As you see we don’t have references to NHibernate at this point. We do have an IOrderRepository which is going to provide the data we need for the conversation.

The implementation of the repository in the example is very simple, one important thing to notice is that we are injecting an ISessionFactory:

public class OrderRepository : Repository<PurchaseOrder>, IOrderRepository
{

public OrderRepository(ISessionFactory sessionFactory) : base(sessionFactory) { }

public PurchaseOrder GetOrderByNumberAndDate(string number, DateTime dateTime)

{

 var query = Session.GetNamedQuery("GetOrderByNumberAndDate").SetParameter("number", number).SetParameter("dateTime", dateTime); 
 return query.UniqueResult<PurchaseOrder>();

}

}
The Repository<T> is a very thin class which gives minimum CRUD implementations using NH. The interesting part is how we are getting a session:
protected ISession Session
{
  get { return factory.GetCurrentSession(); }
}

From the doc: GetCurrentSession() Obtains the current session. The definition of what exactly "current" means controlled by the CurrentSessionContext impl configured for use

In our case if you remember from the first post we are using the uNHAddins ThreadLocalConversationalSessionContext, which in general is good enough for a Windows Forms Application.

You may have noticed a property ConversationalEndMode in the PersistenceConversation attribute, for now there are three options:

EndMode.Continue: Can start a conversation and lives it alive.

EndMode.End: Flushes the session and commits current transactions, then it disposes the session

EndMode.Abort: Disposes the session (without accepting the changes)

Basically this is it. In the example code you can find an application using MVP with this ideas. Feel free to comment / ask in the uNHAddins forum about it.

16 comments:

  1. Hi Gustavo, I know that what you are saying might be the way to go (I hear the same from Fabio and others), but I really don't get how you use that.
    If you define a method as:

    [PersistenceConversation(ConversationEndMode = EndMode.End)] public void Persist(PurchaseOrder order)

    ...that means that everytime a service saves a PurchaseOrder the transaction will be commited; but what if sometimes I want to do something else after saving the purchase order (updating other information, saving a notification, ...)

    Also, the abort method is wrong to me:

    PersistenceConversation(ConversationEndMode = EndMode.Abort)]public void AbortConversation()

    In DDD, you can and usually will have multiple repositories, and a service might want use more than one at the same time. All those repositories need to work together in the same transation to be reliable, but you put the AbortConversation in every repository as if they should work alone.

    Maybe I'm misunderstanding what a Conversation means to you.

    Thanks Gustavo!

    ReplyDelete
  2. Hi Diego, I define a conversation for every use case, so in one conversation i might define a Persist method which ends the conversation in another conversation there might not be an explicit persist method...
    I'm not a DDD expert, i think in DDD jargon these are not repositories but services.
    Regarding the abort method it is not in my repositories but in the services and i can rule there a transaction calling several repositories. The aborts will abort the whole transaction.
    In any case, I won't put an abort in every service, In most use cases i don't need a way to abort. I suggested this as an option to explicitly say i won't end the conversation, if i need to.

    ReplyDelete
  3. OK Gustavo, I read it carefully again and I got it this time. I got confused by the class xxxModel.

    Thanks, now I can say "Amazing post, very useful!" ;)

    ReplyDelete
  4. Hi Gustavo,

    I've brought your pattern to the attention of the Spring.net community and received some interesting feedback, asking 'what do you do when the data in the session becomes stale?'
    You can see the thread here: http://forum.springframework.net/showthread.php?t=5385

    Would highly appreciate an answer here or there... :)

    ReplyDelete
  5. In that thread you are talking about WinForm and the last post are talking about session-per-request; which is the request in WinForm ?
    The pattern allow you to work as session-per-request. BTW the management of StaleObjectStateException is an issue even in session-per-request pattern. There is not a common shared solution for StaleObjectStateException because, for example, "you can catch the exception, close the current
    session, and show the second user a new screen, allowing the user to manually
    merge changes between the two versions (using a new session)"; what we are doing in uNhAddIns is ensuring that the current session will be closed.

    ReplyDelete
  6. Hey Fabio,
    thanks for the prompt reply :)
    Knowing there is not a common shared solution for stale data issue, just gives us all the more reason not to have long lived sessions. Now, if that be the case for me, is there any difference between using, say, a transaction attribute on a method and using your [PersistenceConversation(ConversationEndMode = EndMode.End)] attribute?

    (we were referring to any sort of request actually; the idea is that the life duration of a session should stay as short as possible- to reduce the chance the data will get stale...)

    ReplyDelete
  7. Hi Nieve,
    Are all your applications multiuser applications where many users are changing the same row?
    Using EndMode.End for Winforms or using session-per-request in Web you can get a stale object exception anyway.
    If updating the same object is something you do a lot and you don't use pessimistic locks (because it is very pervassive) then you have to manage the conflicts.
    Using End all the time with conversations is rather confuisng.
    We are thinking about making a special case where you can declare a service where every call opens a session. I will update about it if we go for it...but in any case, the kind of problems produced by Stale objects is something you need to solve even if you are in web in session-per-request, or you end the session after every call, there for sure the chances are lower.

    ReplyDelete
  8. @Nieve
    well... in IT "less probability" is not enough.
    If you know something may happen you should manage it. How you are managing StaleObjectStateException today ?

    ReplyDelete
  9. @Fabio
    This is exactly one of the issues I'm considering currently. The approach of avoiding SOSE as much as possible makes sense to me as a first step, handling the SOSE itself is the next one (if you have any suggestions, I'll be more than grateful to hear them) :)

    Anyway, after reading what Mark Pollack had to say on the issue, I finally opened a spring jira and if it gets enough votes, I'll be more than happy to contribute in creating an aop support for your conversation-per-business-transaction. One thing Mark did brought to my attention is that an overall timeout to clean the information stored in the http session could be added.

    ReplyDelete
  10. @Nieve as i told in the Spring forum creating a SpringAdapter for uNHAddins shouldn't be a lot of work. Currently we only have a CastleAdapter, but the idea is to add some more. You are invited to contribute with it if you want to.
    It is not the same as managing it all with Spring but you can still use your IoC framework of preference but let uNHAddins manage the session.

    ReplyDelete
  11. Gustavo,
    yeah, it actually should be rather simple to implement in spring. I'll give it a look as soon as I've got the time.
    Will you consider adding a timeout property?

    ReplyDelete
  12. Why the conversation works so slowly ?
    I have ProductCrudModel method :
    [PersistenceConversation(ConversationEndMode = EndMode.Continue)]
    public IEnumerable[Product] FindByProductName(string name) {
    return productReadOnlyDao.FindByName(name);
    }

    This works very slowly.
    I was using CastleNhibIntegration and it works very quick for this type of request.

    ReplyDelete
  13. I've checked two version FindByProductName:

    1) with PersistenceConversation attribute (using ThreadLocalConversationalSessionContext, and Castle AOP)
    2) without PersistenceConversation attribute (simple using ThreadLocalSessionContext)

    The result is:
    1) NHibernate: SELECT bla-bla FROM products this_ WHERE this_.name like @p0; @p0 = 'Tex%'
    00:00:05.1625466

    2) NHibernate: SELECT bla-bla FROM products this_ WHERE this_.name like @p0; @p0 = 'Tex%'
    00:00:00.3615015

    Why, this difference is so much ?

    ReplyDelete
  14. @Rub && @Sa
    I suggest to post the question in the uNHAddins forum, preferably with a test logging the times, so we can check what is going on..
    http://groups.google.com/group/unhaddins

    ReplyDelete
  15. Hi Gustavo,

    looking at the example code I saw that you do not use lazy loading. At the end of a request a session is closed, however.

    What would I need to do if I use your example and want to use lazy loading deploying you architecture blue print with my business Model.

    I would be so interested to get a feedback from you.

    Thank you so much

    ReplyDelete
  16. Hi,

    there is another question comming up reading the code. There is a file called core.config which obviously takes care of IoC for the models and repositories.
    How do I know what to write as an id (e.g. product.repository) and lifestyle. And even more important: Repositories and Models do need exactly one parameter for the constructor. How do you pass them via IoC. in The code you just say IoC.Resolve IProductModel () which obviously gets the implementation by reading core.config. But what about the constructor parameter?

    ReplyDelete