Safe Domain Objects with MapStruct and Immutables
Tags: java, programming, persistence, modelling
Published: 2020-12-16 09:45:24

#### Even in Java you are not doomed to deal with mutable objects and nullable fields. With the help of annotation processor libraries like MapStruct and Immutables you can add some safety and convenience to your codebase.

During the last couple of months I have been working with Java, using mutable POJOs as domain objects and [MyBatis](https://mybatis.org/mybatis-3/) for database access. Queries are declared in XML files, with only some [basic and rather cumbersome support](https://mybatis.org/mybatis-3/dynamic-sql.html) for modularisation and code reuse. Why would I want to deal with another XML-based expression language, when I have a perfectly capable programming language with complete IDE support at my disposal?

Without the compiler protecting me from my own mistakes and the dreaded `NullPointerException` lurking behind every corner, I dearly missed the comfort and safety of the Scala ecosystem. So I started wondering how to get something akin to [case classes](https://docs.scala-lang.org/tour/case-classes.html) and [Quill](https://getquill.io) in the cold and harsh winter of the Java world. Let's start with a brief wishlist.

All I want for Christmas is

* A substitute for case classes — immutable objects, preferrably with support for cloning (with modified fields) and [optics](https://lahteenmaki.net/dev_*16/#/).
* Support for mapping primitive database fields to enums, value classes and complex types.
* Dedicated domain objects for creating and reading (more on this below).
* Type-safe, modular queries.
* Minimal boilerplate would be the icing on the gingerbread biscuit.

# Libraries

After a little bit of research, I identified the following libraries as possible candidates for the persistence and domain layers:

* [Immutables](https://immutables.github.io) — Annotation processor to generate simple, safe and consistent value objects
* [MapStruct](https://mapstruct.org) — Another annotation processor; we will use it to map between domain objects and [data transfer objects (DTOs)](https://en.wikipedia.org/wiki/Data_transfer_object). I picked this library out of several similar ones since it seems to be mature and performant and supports Immutables out of the box.
* [Jinq](http://www.jinq.org) — Type-safe queries
* [Functional Java](https://www.functionaljava.org) — Because we can't have [Cats](https://typelevel.org/cats/).

The example code for this article can be found on [GitHub](https://github.com/devkat/mapstruct-immutable-example).

# The Domain Model

Let's start with our domain model. We define the interfaces of our domain objects and let Immutables generate the implementations.

```java
@Value.Immutable @Tuple
public interface Book {

    String getTitle();

}
```

```java
@Value.Immutable @Tuple
public interface Character {

    String getName();

    Option<Species> getSpecies();

}
```

A few things worth mentioning:

* The `@Tuple` annotation denotes that we would like to use a [single static method](https://immutables.github.io/immutable.html#tuple-style) to initialize our objects, as opposed to a [builder](https://immutables.github.io/immutable.html#builder). This way we can ensure at compile-time that all fields are initialized.
* The type `Species` is an enum. JPA can map this to an ordinal value.
* We are using the `Option` class from the Functional Java library, a safer and more convenient alternative to Java's `Optional`. We use a [custom JPA attribute converter](https://github.com/devkat/mapstruct-immutable-example/blob/main/src/main/java/com/github/devkat/persistence/conversion/OptionConverter.java) to map this field to a nullable field in the DTO.

# Domain object identifiers

You may have noticed that our domain classes are lacking an identifier which will mapped to a primary key in the DTO. This is by design: Primary keys are often automatically generated by the database. When we create a new domain object, it doesn't have an identifier yet. We could model it as an `Option`, but this wouldn't accurately reflect reality — a domain object *never* has an ID before it is persisted, and it *always* has an ID when it has been retrieved from the database. Instead, we wrap our domain object in a separate object to augment it with the ID:

```java
@Value.Immutable
public interface WithId<ID, A> {

    ID getId();

    A getData();

}
```

We will use value objects for our IDs as an additional safety measure. Luckily, Immutables supports them out of the box by means of [wrapper types](https://immutables.github.io/immutable.html#wrapper-types):

```java
@Value.Immutable @Wrapped
abstract class _BookId extends Wrapper<Integer> {}
```

```java
@Value.Immutable @Wrapped
abstract class _CharacterId extends Wrapper<Integer> {}
```

Based on these definitions, the Immutables annotation processor will generate the concrete classes `BookId` and `CharacterId`.

Last but not least, we define a many-to-many relation between books and characters:

```java
@Value.Immutable @Tuple
public interface BookToCharacter {

    BookId getBookId();

    CharacterId getCharacterId();

}
```

# Data transfer objects (DTOs)

Due to the limitations of JPA, in addition to our domain classes, we have to use dedicated mutable classes for the data transfer objects. This results in quite a bit of knowledge duplication and boilerplate. If you know a solution for this problem, I would be very happy to hear about it in the comments section. It would more or less render this article obsolete, which I wouldn't be particularly unhappy about.

Since the DTOs are significantly more verbose than the domain objects, I will only show the most relevant sections.

```java
@Entity
public class BookDto implements WithIdDto<BookId, Book> {

    @Override
    public WithId<BookId, Book> toEntity() { … }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String title;

    // Getters and setters
}
```

```java
@Entity
public class CharacterDto implements WithIdDto<CharacterId, Character> {

    @Override
    public WithId<CharacterId, Character> toEntity() { … }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String name;

    @Enumerated(EnumType.ORDINAL)
    @Convert(converter = OptionConverter.class)
    private Option<Species> species;

    // Getters and setters

}
```

Both of these classes implement the `WithIdDto` interface, which defines the `toEntity()` method. This method allows us to convert a DTO to the domain object, including the ID which is derived from the primary key.

And, last but not least, the DTO for our relation, which uses a composite primary key. We are using an `@IdClass` instead of an `@EmbeddedId` to simplify the mapping from/to the domain class.

```java
@Entity
@IdClass(BookToCharacterDto.BookToCharacterId.class)
public class BookToCharacterDto {

    public static class BookToCharacterId implements Serializable {

        private final int bookId;
        private final int characterId;

        // Constructor and getters
    }

    @Id
    private int bookId;

    @Id
    private int characterId;

    // Getters and setters
}
```

# Mapping between domain objects and DTOs

The heavy lifting is done by MapStruct, we just have to declare our mappers. First, we define a common interface for all domain-object-to-DTO mappers:

```java
public interface DtoMapper<E, D> {

    D toDto(final E entity);

    E fromDto(final D dto);

}
```

Now we declare the individual mappers:

``` java
import static org.mapstruct.factory.Mappers.getMapper;

public final class Mappers {

    @Mapper(uses = BookIdMapper.class)
    public interface BookMapper
            extends DtoMapper<Book, BookDto> {
        BookMapper instance = getMapper(BookMapper.class);
    }

    @Mapper(uses = CharacterIdMapper.class)
    public interface CharacterMapper
            extends DtoMapper<Character, CharacterDto> {
        CharacterMapper instance = getMapper(CharacterMapper.class);
    }

    @Mapper(uses = { BookIdMapper.class, CharacterIdMapper.class })
    public interface BookToCharacterMapper {
        BookToCharacterMapper instance =
                getMapper(BookToCharacterMapper.class);
        BookToCharacterDto toDto(final BookToCharacter entity);
    }

}
```

Note that `BookToCharacterMapper` doesn't extend `DtoMapper`; for some reason MapStruct refused to generate the mapper class.

I implemented a custom mapper for each wrapper type. There is probably a way to get rid of this boilerplate, but it didn't bother me enough yet to spend more time on this issue:

``` java
public class BookIdMapper {

    public BookId asBookId(final int id) {
        return BookId.of(id);
    }

    public int asInt(final BookId bookId) {
        return bookId.value();
    }

}
```

Finally, let's take a quick look at the implementation of the `toEntity` method in our DTOs:

``` java
@Entity
public class BookDto implements WithIdDto<BookId, Book> {

    @Override
    public WithId<BookId, Book> toEntity() {
        return ImmutableWithId.of(
                BookId.of(id),
                Mappers.BookMapper.instance.fromDto(this)
        );
    }

    // …
}
```

# Querying data

Now that we have declared our domain objects and defined how they are persisted, we can start using them. I won't go into the details of building queries with Jinq — this is covered extensively in the documentation — and rather just show some example code.

``` java
public class BookService extends AbstractPersistenceService {

    …

    public Stream<P2<
                WithId<BookId, Book>,
                WithId<CharacterId, Character>
            >> getBooksWithCharacters() {
        return Stream
                .iterableStream(books(entityManager)
                .leftOuterJoin(
                        (b, source) -> source.stream(BookToCharacterDto.class),
                        (b, r) -> b.getId() == r.getBookId()
                )
                .leftOuterJoin(
                        (p, source) -> source.stream(CharacterDto.class),
                        (p, c) -> p.getTwo().getCharacterId() == c.getId()
                )
                .toList())
                .map(TupleOps::p2)
                .map(p -> p
                        .map1(TupleOps::p2)
                        .map1(P2::_1)
                        .map1(WithIdDto::toEntity)
                        .map2(WithIdDto::toEntity));
    }

}
```

At this level, we still have to deal with the DTO classes, but only to build queries, so there is no danger of introducing mutable state.

The joins result in nested Jinq tuples. At the end we transform these tuples into FunctionalJava tuples, and map the contained DTOs to their respective entities. In a real-world project, I would make this code generic and reuse it across all services.

# Using the service

Now we can take a look at our test method, which persists and reads domain objects using the service:

``` java
    @Test
    public void testPersistence() {
        transactional(() -> {

            final WithId<BookId, Book> book =
                bookService.createBook(ImmutableBook.of("Night Watch"));

            final Effect1<Character> addCharacter = (ch) -> {
                final WithId<CharacterId, Character> chRead =
                    bookService.createCharacter(ch);
                bookService.addCharacterToBook(
                    book.getId(), chRead.getId());
            };

            final Character vimes = ImmutableCharacter
                .of("Samuel Vimes", some(HUMAN));
            final Character nobby = ImmutableCharacter
                .copyOf(vimes)
                .withName("Nobby Nobbs")
                .withSpecies(none());

            addCharacter.f(vimes);
            addCharacter.f(nobby);

            final Stream<P2<
                WithId<BookId, Book>,
                WithId<CharacterId, Character>
            >> booksWithCharacters =
                    bookService.getBooksWithCharacters();

            assertTrue(booksWithCharacters.isNotEmpty());

            booksWithCharacters.foreach(tuple((bk, ch) -> {
                System.out.printf("%s (%s)%n",
                    ch.getData().getName(),
                    ch.getData().getSpecies());
                return Unit.unit();
            }));

        });
    }
```

# Summary

We have managed to address some critical pain points, in particular we can map between immutable domain objects and DTOs with a reasonable amout of boilerplate and uild type-safe queries using Java code.

Evidently, there is quite a bit of room for improvement:

* Our implementation supports both the Builder style (for MapStruct) and the Tuple style (for our code). Using the Builder style can lead to runtime errors since it doesn't ensure at compile time that all fields are initialised. Please leave a comment if you know how to get MapStruct to use the Tuple style for mapping.
* The error messages emitted by the MapStruct and Immutables annotation processors leave a lot to be desired. I'm not particularly fond of meta programming in general; this experience confirms my preconception. Maybe it is possible to get more information by setting compiler arguments, but my attempts were unsuccessful.
* Jinq queries are not completely type-safe, there can still be runtime errors ("Could not analyze lambda code").
* We are not treating effects as [first-class citizens](https://dev.to/devkat/effects-and-the-illusion-of-correctness-258j). The Functional Java library provides an `IO` type for this purpose, but this is a topic for a future article.