DEV Community

Cover image for The hidden value of the Value Object
Rafal Pienkowski
Rafal Pienkowski

Posted on

The hidden value of the Value Object

Introduction

After my lecture on Vaughn Vernon's "Implementing Domain-Driven Design.", I've decided to write a post which will be connected with DDD and draw attention to the underestimated Value Objects. In my experience, developers are abusing Entities in their projects. Very often that behavior ends with an occurrence of many anemic domain models without or with little business logic. In my opinion, some of the anemic domain models could be quickly transformed into the Value Objects.

Note: All examples are written in C#, and they are using newest C# syntax. I hope they would be easy to understand also for developers who are using different languages.

Example:

Let's start with the simple example of a Car entity like in the code snippet below.

public class Car
{
    /* Properties */
    public string Color { get; set; }
    public bool Metallic { get; set; }

    public string FuelType { get; set; }
    public int EngineCapacity { get; set; }
    { ... }

    /* Methods */
    public void Drive(int distance) { ... }
    { ... }
}
Enter fullscreen mode Exit fullscreen mode

We will focus on properties related to car's color. I mean Color and Metallic property. For simplification, color is represented by string type.

Characteristic of the Value Object:


1) Measures, quantifies or describes the subject of a domain

In our example, Color and Metallic are the best candidates for the Value object. Both are describing the look of the vehicle. So extract the new class:

public class Color
{
    public string Hue { get; set; }
    public bool Metallic { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Whenever Hue or Metallic changes we receive a new color, so let's go to the next point.

2) Is immutable

Immutability means that object state cannot be changed after it's initialization. In Java or C# this could be achieved by passing all parameters to the constructor of a value object. Based on the values of parameters state of the object will be set up. Of course based on values some additional properties could be calculated.
Notice: The object itself can't call property setters either by the public, nor the private methods. This is forbidden.

Let's enhance our example according to previous thoughts.

public class Color
{
    public Color(string hue, bool metallic)
    {
        Hue = hue;
        Metallic = metallic;
        if (hue == "white" && !metallic)
        {
            BasicPallete = true;
        }
     }

    public string Hue { get; }
    public bool Metallic { get; }
    public bool BasicPallete { get; }
}
Enter fullscreen mode Exit fullscreen mode

In the example above we've calculated the BasicPallete property. For example, it could influence a car's price calculation.

3) Composes linked attribute values into the integral unit

This means that all properties are bounded, and each provides the crucial part of the information which describes the whole value of the object. Individual property value doesn't provide comprehensive information about the object. In our case knowledge about that if a color is metallic or not doesn't give us information about its hue. The same situation is when we know hue, but we don't know if it's metallic or not.

Worth is a perfect example cited often by this point. Let's take 100 USD as an example. Currency and amount are linked together. Let's imagine that someone has changed the currency from USD to MXN (Mexican Peso) in our object. That change has the significant impact. 100 MXN is worth approximately 5.25 USD. Oneone wants to be a victim of such a small change.

4) Is replaceable in the situation when the measurement changes

In our case car is an entity and color is a value object in our domain. Someday we decide to change the color of our car. Let's say we've bought a green non-metallic vehicle, but after a few years, we want to have a green metallic one. We cannot just add metallic gloss to existing painting. We need to repaint it with the new green metallic paint.

Color color = new Color("green", false);
//color.Metallic = true;  -> This is not allowed

color = new Color("green", true);
Enter fullscreen mode Exit fullscreen mode

5) Could be compared with other value objects

In languages like C# when we try to compare two objects by default, the comparison covers the location in memory of two objects (called Reference Equality). Two objects can have the same property values, but the will not be equal. This is the wrong assumption regarding the Value Objects. In that case, we need to have equality of given type and all property values (called Value Equality).

According to our example, two colors are the same if they have the same hue and are metallic or not. Two cars which are green and metallic have the same color.

So let enhance our class and implement the Value Equality:

public class Color
{
    public Color(string hue, bool metallic)
    {
        Hue = hue;
        Metallic = metallic;
        if (hue == "white" && !metallic)
        {
            BasicPallete = true;
        }
    }

    public string Hue { get; }
    public bool Metallic { get; }
    public bool BasicPallete { get; }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Color);
    }

    public bool Equals(Color otherColor)
    {
        if (Object.ReferenceEquals(otherColor, null)) return false;
        if (Object.ReferenceEquals(this, otherColor)) return true;
        if (this.GetType() != otherColor.GetType()) return false;

        return (Hue == otherColor.Hue) && (Metallic == otherColor.Metallic);
    }

    public static bool operator ==(Color leftColor, Color rightColor)
    {
        if (Object.ReferenceEquals(leftColor, null))
        {
            // null == null = true
            return (Object.ReferenceEquals(rightColor, null));
        }
        return leftColor.Equals(rightColor);
    }

    public static bool operator !=(Color leftColor, Color rightColor)
    {
        return !(leftColor == rightColor);
    }

    public override int GetHashCode()
    {
        return (Hue.GetHashCode() * 0x100000) + (Metallic.GetHashCode() * 0x1000) + BasicPallete.GetHashCode();
    }
}
Enter fullscreen mode Exit fullscreen mode

