Skip to content

Context-dependent currency for Money properties #2677

@homedirectory

Description

@homedirectory

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 businessUnit is in Australia, cost should be expressed in AUD.
  • If businessUnit is in Europe, cost should 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.

  1. Both m and x should have a definer that determines the currency from x and replaces the current value of m with a currency-adjusted one.

  2. There may be values of x from which currency cannot be determined.
    In such cases, m should have a validator that rejects such values.

    Since m may be invalid, assigning x should first revalidate m and then adjust its currency.
    One simple idea is to annotate x with @Dependent(m). This will revalidate m whenever x is assigned and, if validation succeeds, the definer for m will adjust the currency.
    However, this will likely lead to non-termination.

    Details The mechanism of @Dependent does 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, @Dependent must be avoided. Instead, the definer for x should handle the case of m being invalid. If m is valid, Money.amount should 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 of currencyFrom if it returns Right.
    • For other properties, it revalidates the dependent Money-typed property and adjusts its currency based on the result of currencyFrom.
  • As a validator it is applicable only to the Money-typed property.
    If currencyFrom returns 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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions