Truths in Java

This post is about why you want immutable objects and how to achieve that in various ways. I specify the language as Java this time because I want to showcase the Quarkus framework. This pattern can be used in any language and framework combination using the tools available in that language of course.

Oracles

Why do objects need to be immutable and what does it even mean? Well to be immutable mean you are not allowed to change a property of the object but you are allowed to assign a whole new reference to the variable for example. Like with String in Java, and with strings in most other languages. You cannot change a part of the String in the middle but you are allowed to assign a new String to a variable declared.

String s = "StealthyCoder";
s = "Coding Ninjas"; 

What you want is the fact that s, in this case, will hold a truth and that truth will not easily be changed halfway true except very explicitly.

In this case you want s to be a so called oracle. It needs to be able to tell you what the source of truth is at this specific moment in time.

DTOs

In the REST API world, there is a pattern that consists of DTOs, Entities, Services and Controllers. In which a Controller is responsible for making sure a route has a method associated with it. The Entities are responsible for modeling the Database tables. The Services are responsible for handling Entities and making business logic decisions. The DTOs are responsible for being the truthful state of the world.

This leads to stating that DTOs, and also to an extent Entities, should be immutable as it was just stated that oracles are there to provide truths. In the REST API we will be making, the fact is that whatever the frontend will be sending as an input should be immutable once it is in the backend as we do not want any code to be able to accidentally change something. Also the generation of a response should be once only.

The following project outline and code will provide some examples on how to achieve this using the Quarkus framework.

Outline

├── build.gradle
├── docker-compose.yml
├── Dockerfile
├── gradle.properties
├── settings.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── stealthycoder
        │           └── dto
        │               ├── models
        │               │   ├── dto
        │               │   │   ├── GiftRequestDTO.java
        │               │   │   └── GiftResponseDTO.java
        │               │   └── entity
        │               │       └── Gift.java
        │               ├── routes
        │               │   └── gifts
        │               │       └── GiftResource.java
        │               └── services
        │                   └── SantaClausService.java
        └── resources
            └── application.properties

build.gradle

The contents of the build.gradle file are as follows:

plugins {
    id 'java'
    id 'io.quarkus' version "${quarkusVersion}"
}

repositories {
     mavenCentral()
}

test {
  useJUnitPlatform()
}

dependencies {
    implementation 'io.quarkus:quarkus-resteasy-jackson'
    implementation 'io.quarkus:quarkus-hibernate-orm'
    implementation 'io.quarkus:quarkus-jdbc-postgresql'
    implementation enforcedPlatform("io.quarkus:quarkus-bom:${quarkusVersion}")

    testImplementation 'io.quarkus:quarkus-junit5'
    testImplementation 'io.rest-assured:rest-assured'
}
group 'com.stealthycoder'
version '0.0.1-SNAPSHOT'

sourceCompatibility = 14

This just sets up the necessary dependencies for our project and the extensions for Quarkus in order to handle JSON serialization (quarkus-resteasy-jackson) and connect to Postgres (quarkus-jdbc-postgresql) for example.

docker-compose.yml

The contents of the docker-compose.yml file are as follows:

version: '3.6'

networks:
  quarkus:
    name: quarkus

services:
  api:
    container_name: api.dto.local
    build:
      context: .
      dockerfile: 'Dockerfile'
    environment:
      PG_HOST: database
      PG_USER: dto
      PG_PASS: dto
      PG_DATABASE: dto
    ports:
      - "8080:8080"
    networks:
      - quarkus
  database:
    image: postgres:13-alpine
    container_name: db.dto.local
    environment:
      POSTGRES_PASSWORD: dto
      POSTGRES_USER: dto
      POSTGRES_DB: dto
    stdin_open: true
    tty: true
    networks:
      - quarkus

This sets us up so we have a running container and database we can use locally for our development purposes. I chose to build the image for the API because I was reluctant this time to install Gradle on the docker image or indeed use a ready made Gradle image to run the tooling in there.

Dockerfile

