【DDD】业务建模实践 —— 人关注人
社区业务领域中,存在‘人关注人’的场景,在这个场景中,关系较为复杂,且均表现在‘人’同一个业务实体上,因此,这个case的建模过程值得思考。本文将就‘人关注人’这个业务case的领域建模进行探讨,欢迎拍砖。
Round-I
在做‘帖子’模块的建模过程中,遇到了‘查询帖子阅读者是否关注了帖子作者’的case,基于这个case,尝试对‘关注’这个业务领域进行建模。
业务建模
就‘人关注人’来讲,可以将人按照角色分为:关注者(FollowingUser,使用进行时标识行为主动发起者)、被关注者(FollowedUser,使用过去式标识行为被动接受者),FollowingUser可以‘关注’(follows)被关注者,FollowedUser可以‘被关注’(followed);FollowingUser持有一个‘被关注者‘集合(followedUsers),FollowedUser持有一个’关注者‘集合(followingUsers)。因此可以梳理出如下实体:FollowingUser、FollowedUser,且他们都应当是‘用户’(UserInfo)实体的子类。
常见的需求中,人与人之间的关系可能有如下几种:单粉(FollowingUser follows FollowedUser)、互粉(FollowingUser follows FollowedUser, FollowedUser follows FollowingUser )、没有关系(不存在关注关系),通常需要判定制定的User和另外一个User之间的关注关系,所以,我们期望FollowingUser 能够判定自己是否关注过给定的User, 这个行为我们把他称为:hasFollowed(UserInfo);对应的,在FollowedUser 也需要判定自己是否被某个User关注,这个行为我们表示为:hasBeenFollowed(UserInfo)。
我们来考虑一个比较特殊的场景:帖子详情查询场景,需要判定‘帖子当前阅读者是否关注了帖子作者’。在这个场景中,我们尝试把‘帖子阅读者’(PostReader)当做一个FollowingUser ,PostReader继承FollowingUser ;将‘帖子作者’(PostAuthor)当着一个FollowedUser ,PostAuthor继承FollowedUser 。
为了完成判定PostReader和PostAuthor的关注关系,我们将PostAuthor作为一个User传入FollowingUser的hasFollowed(UserInfo)中,但是会发现无法识别出互粉的情况,因为,在这个场景中,我们并不认为PostAuthor是一个FollowingUser,它并不持有’被关注者‘集合(followedUsers),所以无法判定出PostAuthor是否关注过PostReader。
那么我们是不是可以为PostAuthor加上FollowingUser这样一个角色呢? 重新梳理一遍,发现其实PostAuthor和PostReader也是我们给UserInfo的一对角色,一个用户在’帖子详情查询‘场景,不可能同时拥有PostAuthor和FollowingUser的角色。PostAuthor并不需要知道自己关注了那些人,因此为PostAuthor加上FollowingUser的角色并不合适。
那么是不是可以撇开PostAuthor角色,再单独引入一个FollowingUser呢?按照这个思路,每个FollowingUser都可以将对方作为判定对象使用自己的hasFollowed(UserInfo)完成判定,这样我们为FollowingUser增加了一个行为:判定一个关注者和自己的关系,这个行为我们记为:getFollowRelation(FollowingUser)。
先不论合理性,先尝试去实现之后再做评估。
业务模型
示例代码
public class UserInfo { // 用户ID private long userId; /** * 判定给定用户是否是自己 * @param UserInfo 给定的用户 * @return true —— 是本人 * false —— 不是本人 */ public boolean isMyself(UserInfo userInfo) { if(userInfo == null) { return false; } if(userInfo.getUserId() == this.getUserId()) { return true; } return false; } ...... }
public class FollowingUser extends UserInfo { /** * 我关注的用户 集 */ private Set<FollowedUser> followedUsers = new HashSet<FollowedUser>(); public FollowingUser(long userId) { super(userId); } /** * 关注者 追随 被关注者 * 如果此人已经关注过指定的用户,则不再重复关注 * @param followedUserId 被关注者userId */ public void follow(long followedUserId) { FollowedUser followedUser = new FollowedUser(followedUserId); this.follow(followedUser); } public void follow(FollowedUser followedUser) { if(!this.followedUsers.contains(followedUser)) { followedUser.followed(this); this.followedUsers.add(followedUser); } } /** * 检查本人是否关注过指定的User * @param userInfo 指定的用户 * @return String * 1:自己 * 2:单粉 * 4:未关注 */ public String hasFollowed(UserInfo userInfo) { String followState = FollowRelationConst.FOLLOW_SIGN_4; if(this.isMyself(userInfo)) { followState = FollowRelationConst.FOLLOW_SIGN_1; } else { if(this.followedUsers.contains(userInfo)) { followState = FollowRelationConst.FOLLOW_SIGN_2; } else { followState = FollowRelationConst.FOLLOW_SIGN_4; } } return followState; } /** * 检查本人与指定的FollowingUser的关注关系,和hasFollowed不一样的地方在于,该方法可以识别出‘互粉’ * @param userInfo 指定的用户 * @return String * 1:自己 * 2:单粉 * 3:互粉 * 4:未关注 */ public String getFollowRelation(FollowingUser followingUser) { String followState = FollowRelationConst.FOLLOW_SIGN_4; if(this.isMyself(followingUser)) { followState = FollowRelationConst.FOLLOW_SIGN_1; } else { if(this.followedUsers.contains(followingUser)) { followState = FollowRelationConst.FOLLOW_SIGN_2; if(FollowRelationConst.FOLLOW_SIGN_2.equals(followingUser.hasFollowed(this))) { followState = FollowRelationConst.FOLLOW_SIGN_3; } } else { followState = FollowRelationConst.FOLLOW_SIGN_4; } } return followState; } //省略 getter/ setter方法 }
public class PostReader extends FollowingUser { ...... }
public class PostAuthor extends FollowedUser { ...... }
编写应用服务层代码,尝试判定PostReader和PostAuthor之间的关注关系,这是否发现现有的模型无法支持,我们需要新建一个临时的FollowingUser传递给PostReader.getFollowRelation()方法,这里看起来非常别扭,一个关注者(postReader这时是一个FollowingUser)怎么会去和另外一个关注者判定相互之间的关注关系呢?不符合业务场景;我们理不清FollowingUser和PostAuthor之间有什么区别,实际上,他们是标识同一个人,但是却被两个实体所表征,这会造成混乱。
public BaseOutBean queryPostDetails(BaseInBean<QueryPostDetailsInBean> baseInBean) throws Exception { ...... postReader.follow(followRepository.queryFollowedUser(post.getPostAuthorUserId(), postReader.getUserId())); FollowingUser followingUser = followRepository.queryFollowingUser(post.getPostAuthorUserId(), postReader.getUserId()); //临时的followingUser让人困惑 String followSign = postReader.getFollowRelation(followingUser); //一个关注者判定自己和一个关注者之间的关注关系,这个在业务上讲不清楚的,很是别扭。 ...... }
Round-II
业务建模
鉴于第一次建模尝试中遇到的困扰,分析下来发现:PostAuthor这个人在‘互粉’的场景下持有了双重角色:FollowingUser和FollowedUser,因此导致模型的实现并不符合业务上的理解,哪个诡异的followingUser和postAuthor本身的关系让人不能一下子识别出来。
既然PostAuthor在‘互粉’场景下即是FollowedUser又是FollowingUser,而FollowedUser和FollowingUser都是UserInfo,也就意味者UserInfo是可以将FollowedUser和FollowingUser的行为包含进去的,因此,我们退一步,在‘人关注人’的场景下,不去区分FollowedUser和FollowingUser,统一称之为UserInfo,并将之前的行为全部赋予UserInfo,这样得到的模型和业务场景完全一致,易于理解。
因此,FollowedUser就没有存在的必要了,那么FollowingUser还要不要呢? 我们先保留,因为它在后面的‘人关注话题’场景中会有用武之地。
业务模型
代码示例
public class UserInfo { // 用户ID private long userId; /** * 判定给定用户是否是自己 * @param UserInfo 给定的用户 * @return true —— 是本人 * false —— 不是本人 */ public boolean isMyself(UserInfo userInfo) { if(userInfo == null) { return false; } if(userInfo.getUserId() == this.getUserId()) { return true; } return false; } /** * 被关注者 被 关注者 追随 * @param followingUserId 关注者userId */ public void followed(long followingUserId) { UserInfo followingUser = new UserInfo(followingUserId); this.followed(followingUser); //NOTE:这里不再调用 followingUser.follow(followedUserId)。避免循环依赖。 } /** * 被关注者 被 关注者 追随 * 如果已经指定的FollowingUser, 则不必再关注 * @param FollowingUser 关注者 */ public void followed(UserInfo followingUser) { if(!followingUsers.contains(followingUser)) { this.followingUsers.add(followingUser); }; } /** * 关注者 追随 被关注者 * 如果此人已经关注过指定的用户,则不再重复关注 * @param followedUserId 被关注者userId */ public void follow(long followedUserId) { UserInfo followedUser = new UserInfo(followedUserId); this.follow(followedUser); } public void follow(UserInfo followedUser) { if(!this.followedUsers.contains(followedUser)) { followedUser.followed(this); this.followedUsers.add(followedUser); } } /** * 检查本人是否关注过指定的User * @param userInfo 指定的用户 * @return boolean * true —— 已经关注了指定的user * false —— 还未关注指定的user,如果指定用户是自己,则也返回false */ public boolean hasFollowed(UserInfo userInfo) { if(this.isMyself(userInfo)) { return false; } else { if(this.followedUsers.contains(userInfo)) { return true; } } return false; } /** * 检查本人与指定的FollowingUser的关注关系,和hasFollowed不一样的地方在于,该方法可以识别出‘互粉’ * @param userInfo 指定的用户 * @return String * 1:自己 * 2:单粉 * 3:互粉 * 4:未关注 */ public String getFollowRelation(UserInfo followingUser) { String followState = FollowRelationConst.FOLLOW_SIGN_4; if(this.isMyself(followingUser)) { followState = FollowRelationConst.FOLLOW_SIGN_1; } else { if(this.followedUsers.contains(followingUser)) { followState = FollowRelationConst.FOLLOW_SIGN_2; //NOTE:这里不能调用followingUser.getFollowRelation(this),否则进入死循环 if(followingUser.hasFollowed(this)) { followState = FollowRelationConst.FOLLOW_SIGN_3; } } else { followState = FollowRelationConst.FOLLOW_SIGN_4; } } return followState; } ...... }
/** * 粉丝,关注者,具有‘关注者’角色的用户 * ‘关注者’可以关注话题等,人关注人的逻辑放到了UserInfo中处理。 * @author LENGFUPING610 * @CreateDate 2017年9月6日 * */ public class FollowingUser extends UserInfo { public FollowingUser(long userId) { super(userId); } }
/** * @author LENGFUPING610 * @CreateDate 2017年8月29日 * 帖子读者 * 读者通常也是追随者,通常会去关注作者,或者关注话题 */ public class PostReader extends FollowingUser { ...... }
Round-III
业务建模
上述两次建模过程没有考虑‘关注’场景的复杂业务规则,现在我们重头梳理下‘关注’场景的业务规则。从需求上看,需要满足如下业务规则:
-
- 一个人不能关注自己
- 不能重复关注同一个人
为了第一条业务规则,我们模型中的follow行为需要调用UserInfo的isMyself判定是否本人,如果是本人则抛出异常。
对于第二条业务规则,为了判定出FollowingUser是否已经关注过FollowedUser,理论上我们需要将FollowingUser关注过的FollowedUser都从存储中查询出来,装入到followedUsers,但是如果一个人关注了成千上万个,那么这种做法在性能上是不可取的。退一步我们可以只查询这次判定的两个人之间的关系,这样将结果集限定在1或者o个。同时我们需要在follow(UserInfo followedUser)方法中将此次关注的FollowedUser返回给调用方,这样调用方判定返回值是否为空,从而决定是否做存储操作。FollowedUser的followed行为和FollowingUser.follow()行为类似,不再赘述。
业务模型
同‘Round-II’中的业务模型
示例代码
public class UserInfo { ...... /** * 关注者 追随 被关注者 * 如果此人已经关注过指定的用户,则不再重复关注 * @param followedUser * @return followedUser 被关注者 * @throws BusinessException */ public UserInfo follow(UserInfo followedUser) throws BusinessException{ if(followedUser == null) { return null; } if(this.isMyself(followedUser)) { throw new BusinessException(MessageConst.CODE_1008); } if(!this.followedUsers.contains(followedUser)) { followedUser.followed(this); this.followedUsers.add(followedUser); return followedUser; } return null; } /** * 被关注者 被 关注者 追随 * 如果已经指定的FollowingUser, 则不必再关注 * @param FollowingUser 关注者 * @param FollowingUser 关注者 * @throws BusinessException */ public UserInfo followed(UserInfo followingUser) throws BusinessException { if(followingUser == null) { return null; } if(this.isMyself(followingUser)) { throw new BusinessException(MessageConst.CODE_1008); } if(!followingUsers.contains(followingUser)) { this.followingUsers.add(followingUser); return followingUser; }; return null; } ...... }
Round-IV
经过上述三次建模迭代,我们得到了较完善的业务模型,但是不能沾沾自喜,当后续开发进入到‘关注’业务领域中的‘人关注人’和‘取消关注’的场景下时,发现上面的模型捉襟见肘了。
业务建模
考虑‘人关注人’的case,我们需要将模型存入到存储介质中,这里的存储介质使用的oracle,在数据模型中,‘人关注人’的关注场景需要包含如下信息项:关注者用户id(following_user_id)、被关注者用户id(followed_user_id)以及可能的其他信息项。
现有业务模型中,在“关注”场景下,following_user_id是followingUser的userId,对于followed_user_id也能从followingUser.follow(UserInfo followedUser)的返回结果中获取(见上节描述)。那么再考虑一个更深层次的业务需求:“取消关注之后再次关注”,这里涉及到‘取消关注’这个场景的数据建模,对于‘取消关注’可以有两种做法:
-
- 取消关注即将该条关注关系硬删除;
- 取消关注不做硬删除,只是给改天关注关系打上删除标记,但是记录还被保留;
对于与上述两种‘取消关注’,“取消关注之后再次关注”可以有如下几种做法:
-
- 方案一、硬删除的情况下,再次关注直接插入一条新的关注关系;
- 方案二、软删除的情况下,插入一条新的关注关系,同时保留旧的被标记为‘软删除’的关注关系;
- 方案三、软删除的情况下,修改旧的被标记为‘软删除’的关注关系为正常的关注关系;
现在我们来评估下三种方案的利弊:
-
- 方案1、优点在于:简洁明了,且符合业务模型,在业务模型中我们可以将‘关注关系’作为一个值对象建模;缺点在于:历史的关注关系会丢失,因为做了硬删除。
- 方案2、优点在于业务模型层处理简单;缺点在于:数据模型需要考虑软删除标记的影响,比如在删除标记上建立索引时需要做过滤;
- 方案3、优点在于数据模型层较简单;缺点在于:业务模型需要区分出“第一次关注”和‘取消关注后再次关注’两种场景,同时数据模型丢失了关注的历史信息。
我们再回过来看下,方案1、3丢失掉的关注的历史信息在方案2中被记录到了“关注关系”数据模型中,那么方案2合理吗? 其实不合理的,一个数据模型承担了两种角色:”关注关系“和”关注历史“。所以我们可以将”关注关系“和”关注历史“分开进行数据建模,那么方案1的缺点就没有了。最终我们得到的最优数据模型为:
“关注关系” —— t_follow_relation(following_user_id, followed_user_id, create_time, last_update_time)
“关注历史” —— t_follow_history(following_user_id, followed_user_id, action, create_time, last_update_time)
回过头来我们再来看看业务模型,上面的业务模型,并没有将“关注关系”单独建模,而是表征在了UserInfo的两个set集合中;实际上,仅仅是表征‘人关注人’的话,业务模型是可以契合上面的最优数据模型的。再往前想一步假如“关注关系”中含有了其他属性呢?比如:关注渠道等,这时候就没法使用UserInfo的两个set属性来表征了。所以我们决定还是对
“关注关系”(UserFollowRelation)单独建模,让UserInfo持有UserFollowRelation的集合。
业务模型
示例代码
/** * 关注关系基类 * @author DAOQIDELV * */ public abstract class FollowRelation { /** * 用户ID */ protected long followingUserId; // 省略setter/getter }
public class FollowFactory extends Factory { public static UserFollowRelation getUserFollowRelationInstance(UserInfo followingUser, UserInfo followedUser) { if (followingUser == null || followedUser == null) { return null; } if (followedUser.getUserId() == followingUser.getUserId()) { return null; } return new UserFollowRelation(followingUser.getUserId(), followedUser.getUserId()); } }
public class UserFollowRelation extends FollowRelation { /** * 用户ID */ private long followingUserId; /** * 关注用户ID */ private long followedUserId; /** * 是否有效:1-有效,0-无效 */ private String enabled; /** * 来源 */ private String source; /** * 关注类型,0:系统默认:1:自主关注 */ private String followType; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (followedUserId ^ (followedUserId >>> 32)); result = prime * result + (int) (followingUserId ^ (followingUserId >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; UserFollowRelation other = (UserFollowRelation) obj; if (followedUserId != other.followedUserId) return false; if (followingUserId != other.followingUserId) return false; return true; } //省略setter/getter方法 }
public class UserInfo { // 用户ID private long userId; /** * user关注关系集合,关注侧和被关注侧维护的相同的、唯一的关系对象 */ private Map<UserFollowRelation, UserFollowRelation> userFollowRelationMap = new HashMap<UserFollowRelation, UserFollowRelation>(); /** * 判定给定用户是否是自己 * * @param UserInfo * 给定的用户 * @return true —— 是本人 false —— 不是本人 */ public boolean isMyself(UserInfo userInfo) { if (userInfo == null) { return false; } if (userInfo.getUserId() == this.getUserId()) { return true; } return false; } /** * 关注者 追随 被关注者 如果此人已经关注过指定的用户,则不再重复关注 * * @param followedUserId * 被关注者userId * @return followedUser 被关注者 * @throws BusinessException */ public UserFollowRelation follow(long followedUserId) throws BusinessException { UserInfo followedUser = new UserInfo(followedUserId); return this.follow(followedUser); } /** * 关注者 追随 被关注者 如果此人已经关注过指定的用户,则不再重复关注,单向 * * @param followedUser * @return followedUser 被关注者 * @throws BusinessException */ public UserFollowRelation follow(UserInfo followedUser) throws BusinessException { if (followedUser == null) { return null; } if (this.isMyself(followedUser)) { throw new BusinessException(MessageConst.CODE_1008); } UserFollowRelation followingRelationKeyOrInitial = FollowFactory.getUserFollowRelationInstance(this, followedUser); // 获取当前对象和需要关注对象的关系 A关注B UserFollowRelation followingSide = this.userFollowRelationMap.get(followingRelationKeyOrInitial); // 获取当前对象和需要关注对象的关系 B被A关注了 UserFollowRelation followedSide = followedUser.userFollowRelationMap.get(followingRelationKeyOrInitial); // 关注侧和被关注侧,只需要做一次判断即可(关系的原子性) if (followingSide == null) { followingSide = followingRelationKeyOrInitial; followingSide.setFollowType(FollowRelationConst.FOLLOW_TYPE_1); followingSide.setSource(FollowRelationConst.SOURCE_COMMUNITY); followingSide.setalreadyPersistentStatus(false); this.userFollowRelationMap.put(followingRelationKeyOrInitial, followingSide); // 同一份关系 followedSide = followingSide; followedUser.userFollowRelationMap.put(followingRelationKeyOrInitial, followedSide); }else if (followingSide != null)) { // 如果有记录并且关注状态为未关注,则更改为关注 followingSide.setFollowType(followingSide.getFollowType() != null ? followingSide.getFollowType() : FollowRelationConst.FOLLOW_TYPE_1); followingSide.setSource(followingSide.getSource() != null ? followingSide.getSource() : FollowRelationConst.SOURCE_COMMUNITY); followingSide.setalreadyPersistentStatus(true); this.userFollowRelationMap.put(followingRelationKeyOrInitial, followingSide); followedSide.setFollowType(followedSide.getFollowType() != null ? followedSide.getFollowType() : FollowRelationConst.FOLLOW_TYPE_1); followedSide.setSource( followedSide.getSource() != null ? followedSide.getSource() : FollowRelationConst.SOURCE_COMMUNITY); followedSide.setalreadyPersistentStatus(true); followedUser.userFollowRelationMap.put(followingRelationKeyOrInitial, followedSide); } else { throw new BusinessException(MessageConst.CODE_1025); } return followingSide; } public void followed(long followingUserId) throws BusinessException { UserInfo followedUser = new UserInfo(followingUserId); this.followed(followedUser); } public void followed(UserInfo followingUser) throws BusinessException { if (followingUser == null) { return; } if (this.isMyself(followingUser)) { throw new BusinessException(MessageConst.CODE_1008); } UserFollowRelation followedRelationKeyOrInitial = FollowFactory.getUserFollowRelationInstance(followingUser, this); // 获取当前对象和需要关注对象的关系 B被A关注 UserFollowRelation followedSide = followingUser.userFollowRelationMap.get(followedRelationKeyOrInitial); // 获取当前对象和需要关注对象的关系 A关注B UserFollowRelation followingSide = this.userFollowRelationMap.get(followedRelationKeyOrInitial); if (followedSide == null) { followedSide = followedRelationKeyOrInitial; followedSide.setFollowType(FollowRelationConst.FOLLOW_TYPE_1); followedSide.setSource(FollowRelationConst.SOURCE_COMMUNITY); this.userFollowRelationMap.put(followedRelationKeyOrInitial, followedSide); // 同一份关系 followingSide = followedSide; followingUser.userFollowRelationMap.put(followedRelationKeyOrInitial, followingSide); }else if (followedSide != null) { // 如果有记录并且关注状态为未关注,则更改为关注 followedSide.setFollowType(followedSide.getFollowType() != null ? followedSide.getFollowType() : FollowRelationConst.FOLLOW_TYPE_1); followedSide.setSource( followedSide.getSource() != null ? followedSide.getSource() : FollowRelationConst.SOURCE_COMMUNITY); this.userFollowRelationMap.put(followedRelationKeyOrInitial, followedSide); followingSide.setFollowType(followingSide.getFollowType() != null ? followingSide.getFollowType() : FollowRelationConst.FOLLOW_TYPE_1); followingSide.setSource(followingSide.getSource() != null ? followingSide.getSource() : FollowRelationConst.SOURCE_COMMUNITY); followingUser.userFollowRelationMap.put(followedRelationKeyOrInitial, followingSide); } } /** * 取消关注 返回 非null,则需改变数据库enable状态 * * @param followedUser * @return * @throws BusinessException */ public UserFollowRelation cancelFollow(UserInfo followedUser) throws BusinessException { if (followedUser == null) { return null; } if (this.isMyself(followedUser)) { throw new BusinessException(MessageConst.CODE_1008); } UserFollowRelation cancelRelationKey = FollowFactory.getUserFollowRelationInstance(this, followedUser); // 1.关注端取消 UserFollowRelation followingSide = this.userFollowRelationMap.get(cancelRelationKey); if (followingSide == null) { throw new BusinessException(MessageConst.CODE_1023); } // 2.被关注端取消 UserFollowRelation followedSide = followedUser.userFollowRelationMap.get(cancelRelationKey); // 如果不为空,且已经关注,且状态有效 if (followingSide != null) { followingSide.setEnabled(FollowRelationConst.FOLLOW_ENABLED_0); followingSide.setSource(FollowRelationConst.SOURCE_COMMUNITY);// 社区场景 followingSide.setFollowType(FollowRelationConst.FOLLOW_TYPE_1); this.userFollowRelationMap.put(followingSide, followingSide); // 如果不为空,且已经关注,且状态有效 followedSide.setEnabled(FollowRelationConst.FOLLOW_ENABLED_0); followedSide.setSource(FollowRelationConst.SOURCE_COMMUNITY);// 社区场景 followedSide.setFollowType(FollowRelationConst.FOLLOW_TYPE_1); followedUser.userFollowRelationMap.put(followedSide, followedSide); } else { throw new BusinessException(MessageConst.CODE_1024); } return followingSide; } /** * 检查本人是否关注过指定的User,单向关系 * * @param userInfo * 指定的用户 * @return boolean true —— 已经关注了指定的user false —— 还未关注指定的user,如果指定用户是自己,则也返回false * @throws BusinessException */ public boolean hasFollowed(UserInfo userInfo) { if (this.isMyself(userInfo)) { return false; } else { UserFollowRelation FollowingRelation = this.userFollowRelationMap .get(FollowFactory.getUserFollowRelationInstance(this, userInfo)); if (FollowingRelation != null) { return true; } } return false; } /** * 检查本人与指定的FollowingUser的关注关系,和hasFollowed不一样的地方在于,该方法可以识别出‘互粉’,双向关系 * * @param userInfo * 指定的用户 * @return String 1:自己 2:单粉 3:互粉 4:未关注 * @throws BusinessException */ public String getFollowRelation(UserInfo followingUser) { String followState = FollowRelationConst.FOLLOW_SIGN_4; if (this.isMyself(followingUser)) { followState = FollowRelationConst.FOLLOW_SIGN_1; } else { boolean AFollowB = this.hasFollowed(followingUser); boolean BFollowA = followingUser.hasFollowed(this); if (AFollowB == true && BFollowA == true) { // A关注 B,B也关注A followState = FollowRelationConst.FOLLOW_SIGN_3; } else if (AFollowB == true || BFollowA == true) { followState = FollowRelationConst.FOLLOW_SIGN_2; } else { followState = FollowRelationConst.FOLLOW_SIGN_4; } } return followState; } //ignore setter/getter }
UserFollowRelation是一个值对象。
NOTE:UserInfo持有UserFollowRelation的表现形式为一个Map,且map的key和value均为同一个UserFollowRelation对象,这样做的目的是为了方便在hasFollowed等场景下,快速地根据(followingUserId和followedUserId)查找到一个UserFollowRelation,如果使用Set也可以实现,但是需要遍历整个Set,性能上有损耗。
Summarize
从上述建模过程中可以发现,我们最开始从‘查询关注关系’入手建模,得到的简易模型无法满足后续‘人关注人’、‘取消关注’两个场景,导致推翻重来。故,最好从模型的最复杂场景开始建模,而不是最简单场景。