Thursday, March 23, 2006

Microsoft doesn't do Encapsulation.

Microsoft has a big problem with encapsulation. It's tools don't support it, it's advice goes against it and your average VB.NET programmer has little idea what it means. I'm currently working on an enterprise application, written in VB.NET and working alongside a team of VB.NET developers. Now, back in a past life, I too was a VB developer (although with a strong interest in Java and C++) and I wrote applications around recordsets, but over the last three years, since I started doing C#, I've been infected by reading lots of Martin Fowler, Eric Evans, Rocky Lhotka and the Domain-Driven-Development agile crowd. Now I want to write Domain-Driven object-oriented applications. I've managed to steer my current team away from datasets but I just can't get them to understand why encapsulation is good and that's mainly because Microsoft don't seem to get it either. So what do I mean? Take the XML Serializer that's used by web services. In order to serialize an object, it has to be written in a particular way. It has to have a default public constructor and all its properties have to be gettable and settable. Here's an example Person object that can be XML serialized:
class Person
{
 string _name;
 int _age;

 public Person()
 {
 }

 public string Name
 {
  get{ return _name; }
  set{ _name = value; }
 }

 public int Age
 {
  get{ return _age; }
  set{ _age = value; }
 }
}
So what's the problem? Well, say we want to have a business rule, such as 'a person's name must be at least have at least one character. How do we enforce this? Well we can write some validation on the Name property setter:
public string Name
{
 get{ return _name; }
 set
 {
  if(value == null) throw new ArgumentNullException();
  if(value.Length == 0) throw new ApplicationException("Name must be at least one character");
  _name = value; 
 }
}
But that doesn't stop a developer writing:
Person myPerson = new Person();
and never setting the Person property. Now you have an invalid person object floating around in your application. You've effectively given up the power that object oriented development gives you to create classes that enforce their rules. Business rules about a Person are now leaking into the rest of the application. Without a parameterised constructor, and read only properties, you have to start relying on convention; rules like: "you must not store a Person object to the database without first setting it's Name property". The object can't protect itself so the developer has to start remembering a ever increasing list of do's and don'ts. My mental stack usually overflows after a handfull of arbitary rules. When I suggested writing properly encapsulated business classes to my team I came up against three main sticking points: 1. You can't pass encapsualted objects over web services (our communications layer is a web service). 2. You can't use form data binding with encapsulated objects (how do you start the new Person form with a blank name?) 3. You can't restore an encapsulated object from the database. Although I can answer all three objections: 1. Web services aren't meant for internal application communication. That's what remoting is for. 2. See my ealier post on using a nested builder class to solve the form databinding issue. 3. You can use reflection to restore the internal state of an object. (Hey, but that's three times slower than a property access! Perfomance old chap!) It was a step to far. It's not the way that Microsoft suggests working in most of it's architectural publications and it's not the way that it's tools support. You tend to have to work around the default behaviour. The little that I've read about LINQ (and it is only a very little) suggests that it wont support persisting the internal state of an object either. I was very pleased to see Rocky Lhotka feels the same pain that I do. Update: I was wrong about DLinq. Of course I should have read a little more about it before commenting:) Today I read the DLinq overview. DLinq uses attributes on domain classes to specify the object relational mapping and those attributes support internal field access, so it can persist the external state of a domain object without invoking property accessor business rules. Actually reading about it made me slightly more enthusiastic. So long as they get around to implementing polymorphic types and many-to-many relationships, I could probably live with attributed domain objects, although it smells of ORMapping in the domian layer, something that I'm against, being a big fan of layered de-coupled architectures.

Tuesday, March 21, 2006

Stevey's Drunken Blog Rants

I've really been enjoying reading Steve Yegge's blog, and that's not only because it's got the word 'rant' in the title:) He's an opinionated amazon software engineer and I seem to agree with most of what he says. Although that could be because I'm easily led.

Thursday, March 16, 2006

To stored procedure or not to stored procedure?

