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.

No comments:

Post a Comment