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.