I am writing an application that I wish to follow the DDD patterns, a typical entity class looks like this:
@Entity
@Table(name = "mydomain_persons")
class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name="fullname")
private String fullName;
@OneToMany(cascade=ALL, mappedBy="item")
private Set<Item> items;
}
As you see, since the JPA/Hibernate heavily relies on annotations on entity classes, my domain entity classes are now polluted by persistence-aware annotations. This violates DDD principles, as well as separation of layers. Also it gives me problems with properties unrelated to ORM, such as events. If I use @Transient, it will not initialize a List of events and I have to do this manually or get weird errors.
Id like the domain entity to be a POJO(or POKO as I use Kotlin), so I do not want to have such annotations on the entity class. However I definitely do not wish to use XML configurations, its a horror and the reason why Spring developers moved on to annotations in the first place.
What are the options I have available? Should I define a DTO class that contains such annotations and a Mapper class that converts each DTO into the corresponding Domain Entity? Is this a good practice?
Edit: I know in C# the Entity Framework allows creation of mapping classes outside of Entity classes with Configuration classes, which is a way better alternative than XML hell. I aint sure such technique is available in the JVM world or not, anyone knows the below code can be done with Spring or not?
public class PersonDbContext: DbContext
{
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//Write Fluent API configurations here
//Property Configurations
modelBuilder.Entity<Person>().Property(p => p.id).HasColumnName("id").IsRequired();
modelBuilder.Entity<Person>().Property(p => p.name).hasColumnName("fullname").IsRequired();
modelBuilder.Entity<Person>().HasMany<Item>(p => p.items).WithOne(i => i.owner).HasForeignKey(i => i.ownerid)
}
The solution I have found for this problem is having abstract domain entities, implemented by my classes at the persistence layer (may or may not be the Hibernate entities themselves). That way, my domain classes know nothing about the persistence mechanisms, my persistence classes know nothing about business logic, and I mostly avoid mapping code. Let me expand on that:
Imagine a project laid out like this (this is pretty much the way I organize my projects):
-
|-business_logic
| |-person
| | |-Person.java
| | |-Item.java //assuming "item" is inside the Person aggregate
| | |-FullName.java // Let's make FullName a Value Object.
| | |-DoXWithPersonApplicationService.java
| |-aggregateB
| |-aggregateC
|
|-framework
| |-controllers
| |-repositories
| |-models
| | |-JpaPerson.java
| | |-JpaItem.java
| | |-etc.
Then your Person class might look something like this:
public abstract class Person {
public abstract int getId();
public abstract FullName getName();
protected abstract void setName(FullName name);
public abstract ImmutableSet<Item> getItems(); // Say you're using Guava
protected abstract void addItem(String itemName, int qtd);
protected abstract void removeItem(Item item);
void doBusinessStuff(String businessArgs) {
// Run complex domain logic to do business stuff.
// Uses own getters and setters.
}
}
Your FullName class might look like this:
public final class FullName {
private final String firstName;
private final String lastName;
// Constructors, factories, getters...
}
And then, finally, your JpaPerson class should look something like:
@Entity
@Table(name = "mydomain_persons")
public class JpaPerson extends Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name="firstName")
private String firstName;
@Column(name="lastName")
private String lastName;
@OneToMany(cascade=ALL, mappedBy="item")
private Set<Item> items;
@Override
public int getId() { return id; }
@Override
public FullName getName() { return FullName.of(firstName, lastName); }
@Override
protected void setName(FullName name) {
firstName = name.getFirst();
lastName = name.getLast();
}
// Implementations for the rest of the abstract methods...
// Notice the complete absence of "business stuff" around here.
}
A few points to notice:
protected
, but getters can be public
(or not). This makes so it's actually pretty safe to traverse relationships across aggregates to get data you need (entities look just like Value Objects from outside their packages).That's it. I'm sure it's not a silver bullet of any kind, but this pattern has served me well for some time now.
The lack of solution might be a good thing for several reasons. Typically it seems to me pretty sane that domain structures and persitence strategy are decoupled. You may want to apply somme persitence patterns in an independant way regarding the way you design your domain model. You don't care about dealing with legacy tables while designing from the top to the bottom, and you could have jpa entities pretty different from domain entities. And what's the problem with that? So it's not a problem since you keep implementing domain/jpa entities mapping in your repo with a FP like approach,reducing the bolerplate thing and setting aside the side effect to the DAO(s) call(s).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With