@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.Listorjava.util.Setwith a type parameter compatible with the class containing the method annotated with the@Referenceannotation.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@Referenceannotation. The inverse MUST be annotated withjavax.annotation.NonnullMultiplicity.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@Referenceannotation. 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();
}