6) Has side-effect-free behavior

This is the fundamental rule. Without it, Value object could be treated as a simple container for attributes. To understand it we should start with understanding side-effect-free function. Function without site effect has a result but doesn't modify its state. Such a function are fundamental in Functional Programming Paradigm.

All methods exposed by a value object should be a function without side effect, which doesn't violate the value object's immutability.

Let's take the car's repainting example. We can achieve repainting by adding such a function to our class.

public class Color
{
    public Color(string hue, bool metallic)
    {
        Hue = hue;
        Metallic = metallic;
        if (hue == "white" && !metallic)
        {
            BasicPallete = true;
        }
     }

    public string Hue { get; }
    public bool Metallic { get; }
    public bool BasicPallete { get; }

    public Color Repaint(string hue, bool metallic)
    {
        return new Color(hue, metallic);
    }

    // Value Equality part
    {...}
}
Enter fullscreen mode Exit fullscreen mode

As we can see, there is a similar effect like in car's repainting case. But the connection between method and value object is stronger and side-effect-free. We don't modify the state of the color object. Instead of that, we've created the new one with demanded values.

Color color = new Color("green", false);
color = color.Repaint("green", true);
Enter fullscreen mode Exit fullscreen mode

Summary

Of course, I didn't cover all topics related to Value Objects, all the more DDD, but I hope this post will inspire you to use it more often in your projects.

Keep calm and use Value Objects

P.S.

Please don't go from one extreme to the other.

Links:

Discussion (8)

Collapse
goaty92 profile image
goaty92

This is great and all but I would suggest that you make the Color class a value type. Specifically I would write it as readonly struct Color : IEquatable<Color>), this has a couple of advantages:

  • It's read-only (immutable) by default
  • Allocation-free when passing between methods (like with public Color Repaint(string hue, bool metallic))
  • Makes the intent more explicit (you want Color to represent some values) if no virtualization is required.
Collapse
rafalpienkowski profile image
Rafal Pienkowski Author • Edited

Thanks for your comment. You are completely right. My old habits (and laziness) won.
I had problems with storing Value Objects with Entity Framework. I’ve checked that even EF Core 2.0 doesn't support storage of struct objects.
In non-db scenarios read-only struct makes the job right. We need to keep in mind that C# 7.2 is required to use it. I added the struct version to the links too.
Point for you for your mindfulness.
Cheers.

Collapse
thomasjunkos profile image
Thomas Junkツ

Yes, value-objects are a pretty valuable (pun intended) tool to have on your belt. Thank you for the post.

The post scriptum reminds me of back in the day, I worked with Microsofts CRM 4.0 and they had encapsulated everything in such value objects which I think is a good example of overdoing things.

Collapse
mario_tilli profile image
Mario Tilli

Thanks for this post. I think it is important to talk about Domain Driven Design, to study and study again, to put it into practice because it is not simple, it is not something simple just because it tries to express needs in the most adherent way to reality through software, but, at the end, it's really worth it

Collapse
rafalpienkowski profile image
Rafal Pienkowski Author

Thanks for good words :)

As you mentioned, talking and practicing DDD is vital for software engineers. We should keep in mind that usually, only a little subset of our system requires DDD. I think no more than 10-15% of the whole system. There is no sense to apply DDD to places where only simple CRUD is needed. That ends with so-called "over-engineering". Of course, everyone makes mistakes during the learning process. In my opinion, Domain Driven Design is worth this.

Collapse
mario_tilli profile image
Mario Tilli

"it depends" is always a good starting point, in my opinion. But I'm sure that, for modern and complex systems, using only CRUD operations could lead to excessive and sometimes unmanageable complexity. It's a trade off: "forcing to follow this path" or "over-engineering" to better understand the code and keep its complexity under control over time is always worth it, but sometimes in our working environments this point of view is not fully accepted or understood: timing and costs are the greatest constraints

Thread Thread
rafalpienkowski profile image
Rafal Pienkowski Author

I totally agree with you. I ’m also a big fan of the ”It depends” clause. We should ask if we need something in our solution.

Collapse
sandordargo profile image
Sandor Dargo

Thanks for the article!

This is the best:

"Please don't go from one extreme to the other."