Restrict Available Operations on Values using Value Objects

Tomasz Ducin
InstructorTomasz Ducin
Share this video with your friends

Social Share Links

Send Tweet
Published 6 months ago
Updated 2 months ago

Value Objects are another pattern in Domain-driven Design that provide more structure around what you can and cannot do with a type.

In TypeScript we create Value Objects with classes that will define what operations can be performed to the value on the class itself. In this lesson we'll explore how to create a Value Object for our Money type.

[00:00] Brand types have one significant limitation and that is we can perform all operations that are available for the base type of a brand. So in case of the money brand, the base type is number so we can perform all operations on money that are available for numbers. And that doesn't always make [00:20] sense. So in case of adding, the question is, can we add money to money? Yes. It makes sense. Can we subtract? Yes. It makes sense. But it doesn't really make sense to multiply money because there is nothing like squared dollar. And at the same time, if we want to have a square root, [00:40] this doesn't necessarily make sense to have a square root of a dollar. Like there is a dollar, but there is no square root of a dollar. So if we want to limit not only the type compatibility, but also the allowed operations on a certain type of the data model, then we can evolve the type into [00:59] a value object. Most often, a value object is going to be an instance of a class that will explicitly list what are the available operations on a certain type of the value. So we can have the add and multiply from the previous example. So what we can do is we can add, let's say, $10 [01:19] to another $10. Obviously, that's a valid operation, so we add money to another money, But we cannot multiply $10 by another $10 because there is nothing like 100 square dollars. So what we can do is we can multiply $10 by a factor that is basically example slightly more interesting, let's also include the fact that each [01:39] money could be of a different currency. So the currency could be either American dollar or euro. And let's also create [01:59] a constructor, which is going to include creating the private properties. So value is going to be the number. So let's also rephrase it to amount. And the other property could be the currency itself. So we have private currency, which is of [02:19] type currency, and let's just fix this syntax error. So our constructor is essentially empty, it just assigns the two properties. And when we add money, it is possible to add dollars to dollars, but it's not possible to directly add dollars to [02:39] Euro. So what we can also include here, and this is the best place for this if statement, not in our constructors, not in our components or whatever framework we are working with, but essentially it is the model which should guarantee and check the rules whether they are satisfied. So we can check whether [02:59] that this dot currency is essentially the same as the another money's currency. So if it is different than another currency, then what we do is basically we throw a new error saying that 2 different currencies cannot be added. So cannot [03:18] add different currencies. And again, whenever we want to run this add operation, we don't need to check this condition outside of the class. This is the one of the biggest advantages of value objects. However, if this requirement is satisfied, then we can just [03:38] return new instance. So new money where this dot amount is being added to another dot amount. And since the currency is the same, then we basically take the currency from this. In case of multiply, we don't have to check the currency since there is just one [03:58] money and a factor. So we'll basically return new money where we multiply this dot amount by what the factor is, and we just reuse the this dot currency. Let's also make sure that the validation logic that we've had in the previous example [04:18] is going to be satisfied. So if we allow the money value object constructor to be called publicly, then it would be possible to pass any number value. And we would like to include the validation within the class so that it would be impossible to create invalid [04:38] value of the amount. So for this reason, we're going to expose a static constructing method and to make the constructor itself to be private. And since the constructor is private, we will add the static, let's just call it from, and we would have that value [04:57] amount, which is a number and currency, which is going to define the currency of the amount. And let's just return obviously the new money with the amount and currency, but the place for actually making sure that all the [05:17] business rules are being checked within the static from method. So if amount is negative, then this is going to be impossible. So basically, if this rule is not held, then we're going to throw new error, impossible [05:37] to create money with negative value. Of course, this is just an example. But again, we want to make sure that all the business rules are placed within the value object class. Right. Negative. Now let's see our money value [05:57] object in action. First, let's export the class and let's get rid of the money brand type. Let's import the money value object. So the way we're going to instantiate it is going to be the money dot [06:16] from. And let's say that we would create 99 dot99 American dollars. And we can see that there is no compatibility between the number primitive and money. This is because money doesn't extend the number primitive or the number prototype in [06:36] any way. Hence, both types are incompatible. It is not possible to use the native addition operator, and obviously, it's also impossible to use the subtraction, multiplication, or doing any kind of other arithmetic operations. So what we can do in order to add is we [06:56] can basically run the method, which is m dot add. And here we can add another money, whatever that is. So that would be basically yet another money value object. Or if you want to do a multiplication, what we can do is we can multiply by factors. So if we had 99 dot 99, [07:16] then it would be basically multiplied. That would be yet one more money value object. And now since we have moved the validation rules into the static from method, we can safely remove the type guard, and we can also remove checking [07:36] the rules of the values since they have been already checked while creation. There is way more when it comes to value objects. For instance, they should always be immutable so that they can be shared across the system and they can never represent entities and hence they can never include IDs. [07:56] Because as the name suggests, they can only represent values and they can be part of other entities. But that's something you can learn from domain driven design.