Hi G-Men,
Here's our use-case: our domain model does not do lazy-loading (we're using Morphia with MongoDB, but if you're familiar with Objectify on AppEngine –I'm not, I've just read the docs–, it would look very similar; except that with Morphia we can opt-in to "managed relationships" and lazy-loading, we just chose not to do so), but we need to retrieve related entities on the client-side (using RequestFactory). We cannot (or "do not want to", if you prefer) put the accessors for the related entity within the domain class as we don't have a handle on the "entity manager" (we use DI all the way, I don't want to introduce a static factory similar to ObjectifyService.begin()).
A concrete example: we have "illustrations" and "files", a "file" can contain several "illustrations" through "legends" (the "legend" has the "file" as a "parent", and "links" to the "illustration", adding some data such as a title). Each one of these: illustration, file and legend are entities.
In Objectify, I think it'll map to something like:
public class Legend {
@Id String id;
@Parent Key<File> fileKey;
Key<Illustration> illustrationKey;
}
Second example: a "contribution" is a link between a "file" and a "person", it adds the role of the contribution (was the person an author of the file? the architect of the building described by the file? etc.)
To finish drawing the whole picture, we have forms to edit legends and contributions: the ID of the legend/contribution is contained in the Place/history token, the illustration/person can only be known by retrieving the legend/contribution.
We don't want to do 2 requests (one for the legend/contribution, followed by, now that we have its ID, one for the illustration/person), but we cannot do a single one because we don't know the illustration/person's ID, and we do not have a getIllustration()/getPerson() on the Legend/Contribution domain objects.
We've solved it using a ServiceLayerDecorator that does "lazy loading" for us: when the property "illustration" is asked for a Legend, we look for the illustrationKey field, load the illustration and return it. That way, on the client-side, we just have to do a .with("illustration") to get the illustration associated with the legend.
As I said, we do not have a getIllustration() on the Legend domain class (but we do have one on the LegendProxy client type), and this makes the RequestFactoryInterfaceValidator bail.
We're working around it in two steps:
1. use @ProxyForName so the RequestFactoryGenerator compiles the client code without looking at the domain classes
2. copy the whole ResolverServiceLayer, removing anything related to RequestFactoryInterfaceGenerator; our copy then totally "overrides"/hides the original ResolverServiceLayer.
Not having our proxies validated against our domain model isn't much of an issue as we're generating one from the other.
The issue is that resolveClientType is not easy to implement, particularly for types that only go from server to client; so we have to do our own "bookmarking" in the resolveDomainMethod method.
Because this issue is likely to come in many projects using Objectify (and not only our project, using Morphia), I therefore suggest adding an annotation to selectively disable validation on methods. And by that I only mean disabling the check that a corresponding method exists in the domain class. You'd use it this way:
@ProxyFor(Legend.class)
public interface LegendProxy extends EntityProxy {
// We do have String actually, but that wouldn't be very diferent with Key<?>
String getIllustrationId();
void setIllustrationId(String id);
@SkipInterfaceValidation
IllustrationProxy getIllustration();
@SkipInterfaceValidation
void setIllustratin(IllustrationProxy illustration);
}
Here, the RequestFactoryInterfaceValidator would validate the illustrationId accessors against the Legend class, would descend into the IllustrationProxy (in case it's not referenced directly by other methods where validation hasn't been disabled), but would not check whether there are corresponding getIllustration/setIllustration methods on the Legend class.
It would apply to both getters/setters on proxies, and request methods on contexts. We could imagine putting it on the class as a shortcut to applying it on each method.
I've already started working on a patch and it's fortunately a somewhat small change (add flag to RFMethod, do not poison() the validator if the flag is present, but perform all other checks otherwise).
What do you think?