The contents of the Dockerfile are as follows:

FROM adoptopenjdk/openjdk14:alpine-slim

COPY build/com.stealthycoder.dto-0.0.1-SNAPSHOT-runner.jar /srv/http/quarkus.jar
COPY build/lib /srv/http/lib

CMD ["/opt/java/openjdk/bin/java", "-jar", "/srv/http/quarkus.jar" ]

Very straightforward, copy a jar into a named other jar and copy the lib folder as well as it needs that to be able to load the dependencies and start it. The name of the jar depends on the group name and the structure of the package and of a setting in the settings.gradle file.

gradle.properties

The contents of the gradle.properties file is as follows:

quarkusVersion=1.8.1.Final

settings.gradle

The contents of the settings.gradle file are as follows:

pluginManagement {
    repositories {
        mavenLocal()
        mavenCentral()
        gradlePluginPortal()
    }
}
rootProject.name = 'com.stealthycoder.dto'

That rootProject.name will mostly determine the naming together with the version of build.gradle.

application.properties

The contents of the application.properties file are as follows:

quarkus.datasource.db-kind = pg
quarkus.datasource.jdbc.url = jdbc:postgresql://${PG_HOST:localhost}/${PG_DATABASE:database}
quarkus.datasource.username=${PG_USER:user}
quarkus.datasource.password=${PG_PASS:password}
quarkus.hibernate-orm.database.generation=${HIBERNATE_GENERATION:drop-and-create}
quarkus.http.access-log.enabled=true
quarkus.http.access-log.pattern="%h "%r" %s"
quarkus.resteasy.gzip.enabled=true
quarkus.resteasy.gzip.max-input=10M

This will set the connection to the database based on Environment variables with some defaults. The HIBERNATE_GENERATION should only be set to drop-and-create the very first time you run the application as it will otherwise consistently delete your previous data.

GiftResource.java

The contents of the GiftResource.java are as follows:

package com.stealthycoder.dto.routes.gifts;

import com.stealthycoder.dto.models.dto.GiftRequestDTO;
import com.stealthycoder.dto.models.dto.GiftResponseDTO;
import com.stealthycoder.dto.services.SantaClausService;

import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PATCH;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Objects;
import java.util.UUID;

@Path("/gift")
public final class GiftResource {

    @Inject
    SantaClausService service;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("{id}")
    public Response get(@PathParam("id") UUID uuid) {
        GiftResponseDTO gift = service.getGift(uuid);
        if (Objects.isNull(gift)) {
            return Response.status(Response.Status.NOT_FOUND).build();
        }
        return Response.ok(gift).build();
    }

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response add(GiftRequestDTO giftRequestDTO) {
        return Response.ok(service.createGift(giftRequestDTO)).build();
    }

    @PATCH
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    @Path("{id}")
    public Response update(@PathParam("id") UUID uuid, GiftRequestDTO giftRequestDTO) {
        GiftResponseDTO responseDTO = service.updateGift(uuid, giftRequestDTO);
        if (Objects.isNull(responseDTO)) {
            return Response.status(Response.Status.NOT_FOUND).build();
        }
        return Response.ok(responseDTO).build();
    }

}

This file will create routes for GET, POST and PATCH on the path /gift. From this we can also determine that the service SantaClausService returns DTOs and also accepts DTOs. The Produces and Consumes annotations help with making sure the serialization will work alright. So in this case the fact is the method will get an instance of the GiftRequestDTO class to be able to use for further processing.

GiftRequestDTO.java

The contents of the GiftRequestDTO.java file are as follows:

package com.stealthycoder.dto.models.dto;

import com.fasterxml.jackson.annotation.JsonAutoDetect;

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public final class GiftRequestDTO {
    private String name;
    private String wrapper;
    private boolean expensive;
    private boolean fragile;

    public String getName(){
        return name;
    }

    public String getWrapper() {
        return wrapper;
    }

    public boolean isExpensive() {
        return expensive;
    }

    public boolean isFragile() {
        return fragile;
    }
}