I've just been reading this post by Frans Bouma 'Stored Procedures are bad m'kay' where he rebuts some of the usual arguments about why you should use stored procedures; security, performance, encapsulating database access. I've been doing enterprise application development with SQL Server for around ten years now and every application I've worked on we've used stored procedures. I was told that stored procedures are good on my SQL Server 6.5 course all those years ago and have never really questioned that view. And I'm not alone. Pretty much every developer I've met over the years has shared this belief that stored procedures are god's own data access tool. On my current project, any suggestion not to use them would be met with horror. Hmm, but I'm begining to question this view, just like Frans Bouma. A couple of things are begining to sway me away from procs. Firstly, Frans makes a good case for rebufing the security and performance concerns, but I think more importantly there is the shift of emphasis in application development away from the relational data model towards the object oriented domain model. This shift is much more advanced in the Java world than it is the Microsoft camp, but us lot are begining to listen to the Java people. If you start to believe that the database should be merely a data persistence tool then the whole API argument with lots of business logic in the procs starts to look less viable. Instead the Elephant in the room these days is the often quoted 'object relational impedence missmatch'. Maintaining all that data access code and all those CRUD stored procedures becomes a major headache. On my current project I've mitigated this to a certain extent by building a code generator that builds all our CRUD procedures and .net wrapper code for us (probably re-inventing the wheel to a certain extent, now that I've discovered Code Smith) but it's still a headache. I haven't used NHibernate or any other OR tools, but I'm getting keener to try. Anything that automates all that boring data access stuff has to be worth a try. So, stored procedures? Well I wouldn't be at all surprised if I'm not using in them in a year or so.

Wednesday, March 15, 2006

A Solution for the reference data problem

What is 'the reference data problem'? In our Winforms application we cache reference data at startup. We hold a reference to that reference data using the ReferenceData class for the lifetime of the application and use it to populate combo boxes and such like. But reference data also enters the application throughout its lifetime as properties of our domain objects. When we get a party object, it comes populated with a gender, a country and other reference data instances. The problem is that we now have multiple versions of the same thing. The party object has a gender, "Male", with an id of 2 (for example), but we also have our cached genders, which include a gender, "Male", with an id of 2. What's the problem with having multiple versions of the same thing? The main issue is that we can't do reference based comparasons; 'myParty.Gender is male' (where male is an instance of Gender). So, you say, of course we can compare the IDs 'myParty.Gender.Id = male.Id', and that's true, but the dot net framework doesn't know about our IDs, so if you try myGenderComboBox.SelectedItem = myParty.Gender it doesn't work as expected. A lot of stuff is much much easier to do if you only have one version of the 'Male' object. So how do we get around this problem? Well it's relatively simple, we just need a few things in place: 1. A way to mark our reference data classes as reference data. 2. A way to cache our reference data in a way that makes it easy to find. 3. A way to set all our domain object's properties that are reference data, to our cached instances. 1 and 2 are easy, 3 is a little bit more involved. To mark our reference data as reference data we can use an attribute. This was a nice from Anthony, one of my fellow developers. I created a new attribute in 'ReferenceDataAttribute'. Now we can just put this attribute on any reference data class, like this:
<ReferenceData()> _
Public Class Gender
    Inherits Entity

End Class
2 and 3 are solved by our old friend the ObjectStore (oops I need to blog about this!) and some changes to our ServiceProvider class. This is the class that provides instances of services; DAL or Web service proxies. We use it like this:
Dim partyService As IPartyService = _
            AIS.Service.ServiceProvider.GetServiceInstance(GetType(IPartyService))
I've used a variant of the Gang-of-Four 'Decorator' pattern here so that when we ask for a service instance, we don't get a reference to a DAL or Web service, we get a reference to a 'Filter' that then in turn holds a reference to the DAL or web service. The Filter intercepts all the calls to the service allowing it to do numbers 2 and 3 above; cache the reference data when it's first loaded and map domain object's properties back to the cached reference data objects as they are loaded. The class that actually does all the work is called ReferenceProvider. Each filter has one of these. They are created by a ReferenceProviderBuilder object which our ServiceProvider class holds a reference to. We use the ReferenceProviderBuilder property of ServiceProvider to set up a new ReferenceProviderBuilder at application startup. The ReferenceProviderBuilder creates and holds a reference to the ObjectStore that's used to cache and retrieve reference data objects and then hands the object store to every ReferenceProvider that it creates. The ReferenceProviderBuilder class also has a property 'Mode' which is a enum with two values; 'Load' and 'Filter'. When the state passed to a ReferenceProvider is 'Load' it caches any reference data it sees in the object store, when the state is 'Filter', it looks for any reference data references in the returned object graph and binds the property to the cached referenced data object instead. So what does the developer have to do to use it? 1. Attribute all their reference data classes with the ReferenceDataAttribute as above. 2. Make sure that those reference data classes are cached at startup. 3. Don't get the reference data instances from the Data Access Layer when building object graphs.

Thursday, March 09, 2006

Doing remote procedure calls using WSE web service attachments

Note this is not an original idea. I'm sure I saw someone describe something similar on a blog a while back. I couldn't find the original source so I ended up re-implementing the same idea. WSE has a nice feature, web service attachments, that allows you to attach a binary file to a web service call. Now it doesn't take a big leap of imagination to see how you could use this to create a kind of remote procedure call. You'd serialize your parameters at the client and attach them to your standard call function. At the server end you retrieve the attachment, de-serialize your parameters, do whatever, serialize the return argument and return the call. Back at the client you de-serialize the return argument and it's done. Simple. Since the application I'm currently working on (the Civil Aviation Authority's Aircraft Register) describes all its service calls as interfaces it also means we can easily generate the client code. Now, you're thinking; "why doesn't he just use remoting?". That's a good point but this does give us a few benefits. We only have to maintain a single web method. We can use all that good stuff in WSE like security and routing. We could also implement compression easily by compressing the byte stream before we attach it. But most importantly we can still say to our managers, 'of course we're still using web services' so that they can live their SOA dream without it messing up our application design:) OK, now for an example. Let's use the canonical Add function, so our 'database layer' code looks like this (sorry they make me use VB.NET here... urgh):
Public Class MathService
    Implements AIS.Domain.Mock.IMathService

    Public Function Add(ByVal a As Integer, ByVal b As Integer) As Integer Implements Domain.Mock.IMathService.Add
        Return a + b
    End Function

End Class
Notice that it implements IMathService, our service interface. Our service client looks like this:
Public Class MathService
    Implements AIS.Domain.Mock.IMathService

    Private INTERFACE_TYPE As Type = GetType(AIS.Domain.Mock.IMathService)

    Public Function Add(ByVal a As Integer, ByVal b As Integer) As Integer Implements Domain.Mock.IMathService.Add

        Dim genericService As New AIS.Service.Generic.Client.GenericService
        Dim result As Object() = genericService.GenericFunction(INTERFACE_TYPE, "Add", New Object() {a, b})
        Return result(0)

    End Function

End Class
This also implements IMathService. It just takes the parameters and calls GenericService.GenericFunction passing in the interface, the method name, "Add" in this case and the parameters. GenericFunction looks like this:
Public Function GenericFunction(ByVal interfaceType As Type, ByVal methodName As String, ByVal parameters() As Object) As Object()

    Dim formatter As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
    Dim parameterStream As New System.IO.MemoryStream

    Dim serviceParameter As New serviceParameter(interfaceType.FullName, methodName, parameters)

    formatter.Serialize(parameterStream, serviceParameter)

    Dim returnStream As System.IO.Stream
    returnStream = InvokeAttachmentFunction(parameterStream)
    Return formatter.Deserialize(returnStream)

End Function
It wraps the interface name, method name and parameters into a ServiceParameter object, serializes it and passes it to InvokeAttachmentFunction, which looks like this:
Private Function InvokeAttachmentFunction(ByVal parameterStream As System.IO.Stream) As System.IO.Stream

    Dim proxy As New GenericServiceProxy
    proxy.Url = m_configuration.ProxyConfiguration.ServerUrl

    ' Create a new DimeAttachment class, and add the stream to that
    Dim attachment As New attachment("text/plain", parameterStream)
    proxy.RequestSoapContext.Attachments.Add(attachment)

    ' make the call
    proxy.GenericAttachmentFunction()

    ' retrieve the return attachment
    ' test for attachments
    If proxy.ResponseSoapContext.Attachments.Count = 0 Then
        Throw New ApplicationException("No attachments detected")
    End If

    ' return the object stream
    Return proxy.ResponseSoapContext.Attachments(0).Stream

End Function
This creates a new GenericServiceProxy (generated by the WSDL.exe tool), attaches the parameter stream to the request, calls the web service, gets the returned attachment and returns it. Our web service looks like this:
<WebMethod()> _
Public Sub GenericAttachmentFunction()

    ' check there's at least one attachnent
    If RequestSoapContext.Current.Attachments.Count = 0 Then
        Throw New ApplicationException("No attachments detected")
    End If

    ' deserialize the attachment stream to a serviceParameter object
    Dim parameterStream As System.IO.Stream = RequestSoapContext.Current.Attachments(0).Stream
    Dim formatter As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
    Dim serviceParameter As serviceParameter = formatter.Deserialize(parameterStream)

    ' invoke the given method on the given interface with the given parameters
    Dim result() As Object = Invoke(serviceParameter.InterfaceFullName, serviceParameter.MethodName, serviceParameter.Parameters)

    ' serialize the result to a stream
    Dim resultByteStream As New System.IO.MemoryStream
    formatter.Serialize(resultByteStream, result)

    ' create an attachment with the stream
    Dim attachment As New attachment("text/plain", resultByteStream)
    ResponseSoapContext.Current.Attachments.Add(attachment)

End Sub

Private Function Invoke(ByVal interfaceFullName As String, ByVal methodName As String, ByVal parameters() As Object) As Object()

    Dim service As Object = ServiceProvider.GetServiceInstance(interfaceFullName)

    Dim serviceType As Type = ServiceProvider.GetServiceInterfaceType(interfaceFullName)
    Dim method As System.Reflection.MethodInfo = serviceType.GetMethod(methodName)
    Dim result As Object = method.Invoke(service, parameters)
    Return New Object() {result}

End Function
The Invoke function calls a method GetServiceInstance on a class ServiceProvider. This returns an instance of a service provider of the given interface type defined in a config file. I'll go into this in another post. So once we've got our concrete instance we can simply use reflection to call the right method. Now have a look at the GenericAttachmentFunction. First it checks that an attachment was attached. Next it de-serializes the parameters to a ServiceParameter object which gives the interface and method that we want to call and the parameters to pass to it. Then we call Invoke as above which returns us an object array as a return value. The return value is then serialized, attached to the response and the function returns.

Tuesday, March 07, 2006

Steve Yegge's whirlwind language tour

This blog by Steve Yegge, a programmer at Amazon, is an excellent diatribe comparing programming languages. No mention of C#, my professional tool (when I'm not forced to use VB.NET - ugh), but I guess a lot of the comments about Java also apply. I'm inspired to have another look at Ruby after reading it.

Wednesday, March 01, 2006

A nice pattern for databinding domain objects

I'm currently working on a windows form application. We've tried to follow good OO principles in designing our business domain classes; encapsulating the business rules, only allowing valid instances of a domain class to be created. We also really like windows form databinding. It's so much easier than writing lots of code to shunt your object's properties back and forth to the form's controls. So, what's the problem. Well, say we have a person class with a name property. The business rules say that the name property can't be an empty string, but how do we create a new person if we can't first bind a blank name to a text box? I've got a nice solution with the use of the 'Builder' pattern. Each domain class has a nested Builder class that is bound to the form instead of an instance of the class itself. The builder allows empty values for required properties so that initially the form has blank fields to be filled in. When the user clicks 'OK', the builder's CreatePerson method is called that returns an instance of the domain class. Because the builder is a nested type it has access to private shared methods that can contain business logic in the domain class, this enforces encapsulation. Validation of the entered fields can be done on the CreateInstance method. Here's the Person class with the nested Builder. Note that the DateOfBirth and Age properties are related via a business rule and that the business rule is also used in the Builder:
Public Class Person

    Private m_name As String
    Private m_age As Integer
    Private m_dateOfBirth As DateTime

    Public Sub New(ByVal name As String, ByVal dateOfBirth As DateTime)
        Me.Name = name
        Me.DateOfBirth = dateOfBirth
    End Sub

    Public Property Name() As String
        Get
            Return m_name
        End Get
        Set(ByVal Value As String)
            If Value = String.Empty Then
                Throw New ValidationException("Name cannot be an empty string")
            End If
            m_name = Value
        End Set
    End Property

    Public Property DateOfBirth() As DateTime
        Get
            Return m_dateOfBirth
        End Get
        Set(ByVal Value As DateTime)
            m_dateOfBirth = Value
            m_age = CalculateAgeFromDob(m_dateOfBirth)
        End Set
    End Property

    Private Shared Function CalculateAgeFromDob(ByVal dateOfBirth As DateTime) As Integer
        Return ((DateTime.Now().Subtract(dateOfBirth).TotalDays) / 360) - 1
    End Function

    Private Shared Function CalculateDobFromAge(ByVal age As Integer, ByVal dateOfBirth As DateTime) As DateTime
        Return New DateTime(DateTime.Now.Year - age, dateOfBirth.Month, dateOfBirth.Day)
    End Function

    Public Property Age() As Integer
        Get
            Return m_age
        End Get
        Set(ByVal Value As Integer)
            m_age = Value
            m_dateOfBirth = CalculateDobFromAge(m_age, m_dateOfBirth)
        End Set
    End Property

    Public Overrides Function ToString() As String
        Return m_name & " " & m_age.ToString() & " " & m_dateOfBirth.ToShortDateString()
    End Function

    Public Class Builder

        Private m_name As String
        Private m_age As Integer
        Private m_dateOfBirth As DateTime

        Public Sub New()
            '
            ' initialise to starting values
            '
            m_name = ""
            m_age = 0
            m_dateOfBirth = DateTime.Now
        End Sub

        Public Function GetPerson() As Person
            Return New Person(m_name, m_dateOfBirth)
        End Function

        Public Property Name() As String
            Get
                Return m_name
            End Get
            Set(ByVal Value As String)
                m_name = Value
            End Set
        End Property

        Public Property DateOfBirth() As DateTime
            Get
                Return m_dateOfBirth
            End Get
            Set(ByVal Value As DateTime)
                m_dateOfBirth = Value
                m_age = CalculateAgeFromDob(m_dateOfBirth)
            End Set
        End Property

        Public Property Age() As Integer
            Get
                Return m_age
            End Get
            Set(ByVal Value As Integer)
                m_age = Value
                m_dateOfBirth = CalculateDobFromAge(m_age, m_dateOfBirth)
            End Set
        End Property

    End Class

End Class

And here's the interesting bits of the form...
Public Class MainForm
    Inherits System.Windows.Forms.Form

    Private m_person As Person
    Private m_personBuilder As Person.Builder

    Private Sub BindPerson(ByVal person As Person)

        m_person = person
        Me.m_nameTextBox.DataBindings.Add("Text", m_person, "Name")
        Me.m_dateOfBirthTextBox.DataBindings.Add("Text", m_person, "DateOfBirth")
        Me.m_ageTextBox.DataBindings.Add("Text", m_person, "Age")

    End Sub

    Private Sub BindPersonBuilder(ByVal personBuilder As Person.Builder)

        m_personBuilder = personBuilder
        Me.m_nameTextBox.DataBindings.Add("Text", m_personBuilder, "Name")
        Me.m_dateOfBirthTextBox.DataBindings.Add("Text", m_personBuilder, "DateOfBirth")
        Me.m_ageTextBox.DataBindings.Add("Text", m_personBuilder, "Age")

    End Sub

#Region " Windows Form Designer generated code "

    Public Sub New(ByVal personBuilder As Person.Builder)
        MyBase.New()
        InitializeComponent()
        BindPersonBuilder(personBuilder)
    End Sub

    Public Sub New(ByVal person As Person)
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call
        BindPerson(person)
    End Sub

    ....

#End Region

    Private Sub m_showPersonButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles m_showPersonButton.Click

        Try
            m_person = m_personBuilder.GetPerson()
            MessageBox.Show(m_person.ToString())
        Catch ex As ValidationException
            MessageBox.Show(ex.Message)
        End Try

    End Sub

End Class