More on Implementing an Interface in the UI, or the 7-layer system.
Matt Berther is doing something similar to what I was talking about in my previous post.
Where we differ is where to do the validation of the business rules. He's wrapping his IMyView in a class that knows how to perform the Business Rule validation. I didn't discuss my validation method in my last post, so I'll discuss it here:
Let's say I have a class that knows how to save an IPerson to a DataStore. The Save Method signature looks like:
Public Function Save(p as IPerson) as Boolean
If my Web page or UserControl implements IPerson, I can simply pass the entire page or UserControl to the Save method of my Data Access object. But where does the validation get done? It gets done during the save attempt. The UI layer does not have direct access to the Data Access layer. It has indirect access via a set of FACTORY objects. When the UI attempts to save, it does so through a FACTORY class that first performs validation of the Business Rules, and if everything is okay, continues with the save. If the validation fails, then a list of errors is returned to the UI via an event handler.
This separation of Business Rule Objects (policy) from Business Data Objects (data) and Data Access objects (persistence) allows for easily turning various business rules on and off. As new rules are added, old ones are removed, or existing ones change their optional status, only one layer has to be modified: the validation layer. Additionally, this layer can itself be stored in the Data Store and configured dynamically. The UI can and should perform its own validation, but if it doesn't, no harm is done to the underlying system.
Consider the following example. Assume an interface IPerson defined as follows:
Public
Interface IPerson Property IsACompany() As Boolean Property CompanyName() As String Property FirstName() As String Property MiddleName() As String Property LastName() As String Property Address() As IAddress Property Contact() As IContactInformation End
Interface
Public
Interface IAddress Property Address1() As String Property Address2() As String Property City() As String Property State() As String Property PostalCode() As String End
Interface
Public Interface IContactInformation
Property PhoneNumbers() As System.Collections.Specialized.NameValueCollection
Property EmailAddresses() As System.Collections.Specialized.NameValueCollection
End
Interface
Consider the following Business Rules: In IPerson, the LastName is required if it is, if fact, a person. The CompanyName is required if the IPerson is a company (anybody have a better name for IPerson that supports it being either a human or a company?)
My FACTORY Object might look like this:
Public
Class FactoryObject Public ReadOnly Property SecurityContext() As SystemUser ' snipped for brevity Public Function Save(ByVal p As IPerson) As Boolean Dim V As New IPersonValidator(p) Dim pm As New PersonManager(Me.SecurityContext) Return pm.Save(p, V) End Function End
Class The FACTORY Object simply instantiates the validator as a required parameter to the DataAccess Object's Save() method, hiding this complexity from the client code. The Validator Object might look like this:
Public
Class IPersonValidator Public ReadOnly Property Person() As IPerson ' snipped for brevity Public ReadOnly Property IsValid() As Boolean Get Return Me.ValidateNames End Get End Property Public Function ListErrors() As String() ' snipped for brevity
Protected Function ValidateNames() As Boolean Dim Result As Boolean = True If Me.Person.IsACompany Then Result =
Me.Person.CompanyName.Trim <> String.Empty Else Result =
Me.Person.LastName.Trim <> String.Empty End If Return Result End Function Public Sub New(ByVal p As IPerson) ' snipped for brevity End
Class And finally, the Save() method on the Data Access object might look like:
Public Function Save(ByVal p As IPerson, ByVal V As IPersonValidator) As Boolean Dim Result As Boolean = False If Not V.IsValid() Then 'Raise a ValidationError event 'snipped for brevity Else Result =
Me.SaveToDataStore(p) ' execute SQL to store IPerson in Database End If Return Result End Function I'd be interested in any feedback from the community on this approach. This is something that I've started doing recently and so far it's working out rather nicely. There may be pitfalls that I can't see yet, so if you see any major problems with this, ping me :-). You can ping me if you like it too :-P.