Let’s make a glimpse of why applying the Value Object approach to our code can be really beneficial.

Note: This blog was originally posted on Softwaremill’s blog


I’m sure pretty much of us have heard about Domain Driven Design (DDD), ubiquitous language, and all this fancy stuff. And yet, I see a lot of code that does not apply ideas based on this approach. Why is it so?

Maybe it’s too complicated to chew all of this at once. Applying DDD requires significant effort and deliberate choices. That’s a lot of domain knowledge and changes to be applied to our code and the way we, developers used to think. I’d say we are more accustomed to operating on a technical level (think of Strings, ints, maps, lists, loops, etc.) in our code — which is rather obvious — than looking on an issue using business terms or even point of view.

Maybe it’s too complicated to chew all of this at once. Applying DDD requires significant effort and deliberate choices. That’s a lot of domain knowledge and changes to be applied to our code and the way we, developers used to think. I’d say we are more accustomed to operating on a technical level (think of Strings, ints, maps, lists, loops, etc.) in our code — which is rather obvious — than looking on an issue using business terms or even point of view.

So why not to apply some of the parts gradually? That could be a good starting point. One of such features, from my point of view, is Value Object (VO). It’s surprising how often we are using primitive values as our input values to objects, methods, or functions. What happened to VO? Did we forget about the concept?


Value Object

What’s the VO you could ask? Shortly, it’s an instance of an immutable class wrapping some bunch of data. What is essential — such an object is defined by values of its attributes and has no identity (like Entity). It’s “just” data.

I will not pretend I’m going to make any breakthrough on the concept and won’t show you anything new. If you look for a more detailed description of the idea of VO, check what Martin Fowler wrote some time ago. Even, if you’re familiar with it, it’s worth to re-read this excellent post from time to time, to be aware of VO’s nuances.


Play with the code

Let’s consider two examples, illustrating the “forgotten” value of VO. For both, we will start with a version using primitives values. Next, we will apply the Value Object approach and see how the code changes and where this will lead us.

While I’m going to use Java in code examples, backed by Lombok features, the overall idea can be utilized in other languages as well. So, keep reading 😉


What’s your id?

The first example is as follows: assume we have a service accepting an access token, and we need to extract a business identifier value from it for further processing. For the sake of the example, the token is of String type.

Let’s start with the initial version of the code. Of course, we are using OOP and have a dedicated extractor for finding the token’s value. Here it is:

class BusinessIdExtractor {
  String extract(String accessToken) {
  return JWT.decode(accessToken).getClaim("businessId");
  }
}

Quite simple, right? A few lines of code that are easy to read. So, what’s wrong with this version? I can spot some issues:
– the returned value can be anything; it is some string,
– moreover, the provided value can be anything too,
– and the code does not document itself well.

What can we do with it? The first thing coming to my mind is to wrap the returned value, so a coder using this class exactly knows what returned data is. Thus, we introduced BusinessId class:

@Value
@Accessors(fluent = true)
class BusinessId {
private final String value;
}
class BusinessIdExtractor {
  BusinessId extract(String accessToken) {
  return new BusinessId(JWT.decode(accessToken).getClaim("businessId"));
  }
}

When we look at the code now, we can see, that we don’t deal with primitive String anymore, but we have named the returned value. The code documents itself. The value returned from the method is not any string; we expect a business identifier.

What about input data? It is a string value as well. Maybe we should implement VO here as well. Let’s try it. We create AccessToken class and apply to the method:

@Value
@Accessors(fluent = true)
class AccessToken {
private final String value;
}
class BusinessIdExtractor {
BusinessId extract(AccessToken accessToken) {
return new BusinessId(JWT.decode(accessToken.value()).getClaim("businessId"));
}
}

Yay! That looks good! We could stop here. The code operates on well-named types, and we know what data we should provide and expect. However, there is still room for improvement. Since we follow the OOP principles, why not to move the extraction mechanism to the AccessToken class? Is it worth it? Let’s see.

@Value
@Getter(NONE)
class AccessToken {
private final String value;
public BusinessId businessId() {
return JWT.decode(value).getClaim("businessId");
}
}

