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

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

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

Related

  • Architecture and Code Design, Pt. 1: Relational Persistence Insights to Use Today and On the Upcoming Years
  • Making Your Life Easier Around Data With Java and Jakarta EE
  • Practical Generators in Go 1.23 for Database Pagination
  • Navigating NoSQL: A Pragmatic Approach for Java Developers

Trending

  • DZone's Article Submission Guidelines
  • Using Python Libraries in Java
  • The Smart Way to Talk to Your Database: Why Hybrid API + NL2SQL Wins
  • How To Build Resilient Microservices Using Circuit Breakers and Retries: A Developer’s Guide To Surviving
  1. DZone
  2. Data Engineering
  3. Data
  4. Data Software Design Pitfalls on Java: Should We Have a Constructor on JPA?

Data Software Design Pitfalls on Java: Should We Have a Constructor on JPA?

In this article, explore details on code, especially inside the Jakarta EE world, mainly to answer the questions: should we have a constructor on JPA, and why?

By 
Otavio Santana user avatar
Otavio Santana
DZone Core CORE ·
Jul. 31, 22 · Opinion
Likes (49)
Comment
Save
Tweet
Share
11.6K Views

Join the DZone community and get the full member experience.

Join For Free

The data in any modern and distributed architecture, such as microservices, work as a vein in a system. It fits like a state in a stateless application. On the other hand, we have the most popular paradigms in the code, especially when we talk about enterprise OOP. How do you combine both archive and software design, primarily on Java? 

This article will explore more details on code, especially inside the Jakarta EE world, mainly to answer the questions in a previous Jakarta JPA discussion: should we have a constructor on JPA, and why?

Context Data and Java

When we talk about Java and databases, the most systematic way to integrate both worlds is through thought frameworks. In the framework, we have types and categories based on communication levels and the usability of API.

  • Communication level: It defines how far the code is from a database or closer to the OOP domain. 
  • A driver is a framework level closer to OOP and domain, and far from a database. A driver we can smoothly work on is data-oriented. However, it might bring more boilerplate to obtain the code to the domain (e.g., JDBC).
  • A mapping goes in another direction, and thus, closer to OOP and far from the database. Where it reduces the boilerplate to a domain, we might face mismatch impedance and performance issues (e.g., Hibernate and Panache).
  • Usability of the API: Give an API, how many times will you use it for different databases? Once we have SQL as a standard on the relational database, we usually have one API for all database types.
  • A specific API is an API that works exclusively on a database. It often brings updates from this vendor; nonetheless, replacing a database means changing the whole API (e.g., Mophia, Neo4j-OGM Object Graph Mapper).
  • An agnostic API is a spread API where you have one API for many databases. It would be easier to use more databases, but the updates or particular database behavior are more challenging.

DDD vs. Data-Oriented

Whenever we talk about software design on Java, we mainly talk about the OOP paradigm. At the same time, a database is usually a different paradigm. The main difference is what we call the impedance mismatch. 

The OOP brings several approaches and good practices, such as encapsulation, composition, inheritance, polymorphism, etc., which won't have support on a database. 

You might read the book "Clean Code" where we have an Uncle Bob quote: "OOPs hide data to expose behavior." The DDD works this way to have a ubiquitous language and domain often around OOP. 

In his book "Data-Oriented Programming", author Yehonathan Sharvit proposes reducing complexity by promoting and treating data as a "first-class citizen."

This pattern summarizes three principles:

  1. The code is data separated.
  2. Data is immutable.
  3. Data has flexible access.

That is the biggest issue with both paradigms: it is hard to have both simultaneously, but it fits in the context. 

JPA and Data

The JPA is the most popular solution with relational databases. It is a Java standard to work, and we can see several platforms use it, such as Quarkus, Spring, and so on.

To fight against the impedance, JPA has several features to reduce this attraction, such as inheritance, where the JPA's implementation engine will translate to/from the database.

