Friday, May 24, 2013

Why Test Driven Development?

A popular buzzword is test driven development.  The thing that separates TDD from everything else is that you must write your unit tests first.  This confuses a lot of people.  They can't for the life of them understand the meaning behind this.  Many programmers simply believe that they are just too good for class.  In this case, TDD class.

These are the same individuals that say it's no problem to fix a bug in production.  They simply spend a few hours searching for the bug and then they spend another hour or two and fix the bug.  They hand this over to the QA team or someone who has to test the application and it's all done.  Just a few hours of waste.  It's no big deal, right?

Let's play with some numbers.  How much do you make an hour?  Do you think this is all that it's costing the company?  There are more costs associated with you than you think.  For instance, you have to have a desk, paper, printer, pens, markers, notepads, and obviously a computer.  If you work at a business that deals in other things besides software then you have to have the Microsoft Office Suite from like XP to the latest.  Visual Studio 2005, 2008, 2010, and 2012.  Maybe Team Foundation Server.  Then you have the test servers (they cost against you), and databases and etc.  This list can get quite long.

While many individuals cost roughly 150% of there salary, software developers are roughly around 250% or more.  So if you make $30 an hour, your actual cost is $75 an hour.  So if you waste 4 hours on a problem, you just cost the company $600.00.  How did I come up with that number?  $75.00 (per hour) * 4 hours = $300.00 + ($75.00 per hour * 4 hours you could have been working on something else) = $600.00.  After the four hours to fix the bug, then you have to submit it to QA or hand it around to people in the office to look at.  This takes some of there time and then you have to deploy the fix.  So by the time it's all said and done most are around the $5,000 to $10,000 range.  This number varies quite a bit and is really based on your company, so take those numbers with a grain of salt until you can figure it out.  I'm sure the price will be a lot higher than you expected, though.

The reason I came up with 4 hours for a minimum is because when communication occurs on a bug, your wasting twice as much time.  When the bug first goes off or is noticed, the individual must tell there manager what happened.  It may only take about 5 minutes, but it's 10 minutes total time wasted.  5 minutes of the individual and 5 minutes of the managers.  Then that manager will have to fill out a report or calls up support or someone in IT.  A 10 minute phone call wastes a total of 20 minutes.  Then the IT person notifies the manger and this just goes on and on until it finally comes to you.  It's so easy to jump to four hours even if the bug fix is a simple spelling correction.

If you have QA individuals who manually test your code then that cost rises astronomically.  Unfortunately, we don't have a QA where I work, so other developers and sometimes managers would have to help out with the manual testing.  They didn't think twice about the cost as it was something that just had to be done.  When I came along, no one ever thought twice about the cost of fixing all of those bugs.  However, when I tried to teach Test Driven Development to them, they saw the extra cost of <i>learning</i> within seconds.  "How much slower is development going to be?", they asked.  "So we have to write all of that extra code?  This is going to take a lot longer."

How do managers and developers not see the cost to fix the bugs after the deployment, but are so worried about the short additional time it takes to write unit tests?  I just don't understand it.  Do I need to scream at them?  I want to.

Now I'm done with that rant.  However, I never answered the question that I started with.  The whole reason to do test first is to make you see that your test fails.  This seems very trivial, but you would be amazed at how many times developers write a non-failing test.  Watching it turn green may also give you a deep warm fuzzy feeling.

The most important part is that you have to stop and think for a minute on what you want the production code to do.  You know that you have to keep your classes separated, so they can be easily testable.  It's so much harder to untangle the mess just to write a unit test.  It also becomes very boring and makes you lose your flow.

If you don't use TDD, you need to at least try it.  I don't mean a "check off the box" attempt at using test driven development.  You have to REALLY try it.  If you don't try it for at least a year then you didn't really try TDD.

Tuesday, May 7, 2013

Converted 4-Tier Architecture

This is a continuation article.  The original article can be found here.

The converted 4-tier architecture is about having flexible views and data access layers.  The view is simply a group of concrete classes that all implement something that is referenced in the presentation layer.  The presentation layer instantiates the concrete views (from a different assembly) by  using the IOC container.

In the presentation layer we'd create our IoC.  The presentation layer references the business logic layer, so it can see all of the interfaces.  So when our presentation layer needs an implementation of ICoupon, the IoC will retrieve the data access layers' implementation that is in a different assembly.

