views:

132

answers:

2

Very simple scenario

N users, each user can have 0 .. N - 1 friends (who are also users)

How can I model this in Java for AppEngine data store

Scenario to consider

  • user x and user y become friends (so both need update their own status, in a transaction
+2  A: 

We've modeled user relations as a simple UserRelation entity:

class UserRelation {
  User _from;
  User _to;
  RelationState _state;
}

Where RelationState is an enum, describing states (normally, there is more than friendship)

enum RelationState {
  BLOCKED, NONE, PENDING_FRIEND, FRIEND;
}

Actually, we also use this enum for authorizaton, e.g. on user profiles.

enum RelationState implements IRole {
  BLOCKED, NONE(BLOCKED), PENDING_FRIEND(NONE), FRIEND(PENDING_FRIEND);

  private final List<IRole> _impliedRoles;
  private final List<String> _roleStrings;

  private RelationState(final IRole... impliedRoles) {
    HashSet<IRole> set = new HashSet<IRole>();
    for (final IRole impliedRole : impliedRoles) {
      set.add(impliedRole);
      set.addAll(impliedRole.getImpliedRoles());
    }
    _impliedRoles = Collections.unmodifiableList(new ArrayList<IRole>(set));

    ArrayList<String> list = new ArrayList<String>(getImpliedRoles().size() + 1);
    list.add(getName());
    for (final IRole implied : getImpliedRoles()) {
      list.add(implied.getName());
    }
    _roleStrings = Collections.unmodifiableList(list);
  }

  public List<IRole> getImpliedRoles() {
    return _impliedRoles;
  }

  public String getName() {
    return name();
  }

  public boolean hasRole(final IRole role) {
    return this == role || _impliedRoles.contains(role);
  }

  public List<String> getRoleStrings() {
    return _roleStrings;
  }
}

public interface IRole {
  public List<? extends IRole> getImpliedRoles();
  public String getName();
  public boolean hasRole(final IRole role);
  public List<String> getRoleStrings();
}

It's easiest to have two objects for each (symmetric) relationship (e.g. friendship as used on facebook) and only a single object for non-symmetric relationships (e.g. followers as used on twitter or blocked users). While this might look like overhead in the first place, using two objects certainly simplifies querying.

I think the AppEngine part itself should then be pretty straight forward.

sfussenegger
This is a great idea and gives me much more flexibility.My original thought was something align to the following class User { Long id; Set<Long> friends;}for which I had hard time to figure out the relationships (self-referential) and how to represent them, and also worry about the transactional cost.I still need to consider using transaction when doing updates, but it's more clear and tangible.
tw
A: 

Consider using a Friendship table with just two foreign keys, user1 and user2. An entry in this table models an social connection between two users. You could even add more columns to describe the type of this social relation.

(or consider sfussenegger's answer ;) same idea but better presentation)

Andreas_D
thanks to both of you - very helpful answers.
tw