-
Notifications
You must be signed in to change notification settings - Fork 7
Synthetic entities
Synthetic entities provide a way to implement the "informative function" of Information Systems and can aggregate data from other synthetic entities or persistent entities.
For simple data aggregration purposes, one can simply define a calculated property for a persistent entity. But, in more complex scenarios, a need for a synthetic entity arises.
An essential part of a synthetic entity is its underlying model, i.e., an EQL query. It can be defined in 2 ways:
-
As a single EQL query assigned to a static field
model_
.class TgAverageFuelUsage extends AbstractEntity<TgVehicle> { static final EntityResultQueryModel<TgAverageFuelUsage> model_ = select(TgVehicleFuelUsage.class).where(). prop("date").gt().iParam("datePeriod.from").and(). prop("date").lt().iParam("datePeriod.to"). groupBy().prop("vehicle"). yield().prop("vehicle").as("id"). yield().prop("vehicle").as("key"). yield().sumOf().prop("qty").as("qty"). yield().sumOf().prop("cost.amount").as("cost.amount"). modelAsEntity(TgAverageFuelUsage.class); @IsProperty private BigDecimal qty; @IsProperty @MapTo private Money cost; @IsProperty @CritOnly private Date datePeriod; }
-
As a non-empty list of EQL queries assigned to a static field
models_
. The resulting queries form an implicit union. This way is provided as a convenient alternative to specifying a union by hand.class SynConditionAssessment extends AbstractEntity<DynamicEntityKey> { static final List<EntityResultQueryModel<SynConditionAssessment>> models_ = List.of(modelForExternalAssessment(), modelForInternalAssessment()); static EntityResultQueryModel<SynConditionAssessment> modelForExternalAssessment() { return select(WorkActivity.class). where().prop(WorkActivity_.conditionAssessmentExternal()).isNotNull(). yield().prop(WorkActivity_.id()).as(SynConditionAssessment_.workActivity()). yield().prop(WorkActivity_.conditionAssessmentExternal()).as(SynConditionAssessment_.conditionAssessmentExternal()). yield().prop(WorkActivity_.conditionAssessmentExternal()).as(SynConditionAssessment_.conditionAssessment()). yield().val(null).as(SynConditionAssessment_.conditionAssessmentInternal()). yield().prop(WorkActivity_.conditionAssessmentExternalEntryDate()).as(SynConditionAssessment_.entryDate()). modelAsEntity(SynConditionAssessment.class); } static EntityResultQueryModel<SynConditionAssessment> modelForInternalAssessment() { return select(WorkActivity.class). where().prop(WorkActivity_.conditionAssessmentInternal()).isNotNull(). yield().prop(WorkActivity_.id()).as(SynConditionAssessment_.workActivity()). yield().prop(WorkActivity_.conditionAssessmentInternal()).as(SynConditionAssessment_.conditionAssessmentInternal()). yield().prop(WorkActivity_.conditionAssessmentInternal()).as(SynConditionAssessment_.conditionAssessment()). yield().val(null).as(SynConditionAssessment_.conditionAssessmentExternal()). yield().prop(WorkActivity_.conditionAssessmentInternalEntryDate()).as(SynConditionAssessment_.entryDate()). modelAsEntity(SynConditionAssessment.class); } }
A synthetic entity's model is not meant to be used directly by the developer. Rather, it gets processed by the EQL engine when a synthetic entity is used as a query source.
A synthetic entity can be one of the following kinds:
-
Based on a persistent entity, meaning that it extends a persistent entity type. Such a synthetic entity enriches the persistent one with properties that get calculated in the underlying model. It also inherits all properties of the persistent entity. Use cases include:
- Enhanced searching capabilities. E.g., by an association with another entity.
- Reporting. E.g., aggregating data from mutiple associatied entities and presenting results in the form of additional properties.
Such entities will greatly benefit from a convenient yielding syntax in EQL -
yieldAll()
, which is equivalent to yielding all properties (both declared and inherited) of the query source (which happens to be a persistent entity that the synthetic one is based on) explicitly. -
One-to-one association (i.e., having a key typed with a persistent entity). A synthetic entity of this kind is associated with a particular persistent entity, similar to those that are based on a persistent type. They differ, however, in that one-to-one synthetic entities don't inherit properties of the persistent entity (because there is no subtyping relationship).
-
Otherwise, it serves a basic purpose of data aggregation. A synthetic entity of this kind is useful when data needs to be pulled from several sources, and there is no single persistent entity type that is associated with the synthetic entity.
In the most general case, synthetic entities do not have an id
associated with them.
This is only natural as aggregated data simply does not have a corresponding record in the database, which would have an id
.
However, there is one case where id
can be derived and be used to provide additional functionality.
Specifically, where a synthetic entity does not aggregate the data, but rather provides additional information on top of one or more persistent entity types.
Depending on the kind of a synthetic entity:
- Based on a persistent entity -
id
is interpreted as a persistent property. - One-to-one -
id
is interpreted as a calculated property with an expression that makes it equal to propertykey
(the associated persistent entity). - Otherwise -
id
is interpreted as a persistent property (this is likely to be changed in the future).
For persistent entities property id
is fetched by default every time they are read from a database.
Due to the intrinsic nature of synthetic entities fetching id
by default is not possible.
So, the platform tries to be smart where it can to dynamically identify cases where implicit fetching of id
is possible.
In other cases, the developer needs to be more explicit to ensure that id
is fetched.
Before discussing these other cases, it is important to emphasise that yielding a property in an EQL model associated with a synthetic entity does not mean that property will be fetched. The fetch model controls what is actually retrieved, while yields in a query only define what is available for fetching. The following is a simplified model of property retrieval:
isRetrieved :: Property -> Boolean
isRetrieved = isYielded AND isFetched
There are two situations where property id
is implicitly fetched for synthetic entity E
:
-
E
is based on a persistent entity. -
E
represents a one-to-one assocation.
In all other cases, id
is not implicitly fetched, hence it needs to be added to a fetch model explicitly.
There may be many different situations where id
values can be derived manually.
This subsection describes two situations where the base model for a synthetic entity effectively represents a sum type (either one or another (or another...) type).
Although, on the surface these situations are different, they boil down to the same transformation technique.
A good example for the first situation is from the TG Air domain - synthetic entity Manpower
:
@KeyType(DynamicEntityKey.class)
class Manpower extends AbstractEntity<DynamicEntityKey> {
@IsProperty
@CompositeKeyMember(1)
@Optional
private Person person;
@IsProperty
@CompositeKeyMember(2)
@Optional
private Team team;
static final EntityResultQueryModel<Manpower> model_ =
select(modelForPersonManpower(), modelForTeamManpower())
.modelAsEntity(Manpower.class);
static EntityResultQueryModel<Manpower> modelForPersonManpower() {
final var nullTeamModel = select(Team.class).where().val(1).eq().val(0).model();
return select(PersonManpower.class).
yield().prop(PersonManpower_.id()).as(Manpower_.id()).
yield().prop(PersonManpower_.person()).as(Manpower_.person()).
yield().model(nullTeamModel).as(Manpower_.team()).
modelAsEntity(Manpower.class);
}
static EntityResultQueryModel<Manpower> produceModelForTeamManpower() {
final var nullPersonModel = select(Person.class).where().val(1).eq().val(0).model();
return select(TeamManpower.class).
yield().prop(TeamManpower_.id()).as(Manpower_.id()).
yield().model(nullPersonModel).as(Manpower_.person()).
yield().prop(TeamManpower_.team()).as(Manpower_.team()).
modelAsEntity(Manpower.class);
}
}
In this case, Manpower
has a composite key with 2 members: Team
and Person
, and it is essential that both of them are optional.
Each instance of Manpower
could be based on either Team
or Person
, which are persistent and thus have id
, making it possible to derive id
for Manpower
.
However, this is a very specific case and the platform does not handle it at the moment. Hence, id
has to be fetched explicitly.
This is where a transformation trick comes in handy. Here are the steps:
- Define property
computedId: Long
as part of a synthetic entity. - Override getter
getId()
to returncomputedId
. - Yield both
id
andcomputedId
as part of the underlying EQL model.
The above recipe will ensure that such synthetic entities can be correctly represented in the Entity Centre, selected entries can be exported to Excel and even the type-specific Entity Masters can be invoked for them.
Another situation, which boils down to the one above, is where several persistent entity types with a common base type are used for defining an EQL model that underpins a synthetic entity.
A good example of this from the TG Air domain is entity EscCoc
with an EQL model, which is based on four (4) sub-models.
Each of those sub-models is based on a separate persistent entity type.
And because of that, the values for id
can be derived for each sub-model and by extension for each instance of EscCoc
.
This can be achieved by following the steps described above.
N.B. In order for id
to be included as part of synthetic entities that are displayed on their corresponding Entity Centres, the centres need to be configured to include this
.
However, if adding this
is inappropriate, then simply extending the default fetch model with id
is the best way.
For example:
// as part of Entity Centre configuration
.setFetchProvider(fetchNone(MyEntity.class).with("id"))
Per aspera ad astra
- Web UI Design and Web API
- Safe Communication and User Authentication
- Gitworkflow
- JavaScript: Testing with Maven
- Java Application Profiling
-
TG Development Guidelines
- TLS and HAProxy for development
- TG Development Checklist
- Entities and their validation
- Entity Properties
- Entity Type Enhancement
- EQL
- Tooltip How To
- All about Matchers
- All about Fetch Models
- Streaming data
- Synthetic entities
- Activatable entities
- Jasper Reports
- Opening Compound Master from another Compound Master
- Window management test plan
- Multi Time Zone Environment
- GraphQL Web API
- Guice
- Maven
- Full Text Search
- Deployment recipes
- Application Configuration
- JRebel Installation and Integration
- Compile-time mechanisms
- Work in progress