【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;
    }
 
    ......
 
}
UserInfo.java
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方法
}
FollowingUser.java
FollowdUser.java
public class PostReader extends FollowingUser {
    ......
}
PostReader.java
public class PostAuthor extends FollowedUser {
    ......
}
PostAuthor.java

   编写应用服务层代码,尝试判定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); //一个关注者判定自己和一个关注者之间的关注关系,这个在业务上讲不清楚的,很是别扭。
        ......
}
PostsServiceImpl.java

 

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.java
/**
 * 粉丝,关注者,具有‘关注者’角色的用户
 * ‘关注者’可以关注话题等,人关注人的逻辑放到了UserInfo中处理。
 * @author LENGFUPING610
 * @CreateDate 2017年9月6日
 *
 */
public class FollowingUser extends UserInfo {
     
    public FollowingUser(long userId) {
        super(userId);
    }  
}
FollowingUser.java
/**
 * @author LENGFUPING610
 * @CreateDate 2017年8月29日
 * 帖子读者
 * 读者通常也是追随者,通常会去关注作者,或者关注话题
 */
public class PostReader extends FollowingUser {
    ......
}
PostReader.java

 

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;
    }
    ......
}
UserInfo.java

 

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
}
FollowRelation.java
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());
    }

}
FollowFactory.java
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方法
}
UserFollowRelation.java
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
}
UserInfo.java

 

  UserFollowRelation是一个值对象。

  NOTE:UserInfo持有UserFollowRelation的表现形式为一个Map,且map的key和value均为同一个UserFollowRelation对象,这样做的目的是为了方便在hasFollowed等场景下,快速地根据(followingUserId和followedUserId)查找到一个UserFollowRelation,如果使用Set也可以实现,但是需要遍历整个Set,性能上有损耗。

Summarize

  从上述建模过程中可以发现,我们最开始从‘查询关注关系’入手建模,得到的简易模型无法满足后续‘人关注人’、‘取消关注’两个场景,导致推翻重来。故,最好从模型的最复杂场景开始建模,而不是最简单场景。

 

posted @ 2017-10-12 22:59  倒骑的驴  阅读(4555)  评论(5编辑  收藏  举报