One of the most common tasks in Zest™ is the management of the life cycle of Entities. Since Zest™ is capable of delivering much higher performance than traditional Object-Relational Mapping technologies, we also expect that people use Entities more frequently in Zest™ applications, so it is a very important topic to cover.
If you want to reproduce what’s explained in this tutorial, remember to depend on the Core Bootstrap artifact:
Moreover, you’ll need an EntityStore for persistence and an Indexing engine for querying. Choose among the available implementations listed in the Extensions section.
At runtime you will need the Core Runtime artifact too. See the Depend on Zest™ in your build tutorial for details.
All Entity operations MUST be done within a UnitOfWork. UnitOfWorks can be nested and if underlying UnitOfWorks are not completed (method complete()), then none of the operations will be persisted permanently.
Entity composites are subtypes of the EntityComposite interface.
Domain code typically don’t need to know of the EntityComposite types directly, and is instead using the domain specific interface. The Visibility rules will be applied to associate the right EntityComposite when a domain type is requested. Ambiguities are not accepted and will result in runtime exceptions.
Zest™ supports that each entity instance can have more than one entity type, and it is managed per instance. This feature is beyond the scope of this HowTO and will be covered subsequently.
We have made the observation that it is good practice to separate the internal state from the observable behavior. By this we mean that it is not a good practice to allow client code to manipulate or even view the internal states of objects, which is such a common (bad) practice in the so called POJO world.
Instead, we recommend that the programmer defines the client requirement of what each participant within the client context needs to conform to, and then create composites accordingly and hide all the state internal to the composite in private mixins. By doing so, the same entity can participate in multiple contexts with different behavioral requirements but using the same internal state.
We recommend limited use of primitive types for Properties and instead subtype the Property.
And try to use ValueComposites instead of Entities.
We need an entity to illustrate how we recommend to separate internal state from public behavior and observable state. We will for the sake of simplicity use a trivial example. Please refer to other (possibly future) HowTos on patterns on Entity management.
public interface Car { @Immutable Association<Manufacturer> manufacturer(); @Immutable Property<String> model(); ManyAssociation<Accident> accidents(); }
public interface Manufacturer { Property<String> name(); Property<String> country(); @UseDefaults Property<Long> carsProduced(); }
public interface Accident { Property<String> description(); Property<Date> occured(); Property<Date> repaired(); }
Above we define a Car domain object, which is of a particular Manufacturer (also an Entity), a model and a record of Accidents.
We will also need to define the composites for the above domain structure;
public interface CarEntity extends Car, EntityComposite {}
public interface ManufacturerEntity extends Manufacturer, EntityComposite {}
public interface AccidentValue extends Accident, ValueComposite {}
For this case, we define both the Car and the Manufacturer as Entities, whereas the Accident is a Value, since it is an immutable event that can not be modified.
All of the above must also be declared in the assembly. We MUST associate the EntityComposites with a relevant Module. We must also assemble an EntityStore for the entire application, but that is outside the scope of this HowTo.
public class MyAssembler implements Assembler { public void assemble( ModuleAssembly module ) { module.entities( CarEntity.class, ManufacturerEntity.class ); module.values( AccidentValue.class ); [...snip...] } }
We have no other Composites involved yet, so we can proceed to look at the usage code.
We recommend that the life cycle management of entities is placed inside domain factories, one for each type and made available as services.
The entity factory is something you need to write yourself, but as with most things in Zest™ it will end up being a fairly small implementation. So how is that done?
public interface CarEntityFactory { Car create(Manufacturer manufacturer, String model); }
That is just the domain interface. We now need to make the service interface, which Zest™ needs to identify services and make it possible for the service injection later.
@Mixins( { CarEntityFactoryMixin.class } ) public interface CarEntityFactoryService extends CarEntityFactory, ServiceComposite {}
Then we need an implementation of the mixin.
public class CarEntityFactoryMixin implements CarEntityFactory {
And doing that, first of all we need to request Zest™ runtime to give us the Module that our code belongs to, and the UnitOfWork current context the execution is happening in.
Injections that are related to the Visibility rules are handled by the @Structure annotation. And the easiest way for us to obtain a Module is simply to;
public class CarEntityFactoryMixin implements CarEntityFactory { @Structure Module module;
Here Zest™ will inject the member module with the correct Module. In case we only need the Module during the construction, we can also request it in the same manner as constructor argument.
public CarEntityFactoryMixin( @Structure Module module ) { }
This is important to know, since the injected member will not be available until AFTER the constructor has been completed.
We then need to provide the implementation for the create() method.
public Car create(Manufacturer manufacturer, String model) { UnitOfWork uow = module.currentUnitOfWork(); EntityBuilder<Car> builder = uow.newEntityBuilder( Car.class ); Car prototype = builder.instance(); prototype.manufacturer().set( manufacturer ); prototype.model().set( model ); return builder.newInstance(); }
So far so good. But how about the Manufacturer input into the create() method?
DDD promotes the use of Repositories. They are the type-safe domain interfaces into locating entities without getting bogged down with querying infrastructure details. And one Repository per Entity type, so we keep it nice, tidy and re-usable. So let’s create one for the Manufacturer type.
public interface ManufacturerRepository { Manufacturer findByIdentity(String identity); Manufacturer findByName(String name); }
And then we repeat the process for creating a Service…
@Mixins( ManufacturerRepositoryMixin.class ) public interface ManufacturerRepositoryService extends ManufacturerRepository, ServiceComposite {}
and a Mixin that implements it…
public class ManufacturerRepositoryMixin implements ManufacturerRepository { @Structure private UnitOfWorkFactory uowf; @Structure private Module module; public Manufacturer findByIdentity( String identity ) { UnitOfWork uow = uowf.currentUnitOfWork(); return uow.get(Manufacturer.class, identity); } public Manufacturer findByName( String name ) { UnitOfWork uow = uowf.currentUnitOfWork(); QueryBuilder<Manufacturer> builder = module.newQueryBuilder( Manufacturer.class ); Manufacturer template = templateFor( Manufacturer.class ); builder.where( eq( template.name(), name ) ); Query<Manufacturer> query = uow.newQuery( builder); return query.find(); } }
But now we have introduced 2 services that also are required to be declared in the assembly. In this case, we want the Services to be available to the application layer above, and not restricted to within this domain model.
public class MyAssembler implements Assembler { public void assemble( ModuleAssembly module ) { module.entities( CarEntity.class, ManufacturerEntity.class ); module.values( AccidentValue.class ); module.addServices( ManufacturerRepositoryService.class, CarEntityFactoryService.class ).visibleIn( Visibility.application ); } }
If you notice, there is a couple of calls to Module.currentUnitOfWork(), but what is current UnitOfWork, and who is setting that up?
Well, the domain layer should not worry about UoW, it is probably the responsibility of the application/service layer sitting on top. That could be a web application creating and completing a UoW per request, or some other co-ordinator doing long-running UnitOfWorks.
There are of course a lot more details to get all this completed, but that is beyond the scope of this HowTo. See UnitOfWork in Core API.