The important bits to note are the fields are all private. Another is that there are only so called getters . The thing that ties it all together is to tell Jackson to allow to use reflection on all fields so it can set even private fields to values. This is what will make the DTO immutable for further processing. Of course there are ways around this as it was just stated that even Jackson can do mutations to private fields. Also the class is marked as final meaning you cannot extend it in some way and therefore cast it and still make changes.

GiftResponseDTO.java

The contents of the GiftResponseDTO.java are as follows:

package com.stealthycoder.dto.models.dto;

import com.fasterxml.jackson.annotation.JsonAutoDetect;

import java.util.UUID;

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC)
public final class GiftResponseDTO {
    protected UUID id;
    protected String name;
    protected String wrapper;
    protected boolean expensive;
    protected boolean fragile;

    public final static class Builder {
        private String name;
        private String wrapper;
        private boolean expensive;
        private boolean fragile;
        private UUID id;

        public Builder withName(String name) {
            this.name = name;
            return this;
        }

        public Builder withWrapper(String wrapper) {
            this.wrapper = wrapper;
            return this;
        }

        public Builder isExpensive(boolean expensive) {
            this.expensive = expensive;
            return this;
        }

        public Builder isFragile(boolean fragile) {
            this.fragile = fragile;
            return this;
        }

        public Builder withId(UUID id) {
            this.id = id;
            return this;
        }

        public GiftResponseDTO build() {
            GiftResponseDTO responseDTO = new GiftResponseDTO();
            responseDTO.name = this.name;
            responseDTO.wrapper = this.wrapper;
            responseDTO.expensive = this.expensive;
            responseDTO.fragile = this.fragile;
            responseDTO.id = this.id;
            return responseDTO;
        }
    }
}

Here there is a bit more going on. First off Jackson is instructed to allow protected fields as well. This time there are no getters or so called setters. There is a Builder class in there though. This is the so named Builder pattern. The gist is to have a static Builder class that you instantiate and all methods except build() will return the instance itself. This causes a Fluent API to emerge where you can in a declarative way set the properties that you need and then even pass around the Builder instance if you wanted to produce carbon copies but separate instances each time by subsequently calling the build() method. The reason for the protected one is because then the Builder can instantiate a new instance of the class GiftResponseDTO and then be allowed to set the properties.

Gift.java

The contents of the Gift.java file are as follows:

package com.stealthycoder.dto.models.entity;

import org.hibernate.annotations.Type;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Objects;
import java.util.UUID;

@Entity
public class Gift {
    private UUID id;
    private String name;
    private String wrappingPaper;
    private Boolean expensive;
    private Boolean fragile;


    @Id
    @GeneratedValue
    @Type(type = "uuid-char")
    public UUID getId() {
        return id;
    }

    public void setId(UUID id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        if (Objects.isNull(this.name) && Objects.nonNull(name)) {
            this.name = name;
        }
    }

    public String getWrappingPaper() {
        return wrappingPaper;
    }

    public void setWrappingPaper(String wrappingPaper) {
        if (Objects.isNull(this.wrappingPaper) && Objects.nonNull(wrappingPaper)) {
            this.wrappingPaper = wrappingPaper;
        }
    }

    public boolean isExpensive() {
        return expensive;
    }

    public void setExpensive(Boolean expensive) {
        if (Objects.isNull(this.expensive) && Objects.nonNull(expensive)) {
            this.expensive = expensive;
        }
    }

    public boolean isFragile() {
        return fragile;
    }

    public void setFragile(Boolean fragile) {
        if (Objects.isNull(this.fragile) && Objects.nonNull(fragile)) {
            this.fragile = fragile;
        }
    }
}

So this class has the Entity annotation to mark it as an entity for the Hibernate framework to do it's magic. There is a novel approach here by having a sort of immutability by allowing the properties to be only set once. This is because the Entity should reflect the database values and it should be explicit when you want to do updates, not just willy-nilly throughout the services. This is the reason for the usage of the Boolean boxing instead of using boolean. A primitive like boolean will automatically become false in the checking for isNull.

