Мы смоделировали отношения пользователя как простую сущность UserRelation:
class UserRelation {
User _from;
User _to;
RelationState _state;
}
Где RelationState - перечисление, описывающее состояния (обычно это больше, чем дружба)
enum RelationState {
BLOCKED, NONE, PENDING_FRIEND, FRIEND;
}
На самом деле, мы также используем это перечисление для авторизации, например, в профилях пользователей.
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();
}
Проще всего иметь два объекта для каждого (симметричного) отношения (например, дружба, как используется в Facebook) и только один объект для несимметричных отношений (например, подписчики, используемые в твиттере или заблокированные пользователи). Хотя это может выглядеть как накладные расходы, использование двух объектов, безусловно, упрощает запросы.
Я думаю, что сама часть AppEngine должна быть довольно простой.