locked
Transaction Control in 3-Tier Architecture Using .NET2 RRS feed

  • Question

  • Hi,

    I'm looking at developing a solution based around the three tier architecture model, with PL, BL, and DL. At this point I'm concentrating on the BL and DL. I do not want to tie the app to a single DL implementation, as I would like in the future to be able to use an XML data store as well as an RDBMS.

    I'm confused about how transaction control fits into the 3 tier model. For example, say I have an action performed in my BL which affects two different entities which don't have a relationship in the business model (say object A and object B). I want this action to be atomic, so either the changes to both or neither are stored.

    As the objects are not related in the business model, I would not be passing a single object to the DL, it would be done as two separate calls. So my question is, how can I wrap these operations in a single transaction, without coupling the BL to the DL? I am reluctant to use TransactionScope as my application may have to run in an environment where MS DTC cannot be used.

    Does this situation indicate that my object model has been designed incorrectly?

    Should I consider implementing a single method in the DL that persists both items, and perform the transaction control in the DL itself?

    Or am I going completely off track here?

    Any comments much appreciated,

    Kind regards
    Thursday, June 19, 2008 9:05 PM

All replies

  • Hi,

    Personally I would design this so you have a "Business Process" entity or method that is basically the root of the transaction.  So you'd do something like this in your method:

    1. Setup all the data you require to persist.
    2. Start the transaction.
    3. Call your DL methods or BL methods that invoke the DL (depending on how you structure your code).
    4. Commit/Rollback the transaction.

    I don't believe using TransactionScope forces you to us MS DTC if you are writing to a single database.  I recently put some code together using TransactionScope on a server that didn't have the MS DTC setup and I had no problems.

    I'm not sure how you've put your code together but you could decouple the DL from the BL using one of the following patterns:

    1. The business process method invokes the DL objects, this is more of a Transaction Script pattern.
    2. Use a factory to decouple the DL implementation and have the BL methods call the factory.  That way the BL doesn't care how it's getting/saving data.

    The nice thing about letting the business method handle the transaction is that you can compose your BL & DL calls however you need to for future requirements without having lots of nested transactions, which could happen if you control the transactions from the DL.

    Hope that helps!
    Thursday, June 19, 2008 10:19 PM
  • It's the without DTC that's the tricky bit. There are some good light-weight transaction mechanisms, esp. around SQL 2008, but it sounds like you may have different stores so I think that's going to be a real problem. Without developing (or using someone elses) two-phase commit mechanism I'm not sure how you'd solve it. What problems do you see where you won't be able to use DTC (and then XA) to support your transactions?

     

    Friday, June 20, 2008 6:37 AM
  • This MSDN article states how System.Transaction transactions are esclated into MS-DTC transactions:

    http://msdn.microsoft.com/en-us/library/ms229978.aspx

    If you are talking to a single datasource then you don't need to worry about MS-DTC.  Why do you think you may not be able to use MS-DTC in a distributed transaction scenario?
    Friday, June 20, 2008 6:54 AM
  • Hi All,

    Thanks for your comments.

    Firstly, the reason why I think there are some instances where I may not be able to use DTC. I'm aiming to be able to implement the solution on Linux, using Mono and MySQL. At this stage, I haven't got my Linux test environment set up, so I haven't been able to prove whether this will work using the System.Transactions namespace, but I have a feeling it won't. In 99% of cases Windows Server 2003 & SQL Server 2005 will be used, but I don't want to do something that will block use of this solution in the other 1%.


    As for how the BL and DL are presently decoupled. I have an interface for my DL methods, and a factory which creates concrete implementations of this interface, which the BL calls, e.g.

    BL.GetProduct(productId)
    {
    IProductRepository concreteRepos = DLFactory.CreateProductRepository()
    return concreteRepos.GetProduct(productId)
    }

    (This is not an exact sample, just a demonstration of the logic used)



    I have looked at one method which would allow me to perform multiple actions for the same type of business entity within a transaction without "mentioning" transactions specifically in the BL, i.e.

    BL.MakeSomeChanges()
    {
    using (IProductRepository concreteRepos = DLFactory.CreateProductRepository())
    {
    concreteRepos.OpenRepository()
    try
    {
    concreteRepos.MethodA()
    concreteRepos.MethodB()
    concreteRepos.CloseAndSave()
    }
    catch
    {
    concreteRepos.CloseAndUndo()
    throw
    }
    }
    }



    This doesn't handle the situation where I need to make changes to two or more repositories though. I did consider some kind of "token" system, but I think that could get a bit messy. Something like:

    BL.MakeSomeMoreChanges()
    {
    using (IRepositoryToken token = DLFactory.CreateToken())
    {
    IProductRepository concreteProdRepos = DLFactory.CreateProductRepository()
    ISkuRepository concreteSkuRepos = DLFactory.CreateSkuRepository()

    token.Start()
    try
    {
    concreteProdRepos.MethodA(token)
    concreteSkuRepos.MethodA(token)
    token.Complete()
    catch
    {
    token.Cancel()
    }
    }
    }



    The IRepositoryToken interface might look like:

    interface IRepositoryToken : IDisposable
    {
    Start()
    Cancel()
    Complete()
    }



    A concrete DbRepositoryToken might look like:

    class DbRepositoryToken : IRepositoryToken
    {
    DbConnection _conn
    DbTransaction _trans

    internal DbConnection ActiveConnection
    {
    get { return _conn; }
    }

    internal DbTransaction ActiveTransaction
    {
    get { return _trans; }
    }

    IRepositoryToken.Start()
    {
    _conn.Open()
    _trans = _conn.BeginTransaction()
    }

    IRepositoryToken.Complete()
    {
    _trans.Commit()
    _conn.Close()
    }

    IRepositoryToken.Cancel()
    {
    _trans.Rollback()
    _conn.Close()
    }

    // IDisposable Methods...
    }

    Then an actual database method might look like:

    DbProductRepository.MethodA(IRepositoryToken token)
    {
    DbRepositoryToken = dbToken as DbRepositoryToken
    DataAccessUtility.ExecuteCommand(commandName, dbToken.ActiveConnection, dbToken.ActiveTransaction)
    }



    Then there could be, for example, a FlatFileRepositoryToken:

    FlatFileRepositoryToken : IRepositoryToken
    {
    MemoryStream _mStream;

    public Stream WriteStream
    {
    get { return _mStream; }
    }

    IRepositoryToken.Start()
    {
    _mStream.Open()
    }

    IRepositoryToken.Cancel()
    {
    // discard the memory stream
    _mStream.Close()
    }

    IRepositoryToken.Complete()
    {
    // write the contents of the memory stream to the file
    FileStream fs = GetFileStream()
    fs.Write(_mStream.ReadToEnd())
    }
    }

    This would be used as:
    FlatFileRepository.MethodA(IRepositoryToken token)
    {
    FlatFileRepositoryToken ffToken = token as FlatFileRepositoryToken
    Data.Write(ffToken.WriteStream)
    }



    Obviously, these examples are a bit contrived, and do not reflect the actual business objects in my model etc, but as for the structure, what do you guys think? Does this seem like a ridiculous way of doing things?

    It's times like this where it is tempting to take the easy way out...
    Friday, June 20, 2008 8:46 AM
  • Rogeclub, unfortunatley the position isn't quite a simple as it seems (although the link you provide does detail the problem too). As soon as you use 2 connections to SQL 2005 the transaction will be promoted to DTC, regardless of the fact it is the only durable store used. I logged this as a bug since I couldn't understand why MS couldn't fix it, but now SQL 2008 has resolved this issue. So if you use SQL 2008 you can fly under the DTC radar. An alternative is use ConnectScope workaround which acts a sort of transaction + connection manager, all a bit nasty for me but hey it works.

     

    Back to this post, it doesn't look good at the moment as I don't believe mono supports a distributed transaction manager so if you're hosting on linux this might be a problem. I don't know this for certain my experience with mono is very limited. Hopefully there might be something around XA that help. One possiblity would be to use a java based managed and provide a .net manager around that? Keep us posted how you get on 'cause it sounds do-able but I just know how - sorry.

     

     


     

    Friday, June 20, 2008 9:10 AM
  • No problem! Like yourself, I've not much experience with Mono, I think over the weekend I will try and get my test system set up and see what happens, I will let you know how it goes.

    I hadn't considered using a Java based manager though, definitely something I'm going to have to spend a bit of time looking into.
    Friday, June 20, 2008 9:28 AM
  • Hi,

    It sounds like you are talking to one db (hope I'm correct here).  Therefore wouldn't it be possible to use an implementation of the Repository pattern that was scoped to the HttpContext.  This would then manage the connection and transaction handling.

    e.g. (psuedo-code)

    public void MyProcessMethod()
    {
    using(DbScope scope = new DbScope()) //This would register a connection with the underlying repository
    {
    scope.BeginTran()
    //Invoke the Dal either directly or via the BL
    } //The Dispose call would clean up the underlying connection
    }

    The DAL would then talk to the underlying repository to get hold of the active connection:

    public void MyDalMethod()
    {
    IDbConnection con = DbScopeRepository.GetCurrentDbScope().GetConnection() //DbScope opens it when required

    //Do some work.
    }

    As there would only be one connection shared across the context of the call, wouldn't TransactionScope then be a viable option?
    Friday, June 20, 2008 1:50 PM
  • The example shows 2 different durable stores, the DB and the flatfile. However, the mechanism you describe is essentially the ConnectionScope mechanism Microsoft recommend as the workaround for SQL 2005...so yes it would work Wink

     

    Friday, June 20, 2008 1:56 PM
  • Yeah sorry, I skimmed past the flat file part.

    What I was mentioning wasn't really about transaction control, it was more about connection control, i.e. only having 1 connection to the same database in the scope of the business process context.

    I'd assume for flat files you'd have to roll your own transactional support (e.g. edit an in-memory copy), so an interface similar to what you have seems ligitimate.


    Friday, June 20, 2008 2:47 PM
  •  Rogeclub wrote:
    Yeah sorry, I skimmed past the flat file part.  

    Heh heh, not surprised, that was a long post...


     Rogeclub wrote:
    I'd assume for flat files you'd have to roll your own transactional support (e.g. edit an in-memory copy), so an interface similar to what you have seems ligitimate.


    That's what the Memory Stream was for


    I like that DbConnectionScope; I think I'm going to take what I can from that and blend it with the token concept to make the scope unspecific to DbConnections - something like "IRepositoryScope".

    I will post my solution here when I have implemented it.

    Thanks for your help!
    Friday, June 20, 2008 3:43 PM
  • Have a read of Transactional File System as it may give you some good tips, good luck,

    Saturday, June 21, 2008 8:19 AM