社区业务领域中,存在‘人关注人’的场景,在这个场景中,关系较为复杂,且均表现在‘人’同一个业务实体上,因此,这个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

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

【DDD】业务建模实践 —— 人关注人的更多相关文章

  1. 【DDD】业务建模实践 —— 删除帖子

    本文是基于上一篇‘业务建模战术’的实践,主要讲解‘删除帖子’场景的业务建模,包括:业务建模.业务模型.示例代码:示例代码会使用java编写,文末附有github地址.相比于<领域驱动设计> ...

  2. 【DDD】业务建模实践 —— 发布帖子

    本文是基于上一篇‘业务建模战术’的实践,主要讲解‘发表帖子’场景的业务建模,包括:业务建模.业务模型.示例代码:示例代码会使用java编写,文末附有github地址.相比于<领域驱动设计> ...

  3. 【DDD】领域驱动设计实践 —— 业务建模小招数

    本文结合团队在ECO(社区服务系统)业务建模过程中的实践经验,总结得到一些DDD业务建模的小招数,不一定是完美的,但是对我们团队来说很有效用,希望能帮到其他人.后面会陆续将项目中业务建模的一些经典例子 ...

  4. 【DDD】领域驱动设计实践 —— 业务建模战术

    本文结合团队在COMMUNITY(社区服务系统)业务建模过程中的实践经验,总结得到一些DDD业务建模的小招数,不一定是完美的,但是对我们团队来说很有效用,希望能帮到其他人.后面会陆续将项目中业务建模的 ...

  5. 【DDD】领域驱动设计实践 —— 业务建模实例(‘发布帖子’)

    本文是基于上一篇‘业务建模小招数’的实践,后面的多篇博文类似.本文主要讲解‘发表帖子’场景的业务建模,包括:业务建模.业务模型.示例代码:示例代码会使用java编写,文末附有github地址.相比于& ...

  6. 使用 UML 进行业务建模:理解业务用例与系统用例的相似和不同之处

    使用 UML 进行业务建模:理解业务用例与系统用例的相似和不同之处   作者:Arthur V. English 出处:IBM   本文内容包括: 背景 业务用例模型与系统用例模型有什么相似之处? 业 ...

  7. DCI架构是如何解决DDD战术建模缺点的?

    摘要:将DCI架构总结成一句话就是:领域对象(Object)在不同的场景(Context)中扮演(Cast)不同的角色(Role),角色之间通过交互(Interactive)来完成具体的业务逻辑. 本 ...

  8. 领域驱动设计(DDD)的实践经验分享之ORM的思考

    原文:领域驱动设计(DDD)的实践经验分享之ORM的思考 最近一直对DDD(Domain Driven Design)很感兴趣,于是去网上找了一些文章来看看,发现它确实是个好东西.于是我去买了两本关于 ...

  9. 直播开始:'云榨汁机'诞生记--聊聊JavaScript中的'业务建模'

    闭包是JavaScript中的一个重要特性,在之前的博文中,我们说闭包是一个'看似简单,其实很有内涵'的特性.当我们用JavaScript来实现相对复杂的业务建模时,我们可以如何利用'闭包'这个特性呢 ...

随机推荐

  1. 关系型数据库和NOSQL数据库对比

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt328 关系型数据库,是建立在关系模型基础上的数据库,其借助于集合代数等数学概 ...

  2. 《Java从入门到放弃》JavaSE入门篇:异常

    异常!!!看看生活中的异常例子: 正常情况下,从家到公司上班,只需要20分钟!但如果在路上碰到堵车或修路或车突然自燃等问题,那就没办法正常去上班了.其中堵车或修路或车突然自燃等问题就属于异常. 碰到异 ...

  3. python爬虫scrapy框架——人工识别登录知乎倒立文字验证码和数字英文验证码(2)

    操作环境:python3 在上一文中python爬虫scrapy框架--人工识别知乎登录知乎倒立文字验证码和数字英文验证码(1)我们已经介绍了用Requests库来登录知乎,本文如果看不懂可以先看之前 ...

  4. js正则知识点

    正则主要是用来匹配有规律的字符串,也就是说你要写一个正则前你必须非常清楚该类型字符串的规则,(比如邮箱)如果你没了解邮箱的规则那么你正则无论怎么写都是错的. \w字符(字母数字下划线)\W非字符\s空 ...

  5. 201521123009 《Java程序设计》第7周学习总结

    1. 本周学习总结 2. 书面作业 Q1:ArrayList代码分析 1.1 解释ArrayList的contains源代码 //contains()方法 public boolean contain ...

  6. 201521123096《Java程序设计》第六周学习总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 2. 书面作业 (1)clone方法 1.1 Object ...

  7. 201521123015 《Java程序设计》第6周学习总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 2. 书面作业 1.clone方法 1.1 Object对 ...

  8. 201521123089 《Java程序设计》第2周学习总结

    1. 本周学习总结 ① java数组的使用.② String类,对象是不可变的,当新的字符串的内容与字符串池中的内容相同时,不是重新开辟新的内存存储,而是共享. 2. 书面作业 (1)使用Eclips ...

  9. 201521123049 《JAVA程序设计》 第1周学习总结

    1. 本章学习总结 1.认识了新的一门计算机编程语言JAVA: 2.JAVA的编写与C语言类似,都是不能利用指针进行编写: 3.在实验课上初步认识JAVA并利用JAVA进行简单的编程,在实践上得到进一 ...

  10. 让SAE下的wordpress支持文件上传

    非PHP程序员照着源码打的小布丁… SAE是不允许本地磁盘读写的,所以使用wordpress撰写文章的时候, 上传控件默认是用不了的,幸好SAE提供了storage服务来存储文件,那就可以修改word ...