Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Domain Object Persistence

DZone's Guide to

Domain Object Persistence

Persistence is a part of almost every Java enterprise application. Unfortunately, persisting domain objects is a non-trivial task. On the contrary, it generates a lot of problems, especially when we're using a relational database. Let's see different approaches to the problem.

Free Resource

Whether you work in SQL Server Management Studio or Visual Studio, Redgate tools integrate with your existing infrastructure, enabling you to align DevOps for your applications with DevOps for your SQL Server databases. Discover true Database DevOps, brought to you in partnership with Redgate.

Persistence is a part of almost every Java enterprise application. Unfortunately, persisting domain objects is a non-trivial task. On the contrary, it generates a lot of problems, especially when we're using a relational database[1]. Let's see different approaches to the problem.

All-in-one Model

The simplest approach, present in most applications, is to use the same model for persistence and business purposes (often in other layers too). From architectural perspective, we get something like this:

Image title

If we consider the fact that these entity classes most likely contain some annotations regarding the database (like JPA), then we make our whole application dependent on database details. Once we update anything in the database schema, we have to update our business classes. Problems related to ORM mechanisms are spread throughout the application e.g. we might get a lazy-init exception in our web controller. What's worse (from domain perspective), developers often add validation annotations to their entities to ensure that data written to the database is correct. At this point, people are extremely reluctant to add business functionalities to these classes, because they already contain a lot of code related to the database. Let's add a bunch of getters and setters (some mapping tools require that) and we get a recipe for database-centric architecture and anemic domain model. No good, no good sir!

Image title

Don't get me wrong. I'm not saying that all those bad things are always the case. I just think it's very hard to keep the model fit and avoid other problems with this solution, especially in big projects. And once these problems start to appear, this approach is no longer the simplest!

@Entity
@Table(name = "TODO_LISTS")
public class TodoList {

    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @Column(name = "NAME")
    @NotEmpty
    private String name;

    @OneToMany(mappedBy = "list")
    @NonNull
    private List<Todo> todos;

    // getters and setters
}

Such class used all around the codebase should definitely give off a code smell.

Separate Models

The next simplest solution, when we take into consideration all the problems mentioned above, is to separate models for the database stuff and the domain. The dependencies should look like this:

Image title

Now, the database layer is responsible for mapping database entities to domain objects and the other way around. The domain part doesn't know anything about the database stuff, which is definitely a good thing. We're also free from ORM related problems, like lazy-init exceptions, because we map all important information before it gets out of the database component.

In the code, our models might look like these:

@Entity
@Table(name = "TODO_LISTS")
public class DbTodoList {

    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @Column(name = "NAME")
    @NotEmpty
    private String name;

    @OneToMany(mappedBy = "list")
    @NonNull
    private List<DbTodo> todos;

    // getters and setters
}
public class TodoList {
    private Long id;
    private String name;
    private List<Todo> todos;

    // business methods

    // builder or setters
}

The obvious downside of this approach is that we have to add a lot of boilerplate mapping code like this:

public class Db2DomainTodoListMapper {

    public static TodoList toDomainModel(DbTodoList dbTodoList) {
        TodoList todoList = new TodoList();
        todoList.setId(dbTodoList.getId());
        todoList.setName(dbTodoList.getName());
        todoList.setTodos(toDomainModel(dbTodoList.getTodos()));
        return todoList;
    }

    // toDomainModel(List<DbTodo> todos) etc.
}

If we have a lot of classes with a lot of state, this can be painful (or rather boring, and I strongly discourage using any magic automappers for this process). On the other hand, complex mappings like enums to strategies or collections are pretty straightforward in this solution. Also, if we use a builder instead of setters, our encapsulation improves a lot. Nobody can change our object's state in any uncontrolled manner.

Model Inheritance

We might decide that these extra mapping classes and separate models are making our application too complex. In such case, we can use inheritance to achieve proper domain-database separation. Look at the diagram:

Image title


Now, we use the database layer to provide all required state for our domain object. Since the domain part doesn't have state[2] anymore, there's nothing to map!

Look at the code, enjoy the beauty of pure, abstract business class and a poor database servant:

@Entity
@Table(name = "TODO_LISTS")
public class DbTodoList extends TodoList {

    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @Column(name = "NAME")
    @NotEmpty
    private String name;

    @OneToMany(mappedBy = "list")
    @NonNull
    private List<Todo> todos;

    // getters and setters overriding the abstract methods
}
public abstract class TodoList {
    protected abstract Long getId();
    protected abstract void setId(Long id);
    protected abstract String getName();
    protected abstract void setName(String name);
    protected abstract List<Todo> getTodos();
    protected abstract void setTodos(List<Todo> todos);

    // business methods
}

It looked very strange to me when I first saw it. But it works and, in fact, works very well. We have our separation and we don't have any boilerplate code. From the encapsulation point of view, we don't have to expose anything as public until we really need to. Perfect solution?

Not really. Now, we're exposed to ORM-related problems again, because our domain objects are JPA entities under the hood. Also, it can be pretty tricky to effectively use mappings like collections or enum-to-strategy in this setup. It's not impossible, but definitely harder, which may make our colleague developers (or ourselves) reluctant to deal with the problem.

Conclusion

It's all about discipline and complexity. In general, we should strive to keep our domain model as database independent as possible. Separate domain and database models generate least problems and make mapping pretty straightforward, but require a lot of boilerplate code. Inheritance, on the other hand, seems convenient to use, but can cause same problems as all-in-one model if used incorrectly. In the end, none of these solutions forces clean separation, they just enable it.


[1]: First time you read that wiki page, you might get a feeling that it's impossible for OO and RDBMs to work together :D

[2]: Technically, the object can have concrete state and we can add JPA annotations to getters/setters in the DB derivative, but many sources suggest that annotating fields is a better approach to using JPA in general.

It’s easier than you think to extend DevOps practices to SQL Server with Redgate tools. Discover how to introduce true Database DevOps, brought to you in partnership with Redgate

Topics:
java ,jpa ,domain classes ,database

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}