From Business Logic Layer:
Code Snippet
  1.  
  2. Public Interface ICoupon
  3.     Inherits CouponService.IEditableObject
  4.  
  5.     ''' <summary>
  6.     ''' Simple on and off states that allow simpler searches.
  7.     ''' </summary>
  8.     ''' <remarks>Instead of being required to validate every date in an open setting, this allows
  9.     ''' the class itself to handle that interpretation.</remarks>
  10.     Enum ExpirationState As Integer
  11.         NotExpired = 0
  12.         Expired = 1
  13.     End Enum
  14.  
  15.     ''' <summary>
  16.     ''' Gets the coupon id.
  17.     ''' </summary>
  18.     ''' <value>
  19.     ''' The coupon id.
  20.     ''' </value>
  21.     ''' <remarks>This is primarily for the database.</remarks>
  22.     ReadOnly Property CouponIdentification As Integer
  23.     ''' <summary>
  24.     ''' Gets or sets the item.
  25.     ''' </summary>
  26.     ''' <value>
  27.     ''' The item.
  28.     ''' </value>
  29.     ''' <remarks>The item property is simply a name given by the person who has the coupon.  This
  30.     ''' name doesn't have to be used and is purely for assistance in searching for specific coupons.</remarks>
  31.     Property CouponName As String
  32.     ''' <summary>
  33.     ''' Gets or sets the expiration.
  34.     ''' </summary>
  35.     ''' <value>
  36.     ''' The expiration.
  37.     ''' </value>
  38.     ''' <remarks>This is a set expiration date for the coupon.</remarks>
  39.     Property ExpirationDate As DateTime
  40.     ''' <summary>
  41.     ''' Gets the type of the expiration.
  42.     ''' </summary>
  43.     ''' <value>
  44.     ''' The type of the expiration.
  45.     ''' </value>
  46.     ''' <remarks>The expiration type has been added to allow a quick retrieval of getting a coupon
  47.     ''' that has or has not been expired.</remarks>
  48.     ReadOnly Property ExpirationType As ExpirationState
  49.  
  50.     ''' <summary>
  51.     ''' Gets or sets the quantity products required.
  52.     ''' </summary>
  53.     ''' <value>
  54.     ''' The quantity products required.
  55.     ''' </value>
  56.     ''' <remarks>This is the number of products that are required before the reduced price or the
  57.     ''' reduced percentage can take into affect on the transaction.</remarks>
  58.     Property QuantityOfProductsRequired As Integer
  59.     ''' <summary>
  60.     ''' Gets or sets the quantity products reduced.
  61.     ''' </summary>
  62.     ''' <value>
  63.     ''' The quantity products reduced.
  64.     ''' </value>
  65.     ''' <remarks>The quantity of products that are allowed to be reduced in price.</remarks>
  66.     Property QuantityOfProductsReduced As Integer
  67.     ''' <summary>
  68.     ''' Gets or sets at percentage.
  69.     ''' </summary>
  70.     ''' <value>
  71.     ''' At percentage.
  72.     ''' </value>
  73.     ''' <remarks>The percentage of the reduction of price.</remarks>
  74.     Property Percentage As Nullable(Of Integer)
  75.     ''' <summary>
  76.     ''' Gets or sets at amount.
  77.     ''' </summary>
  78.     ''' <value>
  79.     ''' At amount.
  80.     ''' </value>
  81.     ''' <remarks>The amount of the reduction in price.</remarks>
  82.     Property Amount As Nullable(Of Double)
  83.  
  84.     ''' <summary>
  85.     ''' Gets or sets the products required.
  86.     ''' </summary>
  87.     ''' <value>
  88.     ''' The products required.
  89.     ''' </value>
  90.     ''' <remarks>A list of products in which one or more may be required to have been bought in
  91.     ''' order to reduce the price.</remarks>
  92.     Property AvailableProductsRequired As IList(Of IProduct)
  93.     ''' <summary>
  94.     ''' Gets or sets the products reduced.
  95.     ''' </summary>
  96.     ''' <value>
  97.     ''' The products reduced.
  98.     ''' </value>
  99.     ''' <remarks>A list of products in which the price may drop for.  The product has to be bought
  100.     ''' for the coupon to take effect.</remarks>
  101.     Property AvailableProductsReduced As IList(Of IProduct)
  102.  
  103.     ''' <summary>
  104.     ''' Gets or sets the storage code.
  105.     ''' </summary>
  106.     ''' <value>
  107.     ''' The storage code.
  108.     ''' </value>
  109.     ''' <remarks>This is a way of allowing the end users to write a storage code or number.</remarks>
  110.     Property StorageInformation As String
  111.     ''' <summary>
  112.     ''' Gets or sets the required store.
  113.     ''' </summary>
  114.     ''' <value>
  115.     ''' The required store.
  116.     ''' </value>
  117.     ''' <remarks>Some coupons work only at certain stores.  This may be a requirement</remarks>
  118.     Property RequiredStore As IStore
  119.  
  120.     Sub GetCouponsByExpirationState(state As ExpirationState, callback As Action(Of IList(Of ICoupon)))
  121. End Interface