Now, that’s something handy! The change results in less code — we don’t need a separate class extracting business identifier value. The only thing we need is to call a method on a token instance. We have encapsulated behavior in a class, together with the data. Two types instead of three used previously. The next step could be decoding a token value in the constructor so we don’t do it every single time asking for businessId.


Send me the email

The other example I’d like to consider is sending an email notification using method accepting an email address and a message.

As you may guess, we have a dedicated class, responsible for that particular feature in our system. We called it EmailSender:

public class EmailSender {
public boolean send(String email, String body) {
return doSend(email, body);
}
}

Pretty simple, right? But, as in the previous example, we can point to some places we could make it better.

Since we operate on strings, we can pass any data to the method as in the previous example. Next, because of having two arguments of the same type, we can even make a dumb mistake in their order and call the method in the following way: send(body, email). We will get to know about the error running a test, or if we don’t have it (scary!) — during a runtime. And at the end — what the returned boolean value stands for? If false states sending an email failed, then what was the cause? We need to read Javadoc at least (if there’s any) to know how to use the method. It’s not an intuitive API, nor it is not well documented.

Let’s handle the remarks step by step. First, we will introduce VOs for input parameters, EmailAddress and Payload for email address and body, respectively:

@Value
@Accessors(fluent = true)
public class EmailAddress {
private final String value;
}
@Value
@Accessors(fluent = true)
public class Payload {
private final String message;
}
public class EmailSender {
public boolean send(EmailAddress emailAddress, Payload payload) {
return doSend(emailAddress.value(), payload.message());
}
}

The move solves a couple of things. We know what to pass to the method in what order — no more dumb mistakes! By extending the EmailAddress class with small validation logic, we can have control over the data held by the class.

Introduction of the Payload class makes it easier to add new fields (for example, attachments to the message).

What about the returned value? Let’s take care of it too. Instead of boolean, we definitely can do better:

@Value
@Accessors(fluent = true)
public class SendingResult {
private final boolean isSend;
}
public class EmailSender {
public SendingResult send(EmailAddress emailAddress, Payload payload) {
return new SendingResult(doSend(emailAddress, payload.message));
}
}

With SendingResult class we can get more sophisticated handling of the result, for example, we can provide more details about an error occurred or add more fancy data to the successful sending like duration of the operation (if it makes sense in the domain of course).

Nothing is holding us back from wrapping both parameters into a single value object, so let’s do it as well:

@Value
@Accessors(fluent = true)
public class Payload {
private final String body;
}
@Value
@Accessors(fluent = true)
public class EmailMessage {
private final Email email;
private final Payload payload;
}
public class EmailSender {
public SendingResult send(EmailMessage message) {
return new SendingResult(doSend(message.email(), message.payload().body()));
}
}

That’s another version of the input parameter that opens some possibilities to extract a base interface of our Port for sending notifications to the outside world using different technologies like KafkaSender or JmsSender


Summary

The question is — is it worth it at all? The Value Object approach adds some additional code to maintain. However, it is still surprising how going with VOs simplifies code and makes it easier to understand intentions. Additionally, it dramatically improves code in terms of documentation.

By using a proper name (naming is hard, right?) of VO, we are documenting the code. It requires only a glimpse to see what are the expectations on the input to a service/component/method and what we get in return after calling this part of a system. You don’t have to think about what kind of data a given String parameter holds. Is it some identifier? Or maybe an address of a person? Could it be any text possibly? With wrapping a value, we provide a context that is easier to read and understand.

And in the end, if there is only one thing I want you to take from this reading, let it be this — don’t be afraid (or lazy!) to play with your code by applying little alterations in it. Who knows where a simple change (like introducing VO in a place of a primitive value) will lead you and what will be the next thought or idea after moving some bits of the code around.

After all, how can we gain any experience if we don’t experiment on our own? No, passive activities like reading blogs or books, attending conferences as an attendee, or watching presentations about even the most exciting topics from the Internet do not count if you do not practice ideas and knowledge presented. You have to take the effort and get your hands dirty. You and only you are responsible for advancing on a software developer path. But that’s a different, yet important, thing about developing skills.

So that’s it. I hope, after reading the post, you see how valuable the little things like applying Value Object concept in code can be. And I’m curious about your experiences and observations of implementing VO in your code. Share them by leaving the comment below!

Photo by Free-Photos on Pixabay

Categories: programming

0 Comments

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.