Records and data transfer
Tags: webdev, java, programming, beginners
Published: 2023-12-07 16:08:49

### 2023-12-07

---
#### So… transferring data

Transferring data! It’s one of the most common practices in development! It’s almost as if one of the most important applications of development is to store and process unfathomable amounts of information. Now one of my colleagues was curious about DTOs and specifically Records in Java, so I thought I would write a bit about the topic. Data flows to and fro. APIs have to talk to each other, but… well… each API has their own representations of data, their own rules for data being the way it is and their own reasons for changing the data… Now, if there is one thing we don’t like in development, it’s having to be *dependant* on something. When building our applications, our own APIs, we don’t want to become dependant on the way data is structured at any moment in time. This is where the Data Transfer Object comes in. Data Transfer Objects, or DTOs, represent a design pattern that serves one purpose and one purpose only: they are there to store *relevant* data, which allows us to decouple our internal structure from external applications. They also provide a single point of change when it comes to the nuances of data serialisation. They’re essential!

#### Structure and Encapsulation

Now, there is a certain structure to a Data Transfer Object. It is necessary to properly encapsulate data, as we need to ensure the validity of the data without doing any sort of synchronization. Again, the idea is to decouple the data structure from any external applications, so the data we get in has to be *immutable*. We don’t want our data to be altered after instantiation, as that would void the validity and accuracy of the data that we just transferred. We also naturally have to be able to see the content of our DTO as well as compare two DTOs with each other based on their content! This means that, in Java at least, the structure of a DTO tends to look a lot like this:

- Every field corresponding to a piece of data has to be private and final.
- Each field needs a getter.
- We need a public constructor with a corresponding argument for each field
- There needs to be an *equals* method (method to test equality)
- There needs to be a *hashCode* method that returns the same value when all fields match
- we need a *toString* method that includes the name of the class, the name of each field and its corresponding value.

Okay, so how does it look like?

Well, lets take an OMDB movie object in JSON. It looks like so: 

```json
{"Title":"Blade Runner",
 "Year":"1982",
 "Rated":"R",
 "Released":"25 Jun 1982",
 "Runtime":"117 min",
 "Genre":"Action, Drama, Sci-Fi",
 "Director":"Ridley Scott",
 "Writer":"Hampton Fancher, David Webb Peoples, Philip K. Dick",
 "Actors":"Harrison Ford, Rutger Hauer, Sean Young",
 "Plot":"A blade runner must pursue and terminate four replicants who stole a ship in space and have returned to Earth to find their creator.",
 "Language":"English, German, Cantonese, Japanese, Hungarian, Arabic, Korean",
 "Country":"United States, United Kingdom",
 "Awards":"Nominated for 2 Oscars. 13 wins & 21 nominations total",
 "Poster":"https://m.media-amazon.com/images/M/MV5BNzQzMzJhZTEtOWM4NS00MTdhLTg0YjgtMjM4MDRkZjUwZDBlXkEyXkFqcGdeQXVyNjU0OTQ0OTY@._V1_SX300.jpg",
 "Ratings":[
		 {"Source":"Internet Movie Database",
			"Value":"8.1/10"},
		 {"Source":"Rotten Tomatoes",
					"Value":"88%"},
		  {"Source":"Metacritic",
		   "Value":"84/100"}],
 "Metascore":"84",
 "imdbRating":"8.1",
 "imdbVotes":"807,587",
 "imdbID":"tt0083658",
 "Type":"movie",
 "DVD":"09 Jun 2013",
 "BoxOffice":"$32,914,489",
 "Production":"N/A",
 "Website":"N/A",
 "Response":"True"}
```

That’s a lot of stuff! Now, I don’t need all that. In my application, I can whittle it down a bit. Lets write a Java DTO!

