Refactoring #5: From arrays to Data Transfer Objects

Geni Jaho

3 min read

We recently pushed a new improvement at OLM to reward admins with XP for verifying uploaded images. The most critical part of the feature, a class that does some calculations and returns some stats, introduces a small design flaw. We're going to see how to fix that in this post.

The problem is simple: our class is returning an array of key-value pairs, and this array is used in quite a few other places in code.

The flaw here is that if we decide to change, add, or delete a key in this array, we'll have to touch every usage of it. And trust me, that's going to introduce bugs.

Using an object instead of an array

This problem is perfect for refactoring to an object. In fact, these objects are called Data Transfer Objects (DTO), as they're meant to transfer data of some kind, not necessarily with logic in it. In our case, it's a 'tags difference' object, but we'll try to find a proper name for it. There are some steps to perform this refactoring safely, but a simple, dummy implementation looks like this.

We're storing the class in the app/DTO directory, you might find a better place in your case. Also, the usages of the new return value are now much simpler to understand and work with.

What's inside the Data Transfer Object?

Generally, the key-value pairs we had in the previous array now become first-class citizens as private object properties. You're going to have to write some getters for them.

It's important to remember that we only set the properties once in the constructor. That gives us a simple validation out of the box. Of course, you can apply extra validation, parsing, and filtering you might need in the constructor as well. I'd say keep the DTO simple though, try to look at it simply as a way to move some data from one place to another. I'd advise you also set the class as final, and implement the public getters and setters only when you need them.

Our final class looks like this.

We'll probably start refactoring into DTOs in other parts of the code as well. We could use your help though, wink.

The benefits of using DTOs are numerous, there's even a package from Spatie for them. See the package's release post and this awesome DTO explanation for a more in-depth take and how to implement and use them in a more sensible way.

The code used for illustration is taken from the OpenLitterMap project. They're doing a great job creating the world's most advanced open database on litter, brands & plastic pollution. The project is open-sourced and would love your contributions, both as users and developers.