Tuesday, May 7, 2013

4 Tier IOC Application (Converted Tier Architecture)

Have you ever noticed that if the business logic entities adds a member then both the database and the views will be updated?  If the business logic entities adds functionality then just the view might need updating.  There are plenty of times when no functionality changes at all, but our view needs to be adjusted to enhance the user experience.  Doing an update to the view requires a complete update.  What if you want the same functionality, but you also have users who like the old archaic looking application instead?  It'd be like creating different skins depending on the user.

What if the database needs to be switched, but no other functionality should be changed?  What if you want to only move a few people (like a specific branch) over at a time?

A traditional n-tier application looks like this:
The presentation layer references the business logic layer.  The business logic layer references the data access layer and the data access layer connects to the database.  I am calling this the Converted Tier Architecture until someone notifies me that I'm wrong.  Now let me try and show you what I am trying to explain:

Here the views references the presentation layer (which I usually call the application layer).  The presentation layer references the business logic layer.  However, instead of the business logic layer referencing the data access layer, I have the data access layer reference the business logic layer.  Then the data access layer still connects to the database.

How this works in more detail is that the views are really just concrete classes that implement interfaces that are found in the presentation layer.  My IOC container will connect these two assemblies together.  I can have one or more view assemblies with different "skins" and they can be switched by a simple change in a configuration file.  I can mix and match multiple view assemblies and really have a lot of flexibility when it comes to determining the best user experience and can really personalize what the end user is viewing.  Now, of course you have to create multiple assemblies and I don't recommend making "skins" just to make a "skin", but it has advantages in work applications.  I can go to someone who uses my application and change their config, drop in the assembly and see how they react.  When I'm done I can either switch it back to the original assembly or leave it as is.

The presentation layer still references the business logic layer like usual.  The business logic layer no longer references the data access layer.  The data access layer references the business logic layer.  I use an interface that defines the connection (in this example it's a simple Linq2Sql data context).  My data layer class just needs to implement the below interface and the IOC container hooks up the business logic layer with the concrete data access layer class.

Code Snippet
  1.  
  2. Public Interface IDataAccess
  3.     Inherits IDisposable
  4.  
  5. #Region "Properties"
  6.     ''' <summary>
  7.     ''' Gets the get data context.
  8.     ''' </summary>
  9.     ''' <value>
  10.     ''' The get data context.
  11.     ''' </value>
  12.     ReadOnly Property GetDataContext As Data.Linq.DataContext
  13.     ''' <summary>
  14.     ''' Gets or sets the timeout.
  15.     ''' </summary>
  16.     ''' <value>
  17.     ''' The timeout.
  18.     ''' </value>
  19.     Property Timeout As Integer
  20. #End Region
  21. #Region "Functions"
  22.     ''' <summary>
  23.     ''' Gets the connection state of the underlying database connection.
  24.     ''' </summary>
  25.     ''' <returns>The <see cref="System.Data.ConnectionState" /> value of the underlying database connection.</returns>
  26.     Function ConnectionState() As System.Data.ConnectionState
  27.     ''' <summary>
  28.     ''' Opens the connection.
  29.     ''' </summary>
  30.     ''' <remarks>This only returns true if the function was able to connect.  If the connection was already established
  31.     ''' then <c>false</c> would be returned since the function did not connect when called.</remarks>
  32.     ''' <returns><c>true</c> if this functionality was able to connect; otherwise <c>false</c>.</returns>
  33.     Function OpenConnection() As Boolean
  34.     ''' <summary>
  35.     ''' Commits to the database using the specified mode.
  36.     ''' </summary>
  37.     ''' <returns><c>true</c> if successful; otherwise <c>false</c>.</returns>
  38.     Function Commit() As Boolean
  39.     ''' <summary>
  40.     ''' Commits to the database using the specified mode.
  41.     ''' </summary>
  42.     ''' <param name="mode">The <see cref="System.Data.Linq.ConflictMode" /> that the datacontext should follow.</param>
  43.     ''' <returns><c>true</c> if successful; otherwise <c>false</c>.</returns>
  44.     Function Commit(mode As System.Data.Linq.ConflictMode) As Boolean
  45.     ''' <summary>
  46.     ''' Removes any pending updates/inserts/deletes from the underlying data connection.
  47.     ''' </summary>
  48.     ''' <remarks>This is a rollback of any "Transactional" data on the underlying data context.
  49.     ''' This is not meant to be used on a multi-DataContext transaction.</remarks>
  50.     Sub Rollback()
  51. #End Region
  52. End Interface

Linq2Sql entities are created with a partial class declaration.  I create a new class with the same name (so they become the same concrete class) and I implement the interfaces found in the business logic layer.  My application layer which only knows of the business logic layer interface entities will end up getting a concrete class from the data access layer that it never needs to reference.

I will post a complete application with notes on using this tiered architecture at another time.  I'm still making it for my wife, but will create a trimmed down version for you to view soon.

3 comments:

  1. How do you pass your domain model objects to the Data Access Layer ? For e.g. if you have a "Customer" domain model object in your Business Layer and you have an "UpdateCustomer" method in you DataContext in the Data Access Layer, then how do you connect the two without defining methods in your interface ?

    ReplyDelete
    Replies
    1. All domain model objects have to have an interface in the business layer. In Linq2Sql or Entity, you would create a class with the same name as the entity generated (ensuring those are partial classes). When you instantiate the domain model you are instantiating the Linq2Sql or Entity. I usually pass along the data context to the model in the constructor. Then if the model changes I just need to commit that data context.

      I wrote a second article with more code as someone else asked a similar question. Eventually, I will submit an entire application.

      http://vbsoftwaredeveloper.blogspot.com/2013/05/converted-4-tier-architecture.html.

      Delete
    2. After re-reading your question I may have missed your point. All low level domain objects will basically mimic your database. They are instantiated in your data layer by using IoC. Higher domain objects will consist of one or multiple lower level domain objects. The tricky part is how to handle the data context in the higher levels for concurrency and transactions in all of the lower levels.

      Delete