asolfre / objectify-appengine

Automatically exported from code.google.com/p/objectify-appengine
MIT License
0 stars 0 forks source link

Support Subclass Migration of Default Discriminator #180

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?
1. Create an objectify entity, and create some data.
2. Create two subclasses and make the entity abstract.
3. Try to load some data.

Currently I have found no easy way to gradually migrate into a new hierarchical 
data structure post-release of the data structure. For example:

@Entity
public class WidgetEvent { }

// Then later:
// Make it abstract.
@Entity
public abstract class WidgetEvent { }

// What the existing data should become.
@EntitySubclass(name="we", alsoLoad=null)
public class WidgetEnabledEvent { }

// The new variant of data we want to capture.
@EntitySublcass(name="wd")
public class WidgetDisabledEvent { }

Unfortunately, the null above doesn't work because of this check in 
PolymorphicEntityMetadata#getConcrete:

String discriminator = (String)ent.getProperty(DISCRIMINATOR_PROPERTY);
if (discriminator == null)
  return this.base.metadata;

This short-cuts any particular loading of the "null" case by a sub-type. It's 
possible this could be changed to support a null key via:
if (discriminator == null && !byDiscriminator.containsKey(null))

(Note that this is not a tested patch, unfortunately; just me observing the 
code and making a guess of what might be a solution).

Nevertheless, because the discriminator is handled out-of-band with respect to 
properties, and intentionally avoids Java-friendly field names (and because you 
can't supply custom names for fields via annotation), you cannot migrate this 
in any way in your application via typical Objectify means like @OnLoad, 
@OnSave, etc. (at least none that I have found).

The only workaround I've found is to start forcing your PROD version to apply 
the discriminator via creating a subclass instance that is otherwise identical 
to the root type, and only ever referencing the root type in your application. 
You can then begin doing the conversion of entities via the DatastoreService 
asynchronously, applying the default ^d value to each of them iteratively.

1. In a new branch of your production version create a WidgetEnabledEvent 
subclass.
2. Add the @EntitySubclass(name="we") to the new sub-type.
3. Change every place you create a new instance for the DB to construct the 
subclass rather than the root class.
4. At this point, new data in the version will persist the discriminator.
5. Use DatastoreService asynchronously to begin popping the ^d into records.
6. Once the asynchronous process has successfully fixed all records missing ^d, 
you can release your full new version.

This is still not ideal, as you have to migrate large volumes of data all 
before you can release, which is manageable for a while, but not on terabytes 
of volume.

If there is another Objectify-enabled workaround I'm unaware of, I'd love 
advice - thank you much!

Original issue reported on code.google.com by rjlori...@realjenius.com on 24 Oct 2013 at 4:40

GoogleCodeExporter commented 9 years ago
This is probably a better discussion for the google group, but it sounds like 
the approach of creating a virtually identical subclass and using that to 
reprocess the data will solve the issue in a way that isn't too unpleasant.

The idea of alsoLoading null is an interesting one which feels like it could 
have dangerous side effects. But start a thread in the group and maybe there's 
something to be done about it. You'll want to work with the v5 codebase 
instead; the code is completely different and should be much easier to 
understand.

Original comment by lhori...@gmail.com on 14 Apr 2014 at 1:32