Domain-Driven Design: Manage Data With Jakarta Data and JNoSQL
Learn how to simplify data management in DDD applications through practical examples of using domain-focused repositories and business semantics across databases.
Join the DZone community and get the full member experience.
Join For FreeManaging data access in Domain-Driven Design (DDD) applications can be challenging, especially when working with different database types, like relational and NoSQL. This article explores how leveraging tools like JNoSQL and Jakarta Data helps simplify data access, making it easier to implement domain-focused repositories, integrate ubiquitous language, and naturally align business semantics with your code.
Understanding DDD vs. DAO
Before diving into the practical implementation details, it's essential to clarify the distinction between Domain-Driven Design (DDD) and the Data Access Object (DAO) pattern. Understanding this difference will help recognize the value of domain-focused repositories in DDD applications.
- DAO focuses on low-level database operations, like queries and CRUD methods. It’s commonly found in heavily database-centric applications that push business logic into service layers. DAOs prioritize data persistence over domain semantics.
- DDD, on the other hand, emphasizes the Ubiquitous Language—a shared vocabulary between the technical team and business stakeholders. This concept permeates the code, including repositories that abstract database access with meaningful domain actions. Instead of thinking about low-level database queries, you model operations like
checkIn
orreserve
in the language of the business, keeping the domain and persistence logic closely aligned.
Aspect | DAO (Data Access Object) | DDD Repository (Domain-Driven Design Repository) |
---|---|---|
Focus | Primarily on data persistence and CRUD operations | Focuses on the domain model, representing business logic |
Responsibilities | Handles database access, queries, and low-level persistence | Encapsulates domain logic, acting as an abstraction for the domain |
Language | Uses technical language (e.g., save , delete , query ) |
Uses business-oriented language (e.g., checkIn , reserve ) |
Separation of Concerns | Often combines database and business logic | Keeps business logic in the domain, separating it from infrastructure |
Transaction Handling | May handle transactions directly | Relies on the application service layer for transaction management |
Use Case | Suited for generic CRUD operations across various applications | Tailored for domain-specific operations in DDD-based applications |
Complexity | Simpler, more focused on database-centric operations | More complex, dealing with domain entities and business rules |
Scalability | Designed to handle data access in a straightforward way | Scales with complex domain models and evolving business logic |
Entity Lifecycle | Focuses on managing the entity lifecycle at the database level | Focuses on the lifecycle of domain entities and value objects |
Data Source | Primarily tied to one data source | Can abstract over multiple data sources (e.g., NoSQL, relational) |
While both patterns handle data persistence, they serve distinct purposes based on the complexity of the domain and business requirements. Understanding when to use each can help ensure your application's architecture aligns with its goals, whether focusing on straightforward data access or embedding rich business logic into your data layer.
DAO (Data Access Object) is the best choice for applications deeply rooted in database-centric architectures, where the primary concern is interacting with the database straightforwardly, such as performing CRUD (Create, Read, Update, Delete) operations. It is a valuable tool in scenarios where the business logic is simple or the application's primary focus is data storage and retrieval without complex domain rules. DAOs are particularly effective in such environments, where the persistence layer plays a significant role and decoupling business logic from database operations is less of a concern. DAO is a good choice if the goal is efficient data access without much domain complexity.
A DDD repository is ideal for more complex, domain-driven applications where business logic is central and you must align data operations with business semantics. The DDD repository pattern shines in scenarios where the domain model drives the application architecture—such as when dealing with rich business logic, evolving domain concepts, and multiple data sources. It provides a layer of abstraction that focuses on the domain, allowing developers to express business actions in the ubiquitous language. A DDD repository is the right fit if the goal is to maintain a clear separation between business logic and infrastructure while keeping the code aligned with business processes.
The new features in Eclipse JNoSQL 1.1.2 allow developers to create repositories focusing on domain semantics rather than database operations, enabling a more natural integration of DDD concepts.
Jakarta Data introduces an API that abstracts the complexities of data access across various databases, whether relational or NoSQL. This release empowers Java developers to interact with databases using repository interfaces that support custom queries and business actions without sacrificing NoSQL’s power or the database systems’ flexibility.
Eclipse JNoSQL 1.1.2 enhances Jakarta Data with features that allow you to write business actions using annotations, keeping the domain terminology consistent and explicit. This feature makes your code a direct reflection of the business model, bridging the gap between technical implementation and business requirements. For example, a hotel management system could utilize the language of “check-in,” “check-out,” and “reservation” to manage guest interactions with rooms.
This tutorial will build a simple Hotel Management System using Jakarta Data, Oracle NoSQL, and Helidon to register guests into rooms. This sample demonstrates how these features can help align your codebase with DDD principles.
Setting Up Oracle NoSQL and Helidon
To start, you’ll need to set up Oracle NoSQL on the cloud or run a standalone version. For a quick standalone setup, you can run the following Docker command:
docker run -d --name oracle-instance -p 8080:8080 ghcr.io/oracle/nosql:latest-ce
Next, create a Helidon project. Here’s how:
- Visit the Helidon Starter.
- Select Helidon MP as the flavor and proceed.
- Choose Quickstart for the application type and enable JSON-B media support.
- Configure your project with desired options like group and artifact ID.
- Generate and download the project.
Dependencies Setup
Once your Helidon project is generated, add the following dependencies in your pom.xml
file:
<dependency>
<groupId>org.eclipse.jnosql.databases</groupId>
<artifactId>jnosql-oracle-nosql</artifactId>
<version>${jnosql.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jnosql.metamodel</groupId>
<artifactId>mapping-metamodel-processor</artifactId>
<version>${jnosql.version}</version>
<scope>provided</scope>
</dependency>
Configure the database properties:
server.port=8181
jnosql.keyvalue.database=hotel
jnosql.document.database=hotel
jnosql.oracle.nosql.host=http://localhost:8080
We will model a Room as an entity and Guest as a value object. Here’s the code to define these:
@Entity
public record Room (@Id String number, @Column Guest guest) {
}
@Embeddable(Embeddable.EmbeddableType.GROUPING)
public record Guest (@Column String documentNumber, @Column String name) {
}
In this design:
- Room is an entity with its own lifecycle.
- Guest is a value object, meaning it is embedded within the Room entity and not treated as a separate entity with its own lifecycle.
Creating the Repository
Now, let’s create a repository for managing hotel operations. This interface represents the business actions using domain semantics:
@Repository
public interface Hotel {
@Save
Room checkIn(Room room);
@Delete
void checkOut(Room room);
@Find
Optional<Room> reservation(@By(org.soujava.samples.hotel._Room.NUMBER) String number);
Page<Room> findBy(PageRequest pageRequest);
}
Here, we use annotations like @Save
, @Delete
, and @Find
to represent meaningful actions in the domain, such as checking in and checking out guests.
The final step is to expose these operations through a REST API using JAX-RS:
@Path("/hotels")
@ApplicationScoped
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class HotelResource {
private final Hotel hotel;
@Inject
public HotelResource(Hotel hotel) {
this.hotel = hotel;
}
@GET
public List<Room> rooms(@QueryParam("page") int page, @QueryParam("size") int size) {
var pageRequest = PageRequest.ofPage(page).size(size);
return hotel.findBy(pageRequest).content();
}
@GET
@Path("/{number}")
public Room reservation(@PathParam("number") String number) {
return hotel.reservation(number)
.orElseThrow(() -> new WebApplicationException("Room not found", Response.Status.NOT_FOUND));
}
@PUT
public Room checkIn(Room room) {
return hotel.checkIn(room);
}
@DELETE
@Path("/{number}")
public void checkOut(@PathParam("number") String number) {
hotel.checkOut(new Guest(number, null));
}
}
Once your application is running, you can interact with it using cURL
commands. Here’s an example:
Check-in a Guest:
curl -X PUT -H "Content-Type: application/json" -d '{"number":101, "guest":{"name":"John Doe", "documentNumber":"12345"}}' http://localhost:8181/hotels
Get All Rooms:
curl -X GET http://localhost:8181/hotels
Check-out a Guest:
curl -X DELETE http://localhost:8181/hotels/101
Conclusion
The combination of Jakarta Data and Eclipse JNoSQL offers developers tools for organizing data access in ways that align with domain-driven design principles. By using these frameworks, developers can write repositories that reflect business logic while maintaining flexibility across various data sources. However, it’s important to consider potential challenges, such as managing multiple database types or tuning performance when working with large datasets. With thoughtful implementation, these tools can help structure applications in a way that supports both technical requirements and evolving business needs.
Source code: https://github.com/soujava/hotel-reservation-syntax
Opinions expressed by DZone contributors are their own.
Comments