Monday, March 02, 2009

What's the point of Value types?

Here's another post that started out as an answer to an excellent comment by Mike Boldischar:

"I'm reading through Domain Driven Design. One thing that bugs me is the idea of a "value" type. Why not just call those objects immutable entities? Maybe it's just a personal preference, but in my opinion, building "values" into the design adds little value. They only encapsulate a subset of entity values. Why not just specify immutable entities? Any thoughts?"

The (very) simple answer is that it's more about identity than immutability. Entities have an independent identity. You can ask for an individual entity by it's ID. Value types only exist in terms of their parent entities and have no separate identity. Think of a line (Entity) made up of some points (Value), or a contact (Entity) that has an address (Value).

A common anti-pattern is to have entities with large numbers of properties of basic types, such as int or string. Often they map 1-to-1 to a database table. Individual properties or groups of basic types usually have some meaning in terms of the business and should be factored into to value types. Take this 'contact' class for example:

public class Contact
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address1 { get; set; }
    public string Address2 { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

It's an entity, because has an Id, and has basic type properties that map directly to table columns. But looking at it, the properties naturally fall into two groups that have a real meaning in a business sense; name and address. It might make more sense to refactor it as:

public class Contact 
{
    public int Id { get; set; }
    public Name Name { get; set; }
    public Address Address { get; set; }
}
public class Name
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
public class Address
{
    public string Address1 { get; set; }
    public string Address2 { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

Here, Name and Address are value types, they don't have an Id. Why is this good? Because we're decoupling stuff to do with names from stuff to do with addresses. We can also reuse name and address in other entities. However, they make no sense as independent things and so don't need to have an identity. Note that the database table wouldn't change and a good ORM like NHibernate can easily manage this kind of mapping. The key is that we're breaking the 1-to-1 class-to-table link that's often the naive starting point for most object-relational mapping.

6 comments:

  1. Nice post - and a really neat example; I'd never thought of partitioning database tables into aggregations of value objects like that.

    One tip - if you implement your value objects as structs instead of classes in .NET, you get immutability and value-comparison equality for free.

    e.g.

    public struct Name {
    public string Firstname { get; set; }
    public string Surname { get; set; }
    }

    will let you write code like:

    if (someCustomer.Name == otherCustomer.Name) {
    }

    - because .NET will automatically determine whether the structs are equal by comparing their member field values, rather than comparing object references.

    ReplyDelete
  2. Dylan,

    Good points. I'm sure I don't need to tell you, but for anyone else reading your advice, beware, you've got to be very careful about using structs. They can't be part of an inheritance hierarchy, so a lot of design patterns are ruled out, not to mention proxying (including mocks, Nhibernate proxies etc). They have their uses and are far more performant, but be sure you're aware of the consequences. If in doubt stick with class for all domain objects.

    ReplyDelete
  3. Anonymous6:56 pm

    it should be "by its ID", not "by it's ID".

    "It's" is always the abbreviation of "it is".

    ReplyDelete
  4. Anonymous3:35 pm

    I dont't think that the intrinsic definition of a Value Object to not have an identity field doesn't not preclude the Value Object to have an identity field such as an ID in itself hence the typical example of the implementation of a Value Object via a Component in NHibernate.
    What I feel is more important is that a Value Object shouldn't expose an some kind of public property that other objects (Value or Entities) can reference.
    In effect, a Value Object delegates its identity to the referencing Entity Object.

    ReplyDelete
  5. In your example your Name and Address classes arn't value types... They'r still reference types. You must use the struct keyword for them to become value types. The two main differences are how value types work vs reference types is how they are passed as parameters (they are duplicated, unless used in the context of other keywords), and how memory is allocated for them.

    I have used value types often in real-time applications due to the lesser cost in terms of performance to allocate them. Most often they have something to do relating to math. Value types should not be used for things such as your example. Using strings also in this context is a bad idea due to the non-typical nature of how they work under the hood.

    I'm not trying to bash :) I think your article presents an interesting way of looking at them - I just think your example needs to be reworked.

    ReplyDelete
  6. K-Dub, unfortunately 'value type' is an overloaded term. The value type you are refering to is in terms of C#, where you are quite correct, Name and Address are 'reference types' not 'value types'.

    The use of the term 'value type' in this post is in the context of Domain Driven Design (Evans, Fowler) where 'value type' is a type within the domain that does not maintain its own identity.

    I can understand the confusion, IT is riddled with overloaded terms; just try talking about 'services' :)

    ReplyDelete

Note: only a member of this blog may post a comment.