[JUG Thüringen] Effortless Domain-Driven Design - The real Power of Scala

Table of Contents

  1. The road to hell is paved with JavaScript and primitive types
  2. Stringly-typed code
  3. Entities and value objects
  4. Scala
  5. Excuses
    1. More code
  6. Avoid primitive types

This article is based on my talk presented at a Java User Group Thüringen meetup in Erfurt, Germany on 26 April 2018.

The road to hell is paved with JavaScript and primitive types

Once upon a time, there was a fearless team of developers who were writing their code in a very usual way. When they needed to pass a number to a method, they used variables of type long. When they needed something represented as text, they used Strings.

The classes defined in their code looked like usual Java code, even though they were writing it in Scala. When they needed to store some user data, they created something like that:

case class User {
  firstName: String,
  lastName: String,
  phoneNumber: String,
  emailAddress: String
}

Everything was fine… usually. The code worked correctly… most of the time. The stakeholders were happy… sometimes. There were no bugs… actually, there were bugs. The team felt productive… well, not really.

One day, a terrible monster showed up. It was called GDPR (General Data Protection Regulation) and it was a nightmare. There were only a few months to prepare all services to the regulations and most of the code were Stringly-typed.

Every time we needed to find the code which processes personal data we needed to search for variable names like: “emailAddress”, “email”, “email_address”, “mail” or even “e_mail”. Ctrl + F was our only hope. There was no EmailAddress type so we could not click the “Find usage” button in our IDE. No, it was just a String and it was used to store email addresses everywhere.

Stringly-typed code

Consider such normally looking method like that one:

public void addToOrder(long orderId, long itemId, long quantity);

We write code like this all the time, don’t we? Is there anything wrong with it? Well, a lot.

What happens when I swap the order of parameters?

addToOrder(item, order, quantity);

Did you even notice the difference? I bet in many cases the tests will not help you find the mistake, because the developers are lazy and probably used the same test value for both the item id and the order id.

What else can I do? I can order -1 item. Yes, I can. It is just a number, so nothing is stopping me from doing something like this:

addToOrder(order, item, -1);

What will happen? In the best case, you have some validation code that will throw an exception. In some cases, you may end up with an invalid order in the database. If you code is totally messed up, you will probably pay the customer for one item because he or she order -1 of them ;)

One more crazy example. Is there anything stopping me from doing some arithmetic using database identifiers?

(orderId + itemId) * quantity

Common sense, perhaps. Well, as we all know “there is nothing less common than common sense.” ;)

Sure, you can “solve” all of those problems using a lot of if statements. Defensive programming will always help you, won’t it?

public void addToOrder(long orderId, long itemId, long quantity) {
  if(quantity <= 0 || quantity > MAX_ORDER)
    throw new IllegalArgumentException(“foo bar”);
  if(!orderService.orderExists(orderId))
     throw new IllegalStateException(“foo bar”);
  if(!itemService.itemExists(itemId))
    throw new IllegalStateException(“foo bar”);
(...)
}

Do you see how many tests I need to write just to check the parameters? 3 if statements = 6 tests. In this case the first if checks two conditions, so I need 7 tests instead of 6. All of that even before I test the first line of the actual logic of adding an item to the order. I don’t like writing such code, do you?

Have you noticed that I need two dependencies just to check parameters? Maybe I do not need them for any other reason. Maybe I have more than one method that needs to verify order id, item id, and the quantity. Hopefully, all of them have the same validation logic. Am I naive? ;) If you write production code for more than two years, you know that every time there are two blocks of code which do the same thing, they do it differently. Every time.

Can I avoid passing primitive types to methods?

Entities and value objects

What is a variable? What is a value? Is it only a type? Is the number of ordered items just an integer? Or is it something else? In my opinion, every value, every variable consists of its type, constraints, and units of the measurement.

When I look at the number of ordered items I need to know not only that it is an integer, but also that it has an acceptable range of values (more than 0, probably less than some maximal number of ordered items), maybe additional constraints (some customers may be allowed to order more items) and the unit (I need to know that 1 means one item, not one million of items).

How can I encode that knowledge in Java?

Instead of using primitive types, I can create classes for every type of parameters and change the method signature to:

public void addToOrder(OrderId orderId, ItemId itemId, Quantity quantity)