The data access layer:
Code Snippet
  1. Imports CouponService
  2. Imports CouponService.Naming
  3.  
  4. ''' <summary>
  5. '''
  6. ''' </summary>
  7. Public Class Coupon
  8.     Implements CouponService.ICoupon, CouponService.IEditableObject, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging
  9.  
  10.     Public Event IsNowClean(sender As Object, e As System.EventArgs) Implements CouponService.IEditableObject.IsNowClean
  11.     Public Event IsNowDirty(sender As Object, e As System.EventArgs) Implements CouponService.IEditableObject.IsNowDirty
  12.     Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
  13.     Public Event PropertyChanging(sender As Object, e As System.ComponentModel.PropertyChangingEventArgs) Implements System.ComponentModel.INotifyPropertyChanging.PropertyChanging
  14.  
  15.     Private _IsDirty As Boolean
  16.     Private _IsNew As Boolean
  17.     Private _IsEditable As Boolean
  18.  
  19.     Private CouponContext As CouponContext
  20.  
  21.     Public Sub New(dataContext As Data.Linq.DataContext)
  22.         CouponContext = dataContext
  23.         _IsEditable = True
  24.         _IsDirty = False
  25.         _IsNew = True
  26.     End Sub
  27.  
  28. #Region "IEditableObject Properties"
  29.     Public ReadOnly Property IsDirty1 As Boolean Implements CouponService.IEditableObject.IsDirty
  30.         Get
  31.             Return _IsDirty
  32.         End Get
  33.     End Property
  34.     Public Property IsDirty As Boolean
  35.         Get
  36.             Return _IsDirty
  37.         End Get
  38.         Set(value As Boolean)
  39.             If _IsDirty <> value Then
  40.                 _IsDirty = value
  41.                 If value Then
  42.                     RaiseEvent IsNowClean(Me, EventArgs.Empty)
  43.                 Else
  44.                     RaiseEvent IsNowDirty(Me, EventArgs.Empty)
  45.                 End If
  46.             End If
  47.         End Set
  48.     End Property
  49.  
  50.     Public ReadOnly Property IsEditable1 As Boolean Implements CouponService.IEditableObject.IsEditable
  51.         Get
  52.             Return _IsEditable
  53.         End Get
  54.     End Property
  55.     Public Property IsEditable As Boolean
  56.         Get
  57.             Return _IsEditable
  58.         End Get
  59.         Set(value As Boolean)
  60.             _IsEditable = value
  61.         End Set
  62.     End Property
  63.  
  64.     Public ReadOnly Property IsNew1 As Boolean Implements CouponService.IEditableObject.IsNew
  65.         Get
  66.             Return _IsNew
  67.         End Get
  68.     End Property
  69.     Public Property IsNew As Boolean
  70.         Get
  71.             Return _IsNew
  72.         End Get
  73.         Set(value As Boolean)
  74.             _IsNew = value
  75.         End Set
  76.     End Property
  77. #End Region
  78.  
  79.     Public ReadOnly Property CouponIdentification As Integer Implements CouponService.ICoupon.CouponIdentification
  80.         Get
  81.             Return Me.CouponID
  82.         End Get
  83.     End Property
  84.  
  85.     Public Property ExpirationDate As Date Implements CouponService.ICoupon.ExpirationDate
  86.         Get
  87.             Return Me.Expiration
  88.         End Get
  89.         Set(value As Date)
  90.             If value <> Me.ExpirationDate AndAlso IsEditable Then
  91.                 IsChanging(GetPropertyName(Function() ExpirationDate))
  92.                 Me.Expiration = value
  93.                 HasChanged(GetPropertyName(Function() ExpirationDate))
  94.             End If
  95.         End Set
  96.     End Property
  97.  
  98.     Public ReadOnly Property ExpirationType As CouponService.ICoupon.ExpirationState Implements CouponService.ICoupon.ExpirationType
  99.         Get
  100.             If Me.ExpirationDate < Now Then
  101.                 Return CouponService.ICoupon.ExpirationState.NotExpired
  102.             Else
  103.                 Return CouponService.ICoupon.ExpirationState.Expired
  104.             End If
  105.         End Get
  106.     End Property
  107.  
  108.     Public Property CouponName As String Implements CouponService.ICoupon.CouponName
  109.         Get
  110.             Return Me.Item
  111.         End Get
  112.         Set(value As String)
  113.             If value <> Me.Item AndAlso IsEditable Then
  114.                 IsChanging(GetPropertyName(Function() CouponName))
  115.                 Me.Item = value
  116.                 HasChanged(GetPropertyName(Function() CouponName))
  117.             End If
  118.         End Set
  119.     End Property
  120.  
  121.     Public Property StorageInformation As String Implements CouponService.ICoupon.StorageInformation
  122.         Get
  123.             Return Me.StorageCode
  124.         End Get
  125.         Set(value As String)
  126.             If value <> Me.StorageCode AndAlso IsEditable Then
  127.                 IsChanging(GetPropertyName(Function() StorageInformation))
  128.                 Me.StorageCode = value
  129.                 HasChanged(GetPropertyName(Function() StorageInformation))
  130.             End If
  131.         End Set
  132.     End Property
  133.  
  134.     Public Property RequiredStore As CouponService.IStore Implements CouponService.ICoupon.RequiredStore
  135.         Get
  136.             Return Me.Store
  137.         End Get
  138.         Set(value As CouponService.IStore)
  139.             If value.StoreId <> Me.Store.StoreID AndAlso IsEditable Then
  140.                 IsChanging(GetPropertyName(Function() RequiredStore))
  141.                 Me.Store = value
  142.                 HasChanged(GetPropertyName(Function() RequiredStore))
  143.             End If
  144.         End Set
  145.     End Property
  146.  
  147.     Public Property Amount As Double? Implements CouponService.ICoupon.Amount
  148.         Get
  149.             Return Me.AtAmount
  150.         End Get
  151.         Set(value As Double?)
  152.             If value <> Me.AtAmount AndAlso IsEditable Then
  153.                 IsChanging(GetPropertyName(Function() Amount))
  154.                 Me.AtAmount = value
  155.                 HasChanged(GetPropertyName(Function() Amount))
  156.             End If
  157.         End Set
  158.     End Property
  159.  
  160.     Public Property Percentage As Integer? Implements CouponService.ICoupon.Percentage
  161.         Get
  162.             Return Me.AtPercentage
  163.         End Get
  164.         Set(value As Integer?)
  165.             If value <> Me.AtPercentage AndAlso IsEditable Then
  166.                 IsChanging(GetPropertyName(Function() Percentage))
  167.                 Me.AtPercentage = value
  168.                 HasChanged(GetPropertyName(Function() Percentage))
  169.             End If
  170.         End Set
  171.     End Property
  172.  
  173.     Public Property AvailableProductsReduced As System.Collections.Generic.IList(Of CouponService.IProduct) Implements CouponService.ICoupon.AvailableProductsReduced
  174.         Get
  175.             Return Me.ProductsReduced.ToList
  176.         End Get
  177.         Set(value As System.Collections.Generic.IList(Of CouponService.IProduct))
  178.             If IsEditable Then
  179.                 IsChanging(GetPropertyName(Function() AvailableProductsReduced))
  180.                 Me.ProductsReduced.Clear()
  181.  
  182.                 For Each prod As CouponService.IProduct In value
  183.                     Me.ProductsReduced.Add(prod)
  184.                 Next
  185.                 HasChanged(GetPropertyName(Function() AvailableProductsReduced))
  186.             End If
  187.         End Set
  188.     End Property
  189.  
  190.     Public Property AvailableProductsRequired As System.Collections.Generic.IList(Of CouponService.IProduct) Implements CouponService.ICoupon.AvailableProductsRequired
  191.         Get
  192.             Dim ProductsToReturn As IEnumerable(Of IProduct) = From p In Me.ProductsRequired Select p.Product
  193.             Return ProductsToReturn.ToList
  194.         End Get
  195.         Set(value As System.Collections.Generic.IList(Of CouponService.IProduct))
  196.             If IsEditable Then
  197.                 IsChanging(GetPropertyName(Function() AvailableProductsRequired))
  198.                 Me.ProductsRequired.Clear()
  199.  
  200.                 For Each prod As CouponService.IProduct In value
  201.                     Me.ProductsRequired.Add(prod)
  202.                 Next
  203.                 HasChanged(GetPropertyName(Function() AvailableProductsRequired))
  204.             End If
  205.         End Set
  206.     End Property
  207.  
  208.     Public Property QuantityOfProductsReduced As Integer Implements CouponService.ICoupon.QuantityOfProductsReduced
  209.         Get
  210.             Return Me.QuantityProductsReduced
  211.         End Get
  212.         Set(value As Integer)
  213.             If value <> Me.QuantityProductsReduced AndAlso IsEditable Then
  214.                 IsChanging(GetPropertyName(Function() QuantityOfProductsReduced))
  215.                 Me.QuantityProductsReduced = value
  216.                 HasChanged(GetPropertyName(Function() QuantityOfProductsReduced))
  217.             End If
  218.         End Set
  219.     End Property
  220.  
  221.     Public Property QuantityOfProductsRequired As Integer Implements CouponService.ICoupon.QuantityOfProductsRequired
  222.         Get
  223.             Return Me.QuantityProductsRequired
  224.         End Get
  225.         Set(value As Integer)
  226.             If value <> Me.QuantityProductsRequired AndAlso IsEditable Then
  227.                 IsChanging(GetPropertyName(Function() QuantityOfProductsRequired))
  228.                 Me.QuantityProductsRequired = value
  229.                 HasChanged(GetPropertyName(Function() QuantityOfProductsRequired))
  230.             End If
  231.         End Set
  232.     End Property
  233.  
  234.     Private Sub IsChanging(propertyName As String)
  235.         RaiseEvent PropertyChanging(Me, New System.ComponentModel.PropertyChangingEventArgs(propertyName))
  236.     End Sub
  237.     Private Sub HasChanged(propertyName As String)
  238.         If IsEditable Then
  239.             IsDirty = True
  240.         End If
  241.  
  242.         RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs(propertyName))
  243.     End Sub
  244.  
  245.     Public Sub GetCouponsByExpirationState(state As CouponService.ICoupon.ExpirationState, callback As Action(Of IList(Of ICoupon))) Implements CouponService.ICoupon.GetCouponsByExpirationState
  246.         If state = ICoupon.ExpirationState.Expired Then
  247.             callback.Invoke(GetExpiredCoupons)
  248.         Else
  249.             callback.Invoke(GetNonExpiredCoupons)
  250.         End If
  251.     End Sub
  252.     Friend Function GetExpiredCoupons() As IList(Of ICoupon)
  253.         Dim CouponsToReturn As IEnumerable(Of Coupon) = From CurrentCoupon As Coupon In CouponContext.GetTable(Of Coupon)() _
  254.                                                         Where CurrentCoupon.Expiration < Now _
  255.                                                         Select CurrentCoupon
  256.  
  257.         Return MarkAsEditable(CouponsToReturn).ToList
  258.     End Function
  259.     Friend Function GetNonExpiredCoupons() As IList(Of ICoupon)
  260.         Dim CouponsToReturn As IEnumerable(Of Coupon) = From CurrentCoupon As Coupon In CouponContext.GetTable(Of Coupon)() _
  261.                                                         Where CurrentCoupon.Expiration >= Now _
  262.                                                         Select CurrentCoupon
  263.  
  264.         Return MarkAsEditable(CouponsToReturn).ToList
  265.     End Function
  266.  
  267.     Private Function MarkAsEditable(ByVal CouponsToMark As IEnumerable(Of ICoupon)) As IEnumerable(Of ICoupon)
  268.         For Each CurrentCoupon As Coupon In CouponsToMark
  269.             CurrentCoupon.IsNew = False
  270.             CurrentCoupon.IsEditable = True
  271.             CurrentCoupon.IsDirty = False
  272.         Next
  273.  
  274.         Return CouponsToMark
  275.     End Function
  276. End Class

Now this coupon class is a partial class that was generated using Linq2Sql.  In other words, my presentation layer is referencing the business logic layers' ICoupon interface which is implemented in the data access layer which is the same as my Linq2Sql entity.  I pass in a data context to the constructor.  Now this data context concrete class is also in the Data access layer and it implements the interface IDataAccess which is in my original article.

Here is the concrete Data Access class.
Code Snippet
  1.  
  2. Public Class DataAccess
  3.     Implements CouponService.IDataAccess
  4.  
  5.     Private _DataContext As CouponContext
  6.  
  7.     Public Sub New(connectionString As String)
  8.         _DataContext = New CouponContext(connectionString)
  9.     End Sub
  10.  
  11.     Public ReadOnly Property GetDataContext As System.Data.Linq.DataContext Implements CouponService.IDataAccess.GetDataContext
  12.         Get
  13.             Return _DataContext
  14.         End Get
  15.     End Property
  16.     Public Property Timeout As Integer Implements CouponService.IDataAccess.Timeout
  17.         Get
  18.             Return _DataContext.CommandTimeout
  19.         End Get
  20.         Set(value As Integer)
  21.             _DataContext.CommandTimeout = value
  22.         End Set
  23.     End Property
  24.  
  25.     Public Function Commit() As Boolean Implements CouponService.IDataAccess.Commit
  26.         Try
  27.             _DataContext.SubmitChanges()
  28.             Return True
  29.         Catch ex As Exception
  30.             Return False
  31.         End Try
  32.     End Function
  33.     Public Function Commit(mode As System.Data.Linq.ConflictMode) As Boolean Implements CouponService.IDataAccess.Commit
  34.         Try
  35.             _DataContext.SubmitChanges()
  36.             Return True
  37.         Catch ex As Exception
  38.             Return False
  39.         End Try
  40.     End Function
  41.  
  42.     Public Function ConnectionState() As System.Data.ConnectionState Implements CouponService.IDataAccess.ConnectionState
  43.         Return _DataContext.Connection.State
  44.     End Function
  45.     Public Function OpenConnection() As Boolean Implements CouponService.IDataAccess.OpenConnection
  46.         _DataContext.Connection.Open()
  47.     End Function
  48.  
  49.     Public Sub Rollback() Implements CouponService.IDataAccess.Rollback
  50.         For Each change As Object In _DataContext.GetChangeSet.Updates
  51.             _DataContext.Refresh(Data.Linq.RefreshMode.OverwriteCurrentValues, change)
  52.         Next
  53.         For Each change As Object In _DataContext.GetChangeSet.Inserts
  54.             _DataContext.Refresh(Data.Linq.RefreshMode.OverwriteCurrentValues, change)
  55.         Next
  56.         For Each change As Object In _DataContext.GetChangeSet.Deletes
  57.             _DataContext.Refresh(Data.Linq.RefreshMode.OverwriteCurrentValues, change)
  58.         Next
  59.     End Sub
  60.  
  61. #Region "IDisposable Support"
  62.     Private disposedValue As Boolean ' To detect redundant calls
  63.  
  64.     ' IDisposable
  65.     Protected Overridable Sub Dispose(disposing As Boolean)
  66.         If Not Me.disposedValue Then
  67.             If disposing Then
  68.                 ' TODO: dispose managed state (managed objects).
  69.             End If
  70.  
  71.             ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
  72.             ' TODO: set large fields to null.
  73.         End If
  74.         Me.disposedValue = True
  75.     End Sub
  76.  
  77.     ' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
  78.     'Protected Overrides Sub Finalize()
  79.     '    ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
  80.     '    Dispose(False)
  81.     '    MyBase.Finalize()
  82.     'End Sub
  83.  
  84.     ' This code added by Visual Basic to correctly implement the disposable pattern.
  85.     Public Sub Dispose() Implements IDisposable.Dispose
  86.         ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
  87.         Dispose(True)
  88.         GC.SuppressFinalize(Me)
  89.     End Sub
  90. #End Region
  91. End Class

Eventually, I will be putting the whole application online when it's completed.  If you have any questions please feel free to ask.  I have a total of 6 assemblies.  2 of them are for automated tests.  I have 1 that is testing the presentation layer that mocks the views and the DAL.  My other one tests the concrete DAL classes.

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.