-
Notifications
You must be signed in to change notification settings - Fork 6
Description
Description
In some modelling scenarios, the currency of a Money-typed property may depend on other properties of the same entity.
For example, entity PurchaseOrder, given below, models purchase orders for several business units that may be located in different countries.
Its property cost has currency that depends on businessUnit.
The business rules could be:
- If
businessUnitis in Australia,costshould be expressed in AUD. - If
businessUnitis in Europe,costshould be expressed in EUR.
class PurchaseOrder {
@IsProperty
String businessUnit;
@IsProperty
// Money: [amount, currency]
@PersistentType(userType = IMoneyType.class)
Money cost;
}The support for context-dependent currency should respect the direct correspondence principle: Money-typed properties should attain context-dependent currency regardless of the origin of assignment, be it via some business logic or as a result of user interaction.
It is proposed to use an existing mechanism: validators and definers.
Given property m: Money whose currency depends on property x, it is required to keep them synchronised.
When the value of either property changes, m should be recomputed with a newly determined currency.
-
Both
mandxshould have a definer that determines the currency fromxand replaces the current value ofmwith a currency-adjusted one. -
There may be values of
xfrom which currency cannot be determined.
In such cases,mshould have a validator that rejects such values.Since
mmay be invalid, assigningxshould first revalidatemand then adjust its currency.
One simple idea is to annotatexwith@Dependent(m). This will revalidatemwheneverxis assigned and, if validation succeeds, the definer formwill adjust the currency.
However, this will likely lead to non-termination.Details
The mechanism of@Dependentdoes not play well with self-reassigning definers. When a dependent property is revalidated, its last attempted value is assigned with "enforcement", which will force unconditional execution of validators and definers for that property. Once such a property has been succesfully revalidated, its self-reassigning definer will be executed. That definer will assign a currency-adjusted value, still in the "enforcement" mode! This will cause it to be executed again and again despite the value not changing.The takeaway is that execution of self-reassigning definers in "enforcement" mode is likely to cause non-termination.
Therefore,
@Dependentmust be avoided. Instead, the definer forxshould handle the case ofmbeing invalid. Ifmis valid,Money.amountshould come from its current value, otherwise -- from its last invalid value.
So far, only one-to-one dependencies were discussed, but it is entirely possible for a Money-typed property to depend on two or more other properties. The rules above can be easily generalised to the one-to-many case: each property from the "many" side will have its own definer.
The platform should provide the "core" for such definers so that they can be concisely defined in TG-based applications.
Change overview
Class AbstractDependentMoneyCurrencyHandler was introduced.
This is a base class for ACE and BCE handlers (definers and validators) that supports modelling of Money-typed properties whose currency depends on other properties of the same entity.
To illustrate how to use this base class, consider the following example:
class PurchaseOrder {
@IsProperty
@Dependent("cost")
@AfterChange(GenericPurchaseOrderCostCurrencyHandler.class)
String location;
@IsProperty
@Dependent("cost")
@AfterChange(GenericPurchaseOrderCostCurrencyHandler.class)
boolean international;
@IsProperty
@PersistentType(value = IMoneyType.class) // [amount, currency]
@BeforeChange(@Handler(GenericPurchaseOrderCostCurrencyHandler.class))
@AfterChange(GenericPurchaseOrderCostCurrencyHandler.class)
Money cost;
}
class GenericPurchaseOrderCostCurrencyHandler<T>
extends AbstractDependentMoneyCurrencyHandler<PurchaseOrder, T>
{
protected GenericPurchaseOrderCostCurrencyHandler() {
super("cost");
}
protected Either<String, Currency> currencyFrom(PurchaseOrder po) { ... }
}In this example, cost.currency depends on location and international.
Class GenericPurchaseOrderCostCurrencyHandler implements determination of context-dependent currency for property cost.
It calls the super constructor which requires the name of the dependent Money-typed property.
It implements method currencyFrom which determines the currency for property cost of a given purchase order.
Class GenericPurchaseOrderCostCurrencyHandler can be used as both a validator and a definer, hence generic.
- As a definer it is applicable to both the
Money-typed property and the properties that determine the currency.- For the dependent
Money-typed property, it simply replaces the currency with the result ofcurrencyFromif it returnsRight. - For other properties, it revalidates the dependent
Money-typed property and adjusts its currency based on the result ofcurrencyFrom.
- For the dependent
- As a validator it is applicable only to the
Money-typed property.
IfcurrencyFromreturns a left value, validation fails with that value as the message; otherwise, validation succeeds.
It is possible to subclass GenericPurchaseOrderCostCurrencyHandler to specialise it for a particular property, either as a definer or a validator.
The subclass must then specify the property type for type parameter <T>.
In addition to declaring definers and validators, it is necessary to annotate properties that determine the currency with @Dependent(x), where x is the dependent Money-typed property.
Expected outcome
Ability to define the currency of a Money-typed property based on other properties of the same entity.