```java
@JsonIgnoreProperties(ignoreUnknown = true)  
public class ExampleDTO {  
    @JsonProperty("Title")  
    private final String title;  
  
    @JsonProperty("Year")  
    private final String year;  
  
    @JsonProperty("Rated")  
    private final String rated;  
  
    @JsonProperty("Released")  
    private final String released;  
  
    @JsonProperty("Runtime")  
    private final String runtime;  
  
    public ExampleDTO(String title, 
									    String year, 
									    String rated, 
									    String released, 
									    String runtime) {  
        this.title = title;  
        this.year = year;  
        this.rated = rated;  
        this.released = released;  
        this.runtime = runtime;  
    }  
    public String getTitle() {  
        return title;  
    }  
  
    public String getYear() {  
        return year;  
    }  
  
    public String getRated() {  
        return rated;  
    }  
  
    public String getReleased() {  
        return released;  
    }  
  
    public String getRuntime() {  
        return runtime;  
    }  
  
    @Override  
    public boolean equals(Object o) {  
        if (this == o) return true;  
        if (o == null || getClass() != o.getClass()) return false;  
        ExampleDTO that = (ExampleDTO) o;  
        return Objects.equals(getTitle(), that.getTitle())   
							 && Objects.equals(getYear(), that.getYear())   
							 && Objects.equals(getRated(), that.getRated())   
						   && Objects.equals(getReleased(), that.getReleased())   
							 && Objects.equals(getRuntime(), that.getRuntime());  
    }  
  
    @Override  
    public int hashCode() {  
        return Objects.hash(getTitle(), 
										        getYear(), 
										        getRated(), 
										        getReleased(), 
										        getRuntime());  
    }  
  
    @Override  
    public String toString() {  
        return new StringJoiner(", ", ExampleDTO.class.getSimpleName() + "[", "]")  
                .add("title='" + title + "'")  
                .add("year='" + year + "'")  
                .add("rated='" + rated + "'")  
                .add("released='" + released + "'")  
                .add("runtime='" + runtime + "'")  
                .toString();  
    }
```

Well… there you have it. This object gives me everything I want, ignores everything that I don’t define and is then essentially immutable after instantiation. A classic java DTO… but isn’t it maybe a bit… much?

#### Records

Well, a lot of people considered all the boilerplate code involved in creating a DTO in Java to be a bit much for what is ultimately just a class that is there to store information. It can get so supremely wordy! And while DTOs provide a single point of change… Well, imagine I wanted to scale my movie project and add a few more fields to my DTO. Suddenly, I wouldn’t just have to write the fields, I would have to change the constructor, the equals method, the hashCode method and the toString method. That seems like an unnecessary amount of work for what is ultimately just the addition of two fields. Other languages tried to rectify this problem with the addition of special data carrier classes… like Kotlin’s data class. Java followed suit in Java 17 with the Record class. So what are records, you might ask?

Well, what if we want to have a class that has all of those things I mentioned before, with a focus on conciseness and readability, without all the mess? That is what a record is. Lets refactor that unwieldy example dto by turning it into a record!

Here we go: 

```java
@JsonIgnoreProperties(ignoreUnknown = true)  
public record ExampleDTO(  
        @JsonProperty("Title")  
        String title,  
        
        @JsonProperty("Year")  
        String year,  
        
        @JsonProperty("Rated")  
        String rated,  
        
        @JsonProperty("Released")  
        String released,  
        
        @JsonProperty("Runtime")  
        String runtime  
  
) {  
}
```

Wow! That’s a lot shorter, and while it’s not explicit, it retains all the functionality that was there before! Under the hood, this class has all the accessor methods we want, has all private, final fields and has a constructor with all the necessary arguments. It is a class that is focused on, as mentioned, conciseness and readability. The released accessor isn’t called getReleased() like it normally would be, but simply released(), almost as if you were invoking the field itself. This breaks with the usual convention, but it improves readability and intuitiveness. It even has a special sort of constructor, called a compact constructor that takes no explicit arguments. Lets add the fields we want an play with that constructor for a moment!

```java
@JsonIgnoreProperties(ignoreUnknown = true)  
public record ExampleDTO(  
        @JsonProperty("Title")  
        String title,  
  
        @JsonProperty("Year")  
        String year,  
  
        @JsonProperty("Rated")  
        String rated,  
  
        @JsonProperty("Released")  
        String released,  
  
        @JsonProperty("Runtime")  
        String runtime,  
  
        @JsonProperty("Director")  
        String director,  
  
        @JsonProperty("Writer")  
        String writer  
  
) {  
  
    public ExampleDTO {  
        boolean isValidYear = 
		        Pattern.compile("^(19|2[0-9])\\d{2}$").matcher(year).matches();  
        if (!isValidYear) {  
            throw new RuntimeException(
            "Year needs to be a valid year between 1900 and 2099"
            );  
            }  
        }  
    }
```

We just added two fields and some slight validation to our constructor, without needing to do a lot in terms of writing out boilerplate. Quite nice, I would say. I hope this informed you a bit about the usage of DTOs and records in Java! May this knowledge prove useful to you in your programming efforts. Let me know if there is anything else you would like me to expand on! I am always curious to learn and to share what I know. Write something beautiful!