Mapping domain models

As mentioned before, entities, aggregates, and value objects are integrated with JPA without introducing many constraints on the model. Entities as well as aggregates, are represented as JPA entities:

import javax.persistence.*;

@Entity
@Table(name = "persons")
public class Person {

    @Id
    @GeneratedValue
    private long id;

    @Basic(optional = false)
    private String name;

    @Embedded
    private Address address;

    ...
}

@Embeddable
public class Address {

    @Basic(optional = false)
    private String streetName;

    @Basic(optional = false)
    private String postalCode;

    @Basic(optional = false)
    private String city;

    ...
}

The person type is an entity. It needs to be identifiable using an ID that will be the primary key in the persons table. Every property is mapped into the database in a certain way, depending on the nature of the type and relation. The person's name is a simple text-based column.

The address is a value object that is not identifiable. From a domain perspective, it does not matter which address we refer to, as long as the values match. Therefore the address is not an entity and thus is not mapped into JPA as such. Value objects can be implemented via JPA embeddable types. The properties of these types will be mapped to additional columns in the table of the entity that refers to them. Since the person entity includes a specific address value, the address properties will be part of the persons table.

Root aggregates that consist of several entities can be realized by configuring the relations to be mapped in appropriate database columns and tables, respectively. For example, a car consists of an engine, one or more seats, a chassis, and many other parts. Some of them are entities that potentially can be identified and accessed as individual objects. The car manufacturer can identify the whole car or just the engine and repair or replace it accordingly. The database mapping can be placed on top of this existing domain model as well.

The following code snippets show the car domain entity, including JPA mapping:

import javax.persistence.CascadeType;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;

@Entity
@Table(name = "cars")
public class Car {

    @Id
    @GeneratedValue
    private long id;

    @OneToOne(optional = false, cascade = CascadeType.ALL)
    private Engine engine;

    @OneToMany(cascade = CascadeType.ALL)
    private Set<Seat> seats = new HashSet<>();

    ...
}

The seats are included in a collection. The HashSet is instantiated for new Car instances; Java collections that are null should be avoided.

The engine represents another entity in our domain:

import javax.persistence.EnumType;
import javax.persistence.Enumerated;

@Entity
@Table(name = "engines")
public class Engine {

    @Id
    @GeneratedValue
    private long id;

    @Basic(optional = false)
    @Enumerated(EnumType.STRING)
    private EngineType type;

    private double ccm;

    ...
}

The car seats represent entities as well, identifiable by their ID:

@Entity
@Table(name = "seats")
public class Seat {

    @Id
    @GeneratedValue
    private long id;

    @Basic(optional = false)
    @Enumerated(EnumType.STRING)
    private SeatMaterial material;

    @Basic(optional = false)
    @Enumerated(EnumType.STRING)
    private SeatShape shape;

    ...
}

All entities, referenced from other entities or standalone, need to be managed in the persistence context. If the engine of a car is replaced by a new entity, this needs to be persisted separately as well. The persist operations are either called explicitly on the individual entities or cascaded from object hierarchies. The cascades are specified on the entity relations. The following code shows the two approaches of persisting a new car engine from a service:

public void replaceEngine(long carIdentifier, Engine engine) {
    Car car = entityManager.find(Car.class, carIdentifier);
    car.replaceEngine(engine);

    // car is already managed, engine needs to be persisted
    entityManager.persist(engine);
}

After loading the car from its identifier, it is a managed entity. The engine still needs to be persisted. The first approach persists the engine explicitly in the service.

The second approach cascades a merge operation, that also handles new entities, from the car aggregate:

public void replaceEngine(long carIdentifier, Engine engine) {
    Car car = entityManager.find(Car.class, carIdentifier);
    car.replaceEngine(engine);

    // merge operation is applied on the car and all cascading relations
    entityManager.merge(car);
}

It is highly advisable to apply the latter approach. Aggregate roots are responsible to maintain an integer and consistent state of the overall state. The integrity is achieved more reliably when all operations are initiated and cascaded from the root entity.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.3.175