@Reference
A reference allows one arez component instance to refer to another arez component by id. The component author
adds the @Reference
annotation to an abstract method that returns a referenced object along
with a method annotated with @ReferenceId
that provides the id of the references object.
The method annotated by @ReferenceId
is expected to return a constant value unless it is
also annotated by @Observable
. The arez runtime is responsible for generating the code to
locate the referenced object by id and will return the referenced object when the object annotated with the
@Reference
annotation is invoked.
References are primarily used:
- when the links between components are bi-directional or can form cycles.
- when the data comes from database systems that model links using foreign keys.
- when the lookup of the referenced object should be cached but the referenced object may not be available when the component is constructed.
The arez runtime looks up the referenced object using a Locator
instance that is registered with the
ArezContext
using the ArezContext.registerLocator(Locator)
method. The
Locator
instance can be used to lookup any arbitrary object and this makes it possible for arez objects
to lookup non-arez objects using a @Reference
annotated method.
The most common scenario involves the application developer creating a TypeBasedLocator
,
registering repositories in the TypeBasedLocator
and registering the
TypeBasedLocator
instance in the ArezContext
. This would allow
@Reference
annotated methods to refer to any of the arez
components that are present in the registered repositories.
Consider the scenario where the application is modelling users, their memberships in groups and the permissions
associated with a group. The reference between a Permission
and the containing Group
could be modelled by a
reference such as:
@ArezComponent
public abstract class Permission
{
...
@Reference
public abstract Group getGroup();
@ReferenceId
public int getGroupId()
{
return _groupId;
}
}
The repository for the Permission
looks like:
@ArezComponent
public abstract class PermissionRepository
extends AbstractRepository<Integer, Permission, PermissionRepository>
{
...
@Nullable
public Permission findById( final int id )
{
return findByArezId( id );
}
...
}
And the code to setup the locator to support this scenario:
final GroupRepository groupRepository = new Arez_GroupRepository();
final PermissionRepository permissionRepository = new Arez_PermissionRepository();
final UserRepository userRepository = new Arez_UserRepository();
final TypeBasedLocator locator = new TypeBasedLocator();
locator.registerLookup( Group.class, id -> groupRepository.findById( (Integer) id ) );
locator.registerLookup( Permission.class, id -> permissionRepository.findById( (Integer) id ) );
locator.registerLookup( User.class, id -> userRepository.findById( (Integer) id ) );
Arez.context().registerLocator( locator );
The point at which a reference will be resolved or looked up in the Locator
depends upon the value
of the @Reference.load
parameter. The default value is
LinkType.EAGER
which means the reference is resolved when the arez component
is constructed or eagerly when the value of the reference id is changed.
The load
parameter can also be set to LinkType.LAZY
which means that the
reference will be resolved when the reference is accessed and cached until the value returned by the method
annotated by @ReferenceId
changes.
The other value that the load
parameter can be set to is LinkType.EXPLICIT
.
This means that the references are resolved explicitly by the application. The application must invoke the method
Linkable.link()
before an attempt is made to access the reference. The
EXPLICIT
value is usually used when changes are applied in batches, across a network in non-deterministic order.
@Inverse
The @Inverse
annotation is used to create a link back from the target component to the
component the declared the method annotated with the @Reference
annotation. If a
@Reference
is paired with an @Inverse
then it must explicitly declare
that it has an inverse either by setting the inverse=ENABLED
parameter or one of the inverseName
or
inverseMultiplicity
parameters on the @Reference
annotation.
It should be noted that Arez deliberately requires that the inverse be annotated on both sides of the relationship so that during incremental compiles, the annotation processor is able to inspect and validate both sides of the relationship even if only one side has code changes. If a invalid change is made to either side then either the annotation processor or the javac compiler will detect the problem.
The multiplicity of a relationship is defined by the type returned by the method annotated by the
@Inverse
annotation and by the value of the multiplicity
parameter on the
@Reference
annotation. Possible values and their implications include:
Multiplicity.MANY
: The inverse is related to many references. The type of the inverse must be one ofjava.util.Collection
,java.util.List
orjava.util.Set
with a type parameter compatible with the class containing the method annotated with the@Reference
annotation.Multiplicity.ONE
: The inverse is related to exactly one reference. The type of the inverse must be compatible with the class containing the method annotated with the@Reference
annotation. The inverse MUST be annotated withjavax.annotation.Nonnull
Multiplicity.ZERO_OR_ONE
: The inverse is related to one or no reference. The type of the inverse must be compatible with the class containing the method annotated with the@Reference
annotation. The inverse MUST be annotated withjavax.annotation.Nullable
.
So if we were to add an inverse relationship between the Group
and Permission
class illustrated earlier
we would need to modify both sides of the relationship. The Permission
class would look like:
@ArezComponent
public abstract class Permission
{
...
@Reference( inverse = Feature.ENABLE )
public abstract Group getGroup();
@ReferenceId
public int getGroupId()
{
return _groupId;
}
}
While the Group
class would look like
@ArezComponent
public abstract class Group
{
...
@Inverse
public abstract Collection<Permission> getPermissions();
}