[Neo] Fwd: Performance question

Atle Prange atle.prange at gmail.com
Wed Apr 28 13:14:20 CEST 2010


Hi,

i have now committed the first early version of the mapper. It uses proxies
to sync the entity with the underlying node or relationship.

Use the tests to see it in action.

I am not sure if you think this projects adds any value since jo4neo and
neo-persistence already exists, but you can see for yourself.

-atle

On Mon, Apr 26, 2010 at 2:36 PM, Atle Prange <atle.prange at gmail.com> wrote:

> Interesting!
>
>
> I have created a project ogrm.org (Terrible name, but its a name
> nonetheless ;) to persuse the different angles here.
>
> My second pass at the problem yielded a more annotation heavy Proxy
> approach:
>
> public class PersonImpl implements Person {
>
> @Id
> private Object id;
>
> @Value
> private String name;
>
> @Value(converter = DateTimeConverter.class)
>  private DateTime birthDate;
>
> @ToOne
> private Person father;
>
> @ToMany
> private Set<Person> family;
>
> @ToMany
>  private TypedRelationBag<Friendship> tempFrends;
>
> @Override
>  public void addFamily( Person member ) {
> family.add( member );
>
>  }
>
> @Override
> public Friendship createFriendshipTo( Person person ) {
>  return tempFrends.createAndAddRelationTo( person, Friendship.class );
> }
>
>  @Override
> @Reflector( { "father" })
> public Person getFather() {
>  return father;
> }
>
> @Override
>  public Set<Person> getFamily() {
> return family;
> }
>
> @Override
> @Reflector( { "name" })
> public String getName() {
>  return name;
> }
>
> @Override
>  public Collection<Friendship> getTempFriends() {
> return tempFrends;
>  }
>
> @Override
> public void removeFriendship( Friendship friendship ) {
>  tempFrends.remove( friendship );
>
> }
>
> @Override
>  @Mutator( { "father" })
> public void setFather( Person father ) {
>  this.father = father;
> }
>
> @Override
>  @Mutator( { "name" })
> public void setName( String name ) {
> this.name = name;
>  }
>
> }
>
>
> public class Client{
>
>  public void doSomething(){
>
> Person person = getEntityManager().create(PersonImpl.class);
>
>
>  //This call is intercepted by the proxy, and the node is updated.
>  person.setName("Atle");
>
>  //The TypedRelationBag in Person creates a new friendship
> and automatically stores all data to the underlying relationship.
>  person.createFriendshipTo(getFriend()).setInterval(new Interval(now,
> now.plus(week));
>
> Person father = getEntityManager().get(Person.class,2);
>
>  //Also intercepted, the proxy fetches data from the node and sets it in
> the entity, before the entity returns the name
>  String name = father.getName();
>
>  }
>
>
> }
>
>
> The entitymamanger wraps implementation classes in proxies, which checks if
> the any call should store a value to the underlying node, or should retrieve
> data from the node. This way the node is kept fresh, and all getters get
> data directly from the node.
>
> Collections are implemented using special collection classes, that maps
> entities to the nodes directly. These collection object are injected by the
> framework at when the entity is loaded.
>
>
> The only dependency the domain has to the framework are the annotations.
>
> Typed Relations are also possible, by using the TypedRelationBag and
> TypedRelation interfaces. They expose factory methods for the relation, and
> are also injected by the framework.
>
> A side effect from using wrapping instead of the load/store is that changes
> in relationships are effective instantly in the same thread. If i use
> Person.setFather(Person), and i have  a collection of sons in another person
> object, that son would be available to that set immediately. No more
> forgetting to add the child to the parent, or the other way around.
>
> This framework is really annotation heavy, but i can live with that since i
> gain so much in return. The next step would be to use gclib proxies instead,
> they are easier to debug that java.lang.proxy.Proxy.
>
> I will upload the code to svn tonight, and i will keep posting to this list
> as long as you don't tell my to shut up ;)
>
> -atle
>
>
>
>
> On Mon, Apr 26, 2010 at 1:47 PM, Tobias Ivarsson <
> tobias.ivarsson at neotechnology.com> wrote:
>
>> I agree that having the domain classes extend NodeWrapper is coupling the
>> domain too tightly to the implementation.
>> You would want your domain object definitions to be interfaces then have
>> the
>> implementation extend NodeWrapper:
>>
>> public interface Person {
>>    public void setName( String name );
>> }
>>
>> class PersonImpl extends NodeWrapper implements Person {
>>     private Value<String> name = Values.property(this,String.class);
>>
>>    public void setName(String newName) {
>>        name.set(newName);
>>    }
>> }
>>
>> What I think would be interesting is to have a framework that could
>> generate
>> the PersonImpl class. For simple cases this could be done purely
>> dynamically, and use a dynamic proxy [1] to do the bridging of interface
>> to
>> implementation. The Person interface above is certainly simple enough to
>> be
>> a candidate for this method. I'm envisioning an API like this:
>>
>> DomainFactory<Person,Node> factory =
>> DomainFactory.createNodeObject(Person.class);
>> Person myGuy = factory.wrap(guyNode);
>>
>> Other domain objects will need more complicated mappings from the API to
>> the
>> properties or relationships they store, a lot of these could be defined
>> using annotations. It would certainly be possible to add these annotations
>> to the domain interface, but I really think they belong in a separate
>> implementation class. I even think that "implementation class" could be an
>> interface that extends the domain interface. For example this more
>> advanced
>> Person object:
>>
>> public interface Person {
>>    void setName(String firstName, String lastName);
>>    String getName();
>> }
>> interface PersonNodeMapping extends Person {
>>    @Override void setName(@Property("first name") String firstName,
>> @Property("last name") lastName);
>>    @Override @Property(pattern="{first name} {last name}") String
>> getName();
>> }
>>
>> Then using a similar API to creating the domain objects, but this time
>> additionally specifying the implementing class(es):
>>
>> DomainFactory<Person,Node> factory =
>> DomainFactory.createNodeObject(Person.class, PersonNodeMapping.class);
>> Person myGuy = factory.wrap(guyNode);
>>
>> Finally we need to realize that sometimes there are domain objects where
>> the
>> mapping is most easily defined through code. This is the first case where
>> we
>> cannot use dynamic proxies[1], since they only work with interfaces, and
>> interfaces cannot contain method bodies. Instead this would have to use
>> some
>> bytecode manipulation framework[2]. The createNodeObject method would be
>> as
>> above, but we would now instead define our domain object and the mapping
>> like this:
>>
>> public interface Person {
>>    void setName(String firstName, String lastName);
>>    String getName();
>> }
>> abstract class PersonNodeMapping extends NodeWrapper implements Person {
>>    PersonNodeMapping(Node node) { super(node); }
>>    public void setName(String firstName, lastName) {
>>        System.out.println("hi " + firstName + "!"); // quite silly
>> example,
>> but still...
>>        this.node.setProperty("first name", firstName);
>>        this.node.setProperty("last name", lastName);
>>    }
>>    @Override @Property(pattern="{first name} {last name}") abstract String
>> getName();
>> }
>>
>> That's how I'd like to see it. Another idea is to look at Qi4j, it does
>> these things, and A LOT MORE: http://www.qi4j.org/
>> And of course, if you think that a load/store solution would yield
>> something
>> interesting, I encourage you to go forth on that path.
>>
>> [1]
>>
>> http://java.sun.com/javase/6/docs/api/index.html?java/lang/reflect/Proxy.html
>> [2] ASM is a good framework for manipulating java byte code:
>> http://asm.ow2.org/
>>
>> On Wed, Apr 21, 2010 at 7:21 PM, Atle Prange <atle.prange at gmail.com>
>> wrote:
>>
>> > The first draft i have implemented is the wrapper approach:
>> >
>> > Entities must subclass NodeWrapper, and typed relations
>> > RelationshipWrapper.
>> > These superclasses can get a context set on them, which contains the
>> graph
>> > element they wrap.
>> >
>> > I added some helper classes that makes the setting and getting of
>> > properties
>> > and relations type safe and less error-prone, but i no longer have my
>> > normal
>> > fieldsin my class:
>> >
>> > class Person extends NodeWrapper{
>> >
>> >    private Value<String> name = Values.property(this,String.class);
>> >
>> >    public void setName(String newName){
>> >        name.set(newName);
>> >    }
>> >
>> > }
>> >
>> >
>> > I have the same thing with relations; special classes that entites can
>> use
>> > to express the relations.
>> >
>> > I don't like it though, it doesn't "feel right". It makes my entity very
>> > connected to the persistence implementation, i will have to investigate
>> > further, and simplify stuff a bit.
>> >
>> > A very interesting sideeffect of using Neo4j as a back-end is its
>> > capability
>> > as a RelationshipManager [1]. This is nothing new to you graph-database
>> > guys, but in standard OO, this idea is worth digging into.
>> >
>> > If i don't want to pursue the subclass pattern, i have three options
>> left:
>> >
>> > 1) load/store, which already has been implemented (almost twice).
>> >
>> > 2) Bytecode manipulation (runtime weaving is hard in felix, therefore i
>> am
>> > stuck with build-time weaving, like Apache Cayenne)
>> >
>> > 3) Proxying. With this option i have to tell some handler what to store
>> > back
>> > to the node when an method is called.
>> > It could look like this:
>> >
>> > class Person{
>> >
>> >    private String name;
>> >
>> >    @Converter(InstantConverter.class)
>> >    private Instant instantOfBirth;
>> >
>> >    @Sideffect({"name"})
>> >    public void setName(String newName){
>> >        this.name = newName;
>> >    }
>> >
>> >    @Sideffect({"name","instantOfBirth"})
>> >    public void setNameAndInstantOfBirth(String newName,Instant
>> > instantOfBirth){
>> >        this.name = newName;
>> >        this.instantOfBirth = instantOfBirth;
>> >    }
>> >
>> >
>> > }
>> >
>> >
>> >
>> > Sorry if i am boring you guys, but i think neo4j is the way to go on
>> > persistence...
>> >
>>
>> Not bored at all. And I apologize for not responding to this sooner, I've
>> been traveling.
>>
>> Cheers,
>> --
>> Tobias Ivarsson <tobias.ivarsson at neotechnology.com>
>> Hacker, Neo Technology
>> www.neotechnology.com
>> Cellphone: +46 706 534857
>> _______________________________________________
>> Neo mailing list
>> User at lists.neo4j.org
>> https://lists.neo4j.org/mailman/listinfo/user
>>
>
>


More information about the User mailing list