Java
 
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Product {
    @Id
    private long id;
    @Column
    private String name;

    //...
}


@Entity
public class Computer extends Product {

    @Column
    private String version;

}

@Entity
public class Food extends Product {

    @Column
    private Localdate expiry;

}


JPA and Constructor

Once we have the context, let's discuss this great Jakarta EE Ambassador discussion, and we also have a GitHub issue. 

We understand that there are always trade-offs when discussing software architecture and design. Thus, the enterprise architecture requires both DDD and a data-oriented approach based on the context.

Recently, Brian Goetz wrote an Oriented Data Programming in Java where he talks about how to archive success on data-programming using features such as record and sealed class.

It would be nice if we could explore and reuse record with JPA, but we have a legacy problem because JPA requires a default constructor. 

The question is, should it be enough? Or should JPA support more than OOP/DDD, ignoring the data programming? In my option, we should run for the data programming even if it breaks the previously-required default constructor.

"JPA requiring default constructors pretty much everywhere is a severe limitation to the entity design for dozens of reasons. Records make that pretty obvious. So, while you can argue that Persistence doesn't 'need ' to do anything regarding this aspect, I think it should. Because improving on this would broadly benefit Persistence, not only in persisting records." Oliver Drotbohm

We can imagine several scenarios where we can have benefits from the code design approach:

  • An immutable entity: We have a read-only entity. The source is the database. 
Java
 
public class City {
    
    private final String city;

    private final String country;

    public City(String city, String country) {
        this.city = city;
        this.country = country;
    }

    public String getCity() {
        return city;
    }

    public String getCountry() {
        return country;
    }
}


  • Force a bullet-proved entity: Imagine that we want both an immutable entity to force the consistency, and the entity is instantiated. So, we can combine it with Bean Validation to always create an entity when it brings valid values.
Java
 
public class Player {

    private final String name;

    private final  String city;

    private final  MonetaryAmount salary;

    private final  int score;

    private final  Position position;

    public Player(@Size(min = 5, max = 200) @NotBlank String name,
                  @Size(min = 5, max = 200) @NotBlank String city,
                  @NotNull MonetaryAmount salary,
                  @Min(0) int score,
                  @NotNull Position position) {
        this.name = name;
        this.city = city;
        this.salary = salary;
        this.score = score;
        this.position = position;
    }
}


JPA and Proposal

We learned from Agile methodology to release continuously and do a baby-step process. Consequently, we can start with support on two annotations, get feedback, fail-fast and then move it forward. 

As the first step, we can have a new annotation: constructor. Once we have it on the constructor, it will ignore the field annotations to use on the constructor. We can have support for two annotations: Id and Column.

Java
 
@Entity
public class Person {

    private final Long id;

    private final String name;

    @Constructor
    public Person(@Id Long id, @Column String name) {
        this.id = id;
        this.name = name;
    }

//...
    
}


We also should have support on Bean Validation on this step.

Java
 
@Entity
public class Person {
    @Id
    private final Long id;
    @Column
    private final String name;

    @Constructor
    public Person(@NotNull @Id Long id, @NotBlank @Column String name) {
        this.id = id;
        this.name = name;
    }

//...
    
}


You can explore records this case as well.

Java
 
@Entity
public record Person(@Id @NotNull Long id,  @NotBlank @Column String name){}


Annotations on a record component of a record class may be propagated to members and constructors of the record class as specified in 8.10.3.

The baby step is proposed and done. The next step is to receive feedback and points from the community.

Conclusion

The software design, mainly on OOP, is a rich world and brings several new perspectives. It is customary to review old concepts to get new ones. It happened with CDI, where it has improved the constructor to express a better design, and it should happen to JPA with the same proposal.con

Database Relational database Software design Data (computing) Java (programming language) Data Types

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
  • Making Your Life Easier Around Data With Java and Jakarta EE
  • Practical Generators in Go 1.23 for Database Pagination
  • Navigating NoSQL: A Pragmatic Approach for Java Developers

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!