DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Architecture and Code Design, Pt. 1: Relational Persistence Insights to Use Today and On the Upcoming Years
  • Simplify Java Persistence Using Quarkus and Hibernate Reactive
  • From Naked Objects to Naked Functions
  • Fixing Your Microservices Architecture Using Graph Analysis

Trending

  • Comparing SaaS vs. PaaS for Kafka and Flink Data Streaming
  • Subtitles: The Good, the Bad, and the Resource-Heavy
  • Mastering Advanced Traffic Management in Multi-Cloud Kubernetes: Scaling With Multiple Istio Ingress Gateways
  • How to Format Articles for DZone
  1. DZone
  2. Data Engineering
  3. Databases
  4. Domain Object Persistence

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.

By 
Grzegorz Ziemoński user avatar
Grzegorz Ziemoński
·
May. 09, 16 · Analysis
Likes (9)
Comment
Save
Tweet
Share
14.7K Views

Join the DZone community and get the full member experience.

Join For Free

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.

Database Object (computer science) Relational database Persistence (computer science) application IT Domain model Inheritance (object-oriented programming)

Opinions expressed by DZone contributors are their own.

Related

  • Architecture and Code Design, Pt. 1: Relational Persistence Insights to Use Today and On the Upcoming Years
  • Simplify Java Persistence Using Quarkus and Hibernate Reactive
  • From Naked Objects to Naked Functions
  • Fixing Your Microservices Architecture Using Graph Analysis

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!