John Santos
John Santos

Reputation: 525

Objectify POST/LIKE/USER Relationship

I'm modelling a google datastore with objectify and part of my datastore has these 3 elements:

User post new Post which can be liked by many Users

User post a new Post which can be liked by many Users (typical social network).

So far I have User:

@Entity
public class UserMW {

    @Id
    private Long id;

    @Index
    private String email;

    ...
}

Post:

@Entity
public class PostMW{

    @Id
    private Long id;

    @Load 
    private Ref<UserMW> owner;

    ...
}

And Like:

@Entity
public class LikeMW {

    @Id
    private Long id;

    @Load
    private Ref<UserMW> user;

    private Key likedObject;

    ...
}

It works perfectly and meets all my needs till now. The problem is now I don't know for which way I should go to unlike a post (delete an entity from kind Likes).

I have the User and the likedObject Key to delete it so if it was in a relational database would be very simple (just a delete with a "where" by userID and likedObjectID) but on Objectify...

I could think about 2 ways:

1 - @index on both attributes of Likes entity then query it and delete (but @Index is so expensive and table Likes is gonna be giant!! Doesn't sound as a good idea)

2 - @Parent on user attribute of Likes entity and @Index on likedObject then query by ancestor then filter by likedObject Key and delete (but if I use @Parent, I understood all the time I load 1 user I'll load ALL HIS LIKES and as I said, table Likes is gonna be giant!! Doesn't sound as a good idea either)

Any suggestion to resolve my problem?

Thank you guys!

Upvotes: 1

Views: 136

Answers (2)

stickfigure
stickfigure

Reputation: 13556

Yes, you can simply index the user and likedObject fields and run queries like a relational database. However, this has two disadvantages:

  • The queries are eventually consistent
  • You aren't taking advantage of memcache

Strong consistency is desirable so that if a user likes/unlikes something and reloads the page, they're guaranteed to see the effect immediately.

Here's what I would do:

@Entity
@Cache
public class Like {
    @Parent
    private Ref<User> user;

    @Id
    private String likedObjectKey;

    public <T> Key<T> getLiked() { return Key.create(likedObjectKey); }
}

Use the toWebSafeString() key of the thing being liked as the string id of the Like entity. You can add some syntactic sugar to hide the stringification/destringification.

This means that fetching Likes for {user, thing} tuples is always a get-by-key operation. You can batch-fetch them and fetches will take advantage of memcache (both positive and negative hits will be cached). The result will always be strongly consistent. You can fetch and modify these in transactions easily.

Note this doesn't let you ask the question "who liked this thing?" You may also want to store the liked object key as a normal indexed field in the Like. I would strongly recommend it even if you don't immediately plan to use it; you will eventually want to run this query even if just for debugging. Indexes don't cost quite as much as everyone seems to fear.

Upvotes: 1

Mihail Russu
Mihail Russu

Reputation: 2536

I would go with adding another index to the Like entity as that is the simplest working solution, especially considering datastore's newish pricing which also makes it a bit cheaper:

...writing a single entity only costs 1 write regardless of indexes and will now cost $0.18 per 100,000. This means writes are more affordable for people using multiple indexes. You can use as many indexes as your application needs without increases in write costs.

Note that indexes still take storage and likely affect performance but you can be a bit more liberal when using them...

Upvotes: 2

Related Questions