Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly map between persistence layer and domain object

Tags:

Let's say I have a domain java class representing a person:

class Person {

    private final String id; // government id
    private String name;
    private String status;

    private Person(String id, String name) {
        this.id = id;
        this.name = name;
        this.status = "NEW";
    }

    Person static createNew(String id, String name) {
        return new Person(id, name);
    }

    void validate() {
        //logic
        this.status = "VALID";
    }


    public static final class Builder {

        private String id;
        private String name;
        private String status;

        private Builder() {
        }

        public static Builder aPerson() {
            return new Builder();
        }

        public Builder id(String id) {
            this.id = id;
            return this;
        }

        public Builder name(String name) {
            this.name = name;
            return this;
        }

        public Builder status(String status) {
            this.status = status;
            return this;
        }

        public Person build() {
            Person person = new Person(id, name);
            person.status = this.status;
            return person;
        }
    }

I store this domain class object in a database, regular class with the same field + getters and setters. Currently when I want to store object I create new PersonDocument (data is stored in mongo), use getters and setters and save it. It gets complicated when I want to fetch it from DB. I would like my domain object to expose only what is necessary, for the business logic currently it is only creation and validation. Simply:

Person p = Person.createNew("1234", "John");
p.validate();
repository.save(p); 

The other way it gets complicated, currently there is a builder which allows creation of object in any state. We do believe that data stored in DB has a proper state so it can be created that way but the downside is that there is a public API available, letting any one to do anything.

The initial idea was to use MapStruct java mapping library but it does use setters to create objects and exposing setters in the domain class (as far as I can tell) should be avoided.

Any suggestions how to do it properly?

like image 758
SirKometa Avatar asked Nov 12 '18 10:11

SirKometa


People also ask

What is a persistence domain object?

Its a state of a Domain object. a persistent instance has a representation in the database and an identifier value. It might just have been saved or loaded, however, it is by definition in the scope of a Session. For example see the states of objects here in java ORM framework hibernate.

Is a lightweight persistence domain object?

An entity is a lightweight persistence domain object. Typically, an entity represents a table in a relational database, and each entity instance corresponds to a row in that table. The primary programming artifact of an entity is the entity class, although entities can use helper classes.

What is a persistence layer?

In Java servers, persistence layer is also known as the repository layer. This layer is responsible for data persistence and is used by the business layer to access the cache and database. In terms of functionality, it is fine to write the persistence layer code in the service class.

What is persistence layer in c#?

Persistence Layer = generally means to isolate read/write/delete logic from the business logic. ideally by placing a few (or a single) point of interaction between the business logic and the persistence modules.


2 Answers

Your problem likely comes from two conflicting requirements:

  1. You want to expose only business methods.
  2. You want to expose data too, since you want to be able to implement serialization/deserialization external to the object.

One of those has to give. To be honest, most people faced with this problem ignore the first one, and just introduce setter/getters. The alternative is of course to ignore the second one, and just introduce the serialization/deserialization into the object.

For example you can introduce a method Document toDocument() into the objects that produces the Mongo compatible json document, and also a Person fromDocument(Document) to deserialize.

Most people don't like this sort of solution, because it "couples" the technology to the object. Is that a good or bad thing? Depends on your use-case. Which one do you want to optimize for: Changing business logic or changing technologies? If you're not planning to change technologies very often and don't plan using the same class in a completely different application, there's no reason to separate technology.

like image 110
Robert Bräutigam Avatar answered Dec 07 '22 22:12

Robert Bräutigam


Robert Bräutigam sentence is good:

Two conflicting requirements

But the is another sentence by Alan Kay that is better:

“I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is messaging.” ~ Alan Kay

So, instead of dealing with the conflict, let's just change the approach to avoid it. The best way I found is to take a functional aproach and avoid unnecessary states and mutations in classes by expresing the domain changes as events.

Instead to map classes (aggregates, V.o.'s and/or entities) to persistence, I do this:

  1. Build an aggregate with the data needed (V.O.'s and entities) to apply aggregate rules and invariants given an action. This data comes from persistence. The aggregate do not expose getters not setters; just actions.
  2. Call the aggretate's action with command data as parameter. This will call inner entities actions in case the overal rules need it. This allows responsibility segregation and decoupling as the Aggregate Root does not have to know how are implemeted their inner entities (Tell, don't ask).
  3. Actions (in Aggregate roots and inner entities) does not modify its inner state; they instead returns events expressing the domain change. The aggregate main action coordinate and check the events returned by its inner entities to apply rules and invariants (the aggregate has the "big picture") and build the final Domain Event that is the output of the main Action call.
  4. Your persistence layer has an apply method for every Domain Event that has to handle (Persistence.Apply(event)). This way your persistence knows what was happened and; as long as the event has all the data needed to persist the change; can apply the change into (even with behaviour if needed!).
  5. Publish your Domain Event. Let the rest of your system knows that something has just happenend.

Check this post (it worth chek all DDD series in this blog) to see a similar implementation.

like image 44
jlvaquero Avatar answered Dec 07 '22 22:12

jlvaquero