Practical .NET

Simplifying Your Object Model with Value Objects

Our applications are complicated enough without adding any more. Here's a way to think about your objects that allows you to dramatically simplify your object model, provided you're willing to turn your current practice around by 180 degrees.

Some objects in your object model need a certain level of sophistication: Typically, you must track them though the application, passing them from one method to another. They also typically have properties you can change and, as a result, have data that must be validated and saved back to the database. If shared in an asynchronous, multi-threaded application they open your application to race and deadlock conditions. Let's call these "entity" objects because they probably represent real entities in your organization.

Other objects are much simpler. You can create or destroy them as you need and you never change their values (at least, in your application). If they're used on multiple threads, you can simply give each thread its own copy of the object. Let's call these "immutable" or "value" objects because all you're interested in is their data/values.

Immutable/value objects are simpler to create than entity objects. Typically, all you need is a set of read-only properties and a constructor that accepts all of the values needed to populate those properties. Any methods included in a value object are just there to provide those values in useful ways, but don't change or update the object (a value object might have a method that returns a time value for different time zones, for example). Because immutable objects don't change you don't have to worry about ensuring their values are consistent across threads.

Despite their simplicity, immutable objects are an unusual beast to find in most applications -- which is interesting because, as a .NET Framework developer, you work with immutable objects all the time. While the most famous immutable class is the String, the Integer class is also immutable. When you change the value in an Integer variable you're not really changing the value, you're replacing it. Unlike most objects, you don't hang on to some Integer object that currently has a value of four but later on has a value of 101 -- when you change the value of an Integer you get a whole new Integer object.

Guidelines for Value and Entity Objects
Our lives would be much simpler if we built our application with more value objects; provided, of course, we can figure out when creating a value object is the right thing to do. In other columns I've discussed a SalesOrder object designed to support the pricing process in the Billing domain. That object looks like this:

Public Class SalesOrder
  Public Property Id As String
  Public Property CustomerOwner As Customer
  Public Property Details As List(of SalesOrderDetails)
  Public Property Adjustments As List(of Adjustment)
End Class

In this design, I would classify the Customer object as an entity object, but classify the Adjustment objects, which contain information about how the price is to be discounted or marked up, as much simpler value objects. To make that decision, I followed a number of guidelines.

My first guidelines is that if the values in the object's properties define the object, then it's probably a value object. To put it another way: If I change the value of a property and the object becomes a different item, then it's probably a value object. For example, changing the discount/markup amount on an Adjustment gives me a different Adjustment; changing the address on a Customer object means that my customer has moved, it does not mean that I have a different customer.

The corollary of that distinction is that entity objects have an identity; value objects do not. The Customer object is the same customer even as the object's values change. That's not true with Adjustment objects: If I passed an Adjustment object to a method for processing and the method handed me back a completely different Adjustment object but with the same values in all its properties, I wouldn't care. With an Adjustment object, all I'm interested in are the amount of the discount or markup. However, if I passed a Customer object to a method and that method handed me back a completely different Customer, I'd be very upset: In this domain, some Customers are eligible for some discounts and others are not.

That example implies another guideline: Does the object connect me to other information? Customers connect me to a customer's contract, which supplies information about what discounts a customer is eligible for. An Adjustment connects me to … nothing else.

Returning to the issue of identity gives me another test: Can objects with identical values be interchanged freely? If I have two Customer objects with exactly the same information, I might be suspicious that there was a data entry error of some kind. Even then, it's still entirely possible that these objects represent two different (though very similar) customers. On the other hand, if I have two Adjustments with exactly the same information, I could discard one (or substitute one for the other). Another way of looking at this guideline is whether a value comparison makes sense: If all the values of all of the properties of the two objects are identical, do I consider them equal?

The identity issue also suggests that if I track the object's information across time and space (as Billing does with Customers to ensure that the bills are, eventually, paid) then it's an entity object. However, if I just use whatever data is available in the object and then discard that object when processing is complete (as is the case with Adjustment objects in the pricing process), then I'm more likely to be using a value object.

Think of value objects like you think of a dollar bill: Within the domain of "paying for lunch," do you really care which bill you use? Of course, the US Mint does see each dollar bill as a separate item (that's why they put serial numbers on them) -- but that's an entirely different domain than "paying for lunch".

Changing Your Habits
Unfortunately, we create entity objects even when we don't need the extra complexity because it's tempting to think, "Well, right now, adjustment is a value object. But some day in the future I might need to track adjustments -- I'd better make Adjustments an entity object." And that's certainly possible. It's not hard to imagine a different domain (one that keeps track of how adjustments are used and how their frequency of use affects revenue) that would need Adjustments to be an entity object. But that's another domain and I don't need to design for it in the Billing domain. With DDD, you don't try to support anything more than the demands of the domain in which you're working.

This distinction turns common practice on its head: Value objects, which are easier to write and work with, should be your first choice when designing objects. You should create entity objects only when you have to. The more complicated your objects are, the sooner your application will become impossible to maintain and have to be replaced rather than enhanced (the CRAP cycle of Create, Repair, Abandon, Replace)

You probably need a lot fewer entity objects than you think. After all, why not create the simplest possible objects? You have enough problems already.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Full Stack Hands-On Development with .NET

    In the fast-paced realm of modern software development, proficiency across a full stack of technologies is not just beneficial, it's essential. Microsoft has an entire stack of open source development components in its .NET platform (formerly known as .NET Core) that can be used to build an end-to-end set of applications.

  • .NET-Centric Uno Platform Debuts 'Single Project' for 9 Targets

    "We've reduced the complexity of project files and eliminated the need for explicit NuGet package references, separate project libraries, or 'shared' projects."

  • Creating Reactive Applications in .NET

    In modern applications, data is being retrieved in asynchronous, real-time streams, as traditional pull requests where the clients asks for data from the server are becoming a thing of the past.

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

Subscribe on YouTube