You may recognise that such parameters are value objects from Domain Driven Design. They have a type, constraints, may contain information about the unit of measurement and are immutable. If I make a mistake and swap parameters, the code will not even compile. That is awesome! I don’t even need to write tests for such cases.

In Java you can create such types in this way:

public class FirstName {
  private final String value;
  public FirstName(String value) {
    this.value = value;
  }
  public String getValue() {
    return this.value;
  }
}

Note that there is no setter. When I want to modify the value I need to create a new instance. This causes some problems in Java. In Java, if I encode the User data in such way, I need to create methods to modify individual fields of the User object:

public User withFirstName(FirstName firstName) {
  return new User(
    firstName,
    this.lastName,
    this.emailAddress,
    this.phoneNumber
  );
}

It is problematic because I need to write or generate such methods. The methods need to be tested. You will not want to do that, will you?

Scala

Fortunately, writing such code is easy in Scala. In Scala I can create a User case class which is immutable, has automatically generated copy function which is used to create a new instance with some modified fields, and has equals and hashCode methods. Additionally, it works flawlessly with pattern matching.

case class User(
  firstName: FirstName,
  lastName: LastName,
  emailAddress: EmailAddress,
  phoneNumber: PhoneNumber
)
(...)
user.copy(firstName = FirstName("John"))

Some time ago, I wrote another article about using Scala type system to create the domain model and the beautiful code you can write if you avoid primitive types. Read it if you want to know how to efficiently create classes which do not waste resources at runtime, because additional memory is not allocated. Cool, isn’t it?

Excuses

When I suggest writing code in such way, I hear a lot of excuses. People don’t want to do that. It is something new. Something unfamiliar, strange, maybe even scary.

In my talk, I commented on seven excuses, but let’s focus on one of them. The one that is the most annoying, because at first glance it looks like a valid concern, but when you think about it for a while it does not make sense anymore.

More code

You need to write more classes, even in Scala that is some additional code. You need to call the constructor every time you use them or the companion object, but that is significantly more code than just typing the value of a String or an Integer.

I do not agree with that. If I have types, I do not need a lot of defensive programming. I do not need to validate parameters in every method that gets a String that is supposed to contain a valid and verified email address. I can simply replace the following validation:

public void sendNotification(String email) {
  if(!emailValidator.isValid(email)) {
    throw new RuntimeException("...")
  }
  if(!emailService.isVerified(email)) {
    throw new RuntimeException("...")
  }
(...)
}

with that code:

public void sendNotification(VerifiedEmail email) {(...)}

Sure, there is a place where I need to create the value and validate the email address, but that is only one place. I do not need to do that in every place where I need an email address.

I just removed two if statements. That means I can remove at least two tests. This method cannot accept an invalid parameter anymore, I do not need to tests that. Actually, if I wrote the VerifiedEmail constructor/companion object correctly and validate the email when it is created, it is impossible to write tests for invalid usage of the sendNotification method. I cannot even create an invalid instance of email.

What about the validation logic? It is only in one place, in the function that creates a new value of my type. There are no duplicates, no copy/pasting, no silly mistakes and different versions of validation of the same value.

Justin Woo — https://twitter.com/jusrin00/status/875238742621028355
Justin Woo — https://twitter.com/jusrin00/status/875238742621028355

Avoid primitive types

What’s the one thing you can do, such that by doing it, everything else will be easier or unnecessary? — “The One Thing” by Gary Keller

In my opinion, avoiding primitive types is such one thing that makes everything else easier or unnecessary. Primitive types do not solve any problems, but they create a lot of them.

If you use separate classes for all of you domain types, you will effortlessly create code that is easy to read and modify. You will make fewer mistakes because some errors are not possible. You do not need a lot of tests when the invalid code does not even compile and when it is not possible to create a value that represents a state that is not existing in your domain.

Get rid of primitive types, please.

Older post

Buzzwords, buzzwords everywhere

Do we behave like a child in a toy store?

Newer post

Can we make it more generic?

What can we learn from a horrible mistake made by a programmer who wanted to make the code more generic?

Are you looking for an experienced AI consultant? Do you need assistance with your RAG or Agentic Workflow?
Schedule a call, send me a message on LinkedIn. Schedule a call or send me a message on LinkedIn

>