SantaClausService.java

The contents of the SantaClausService.java are as follows:

package com.stealthycoder.dto.services;

import com.stealthycoder.dto.models.dto.GiftRequestDTO;
import com.stealthycoder.dto.models.dto.GiftResponseDTO;
import com.stealthycoder.dto.models.entity.Gift;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import java.util.Objects;
import java.util.UUID;

@ApplicationScoped
public final class SantaClausService {

    @PersistenceContext
    EntityManager em;

    @Transactional
    public GiftResponseDTO createGift(GiftRequestDTO newGift){
        Gift gift = fromDTO(newGift);
        em.persist(gift);
        return toDTO(gift);
    }

    @Transactional
    public GiftResponseDTO updateGift(UUID uuid, GiftRequestDTO updatedGiftDto) {
        Gift originalGift = em.find(Gift.class, uuid);
        if (Objects.isNull(originalGift)) {
            return null;
        }

        Gift updatedGift = fromDTO(updatedGiftDto);
        updatedGift.setId(uuid);
        updatedGift.setWrappingPaper(originalGift.getWrappingPaper());
        updatedGift.setFragile(originalGift.isFragile());
        updatedGift.setExpensive(originalGift.isExpensive());
        updatedGift.setName(originalGift.getName());

        updatedGift = em.merge(updatedGift);
        em.flush();

        return toDTO(updatedGift);
    }

    public GiftResponseDTO getGift(UUID uuid) {
        return toDTO(em.find(Gift.class, uuid));
    }

    private GiftResponseDTO toDTO(Gift gift) {
        if (Objects.isNull(gift)) {
            return null;
        }
        return new GiftResponseDTO.Builder()
                .withName(gift.getName())
                .withWrapper(gift.getWrappingPaper())
                .isExpensive(gift.isExpensive())
                .isFragile(gift.isFragile())
                .withId(gift.getId())
                .build();
    }

    private Gift fromDTO(GiftRequestDTO requestDTO) {
        Gift gift = new Gift();
        gift.setName(requestDTO.getName());
        gift.setExpensive(requestDTO.isExpensive());
        gift.setFragile(requestDTO.isFragile());
        gift.setWrappingPaper(requestDTO.getWrapper());
        return gift;
    }
}

So here is the meat of the whole application basically. There are two helper methods that either take in an Entity and transmogrify it into a DTO or the other way around. Here is where we use the Builder to build the DTO. There is only one place now in the application where GiftResponseDTO classes can be instantiated and therefore after this service nobody can change it. The Gift that comes back from the helper method fromDTO might have properties that still can be set. Namely they can be still null.

The create and get methods are quite self explanatory. The update one might require some more explanation. So we get a DTO and we turn it into a Gift entity. Then we get the one that is currently in the database. That will set the Persistence Context to that object. Then we update the Gift from the DTO to set the properties that were not set yet (i.e. are still null), meaning the properties that were set override the ones in the database. Then the em.merge happens which will give back the representation in the database of the newly updated Gift with the correct properties. Then the em.flush call will make sure this is actually persisted/synchronised with the database.

The Transactional annotations make sure the methods will be transactional and that means if something fails at all, it will not be persisted and rolled back.

Getting started

After you copied all the content and setup the project exactly as is and have Gradle installed, run the following command in the root of the project: gradle quarkusBuild . Then run docker-compose up . Next use your favourite API exploration tool to POST something to http://localhost:8080/gift and see what happens.

Conclusion

I hope that the patterns above can be useful for you when you want to have immutability in Java and in general. I think this pattern is quite powerful as it also makes you think about what you are doing and what you want to change and where and why. This makes it transparent who generates the instances, who mutates them and what is being changed. Also I quite like the novel approach of only allowing to set the properties once.

Also I really like Quarkus. It gives you enough control but still allows for very fast development. It has some cool patterns like Event driven design out of the box as well as Reactive programming. Check out their guides.

#code #java