Skip to content

Commit

Permalink
Merge pull request #140 from hellokitty-coding-club/feature/#139-miss…
Browse files Browse the repository at this point in the history
…ion-view

Feature/#139 mission view
  • Loading branch information
ray-yhc authored Nov 17, 2023
2 parents 4047a50 + d09b943 commit 6c2c60b
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ public enum CacheKey {
TECH_TAG_PER_MEMBER("tech_tag_per_member", ONE_DAY.getExpiryTimeSec()),
TECH_TAG_PER_MISSION("tech_tag_per_mission", ONE_DAY.getExpiryTimeSec()),
MISSION_PARTICIPANT_COUNT("mission_participant_count", ONE_DAY.getExpiryTimeSec()),
AB_TEST_USER_GROUP("ab_test_user_group", ONE_DAY.getExpiryTimeSec()),
ON_GOING_MISSIONS("on_going_missions", ONE_MIN.getExpiryTimeSec()),
RECOMMENDED_MISSIONS("recommended_missions", ONE_MIN.getExpiryTimeSec()),
TOTAL_MISSIONS("total_missions", ONE_MIN.getExpiryTimeSec()),
MOST_VIEWED_MISSIONS("most_viewed_missions", ONE_MIN.getExpiryTimeSec()),
MISSION_SCRAP_COUNT("mission_scrap_count", ONE_MIN.getExpiryTimeSec()),
MISSION_VIEW_COUNT("mission_view_count", ONE_MIN.getExpiryTimeSec()),
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public enum HomeServerDrivenUISequenceByVersion implements ServerDrivenUISequenc
RECOMMENDED_MISSION_TITLE_V1,
RECOMMENDED_MISSION_LIST_V1,
SECTION_DARK_CLOSER_V1,
HOT_MISSION_TITLE_V1,
HOT_MISSION_LIST_V1,
SECTION_DARK_CLOSER_V1,
TOTAL_MISSION_TITLE_V1,
TOTAL_MISSION_LIST_V1,
SECTION_DARK_CLOSER_V1
Expand All @@ -32,11 +35,14 @@ public enum HomeServerDrivenUISequenceByVersion implements ServerDrivenUISequenc
List.of(ON_GOING_MISSION_TITLE_V1,
ON_GOING_MISSION_LIST_V1,
SECTION_LIGHT_CLOSER_V1,
TOTAL_MISSION_TITLE_V1,
TOTAL_MISSION_LIST_V1,
HOT_MISSION_TITLE_V1,
HOT_MISSION_LIST_V1,
SECTION_DARK_CLOSER_V1,
RECOMMENDED_MISSION_TITLE_V1,
RECOMMENDED_MISSION_LIST_V1,
SECTION_DARK_CLOSER_V1,
TOTAL_MISSION_TITLE_V1,
TOTAL_MISSION_LIST_V1,
SECTION_DARK_CLOSER_V1
));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public enum MissionContentType {

RECOMMENDED_MISSION_LIST_V1(ViewType.ITEM, Theme.GRAY, 3),

HOT_MISSION_TITLE_V1(ViewType.TITLE, Theme.GRAY, "핫한 미션"),

HOT_MISSION_LIST_V1(ViewType.ITEM, Theme.GRAY, 3),

TOTAL_MISSION_TITLE_V1(ViewType.TITLE, Theme.GRAY, "전체 미션"),

TOTAL_MISSION_LIST_V1(ViewType.ITEM, Theme.GRAY, 5),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenScreenResponse;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.domain.groupAssignment.HomeScreenABTestService;

import static swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ABTest.HOME_SCREEN_SEQUENCE_TEST;
import static swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ABTest.HOT_MISSION_FEATURE_TEST;

@Slf4j
@RestController
Expand All @@ -22,7 +22,7 @@ public class HomeController {

private final HomeService homeService;
private final HomeScreenABTestService abTestService;
private final String CURRNT_AB_TEST_NAME = HOME_SCREEN_SEQUENCE_TEST.getTestName(); // TODO : 현재 테스트 이름을 AB test 스케줄러에서 가져오도록 수정
private final String CURRNT_AB_TEST_NAME = HOT_MISSION_FEATURE_TEST.getTestName(); // TODO : 현재 테스트 이름을 AB test 스케줄러에서 가져오도록 수정

@GetMapping
public ApiDataResponse<ServerDrivenScreenResponse> getHomeScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import swm.hkcc.LGTM.app.global.entity.BaseEntity;
import swm.hkcc.LGTM.app.modules.member.domain.Member;

import java.io.Serializable;

@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MissionView extends BaseEntity {
public class MissionView extends BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "mission_view_id")
Expand All @@ -23,4 +25,11 @@ public class MissionView extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "mission_id")
private Mission mission;

public static MissionView from(Mission mission, Member member) {
return MissionView.builder()
.mission(mission)
.viewer(member)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class MissionItemHolder {
public static final String ONGOING_MISSION_EMPTY_VIEW = "ongoing_mission_empty_view";
public static final String RECOMMENDED_MISSION_EMPTY_VIEW = "recommended_mission_empty_view";

public static final String HOT_MISSION_EMPTY_VIEW = "hot_mission_empty_view";

private final MissionService missionService;

public Function<Long, MissionContentData> getMissionListFunction(MissionContentType missionContentType, MemberType memberType) {
Expand All @@ -31,6 +33,7 @@ public Function<Long, MissionContentData> getMissionListFunction(MissionContentT
case SENIOR -> missionService::getSeniorOngoingMissions;
default -> null;
};
case HOT_MISSION_LIST_V1 -> missionService::getMostViewedMissions;
default -> null;
};
}
Expand All @@ -40,6 +43,7 @@ public ServerDrivenContent getMissionEmptyView(MissionContentType missionContent
case TOTAL_MISSION_LIST_V1 -> ServerDrivenContent.from(convertToDto(TOTAL_MISSION_EMPTY_VIEW), missionContentType.getTheme(), ViewType.EMPTY);
case ON_GOING_MISSION_LIST_V1 -> ServerDrivenContent.from(convertToDto(ONGOING_MISSION_EMPTY_VIEW), missionContentType.getTheme(), ViewType.EMPTY);
case RECOMMENDED_MISSION_LIST_V1 -> ServerDrivenContent.from(convertToDto(RECOMMENDED_MISSION_EMPTY_VIEW), missionContentType.getTheme(), ViewType.EMPTY);
case HOT_MISSION_LIST_V1 -> ServerDrivenContent.from(convertToDto(HOT_MISSION_EMPTY_VIEW), missionContentType.getTheme(), ViewType.EMPTY);
default -> null;
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package swm.hkcc.LGTM.app.modules.mission.repository;

import org.springframework.cache.annotation.Cacheable;
import swm.hkcc.LGTM.app.modules.mission.domain.MissionView;

import java.util.List;

public interface MissionViewCustomRepository {
@Cacheable(value = "most_viewed_missions", key = "#p0")
List<MissionView> findByOrderByViewCountDesc(int n);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package swm.hkcc.LGTM.app.modules.mission.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import swm.hkcc.LGTM.app.modules.mission.domain.MissionView;

import java.util.List;

import static swm.hkcc.LGTM.app.modules.mission.domain.QMissionView.missionView;


@Slf4j
@Repository
@RequiredArgsConstructor
public class MissionViewCustomRepositoryImpl implements MissionViewCustomRepository {
private final JPAQueryFactory jpaQueryFactory;

@Override
public List<MissionView> findByOrderByViewCountDesc(int n) {
return jpaQueryFactory
.select(missionView)
.from(missionView)
.join(missionView.mission).fetchJoin() // missionView 캐싱 시 mission 정보까지 저장하기 위함
.join(missionView.viewer).fetchJoin() // missionView 캐싱 시 viewer 정보까지 저장하기 위함
.groupBy(missionView.mission.missionId)
.orderBy(missionView.mission.missionId.count().desc())
.limit(n)
.fetch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.springframework.data.jpa.repository.JpaRepository;
import swm.hkcc.LGTM.app.modules.mission.domain.MissionView;

public interface MissionViewRepository extends JpaRepository<MissionView, Long> {
public interface MissionViewRepository extends JpaRepository<MissionView, Long>, MissionViewCustomRepository {
@Cacheable(value = "mission_view_count", key = "#p0")
int countByMission_MissionId(Long missionId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface MissionService {

MissionContentData getRecommendMissions(Long memberId);

MissionContentData getMostViewedMissions(Long memberId);

MissionContentData getTotalMissions(Long memberId);

MissionDetailViewResponse getMissionDetail(Long memberId, Long missionId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import swm.hkcc.LGTM.app.modules.member.service.MemberService;
import swm.hkcc.LGTM.app.modules.mission.domain.Mission;
import swm.hkcc.LGTM.app.modules.mission.domain.MissionContentData;
import swm.hkcc.LGTM.app.modules.mission.domain.MissionView;
import swm.hkcc.LGTM.app.modules.mission.domain.mapper.MissionMapper;
import swm.hkcc.LGTM.app.modules.mission.dto.MissionDetailViewResponse;
import swm.hkcc.LGTM.app.modules.mission.dto.MissionDetailsDto;
Expand Down Expand Up @@ -39,6 +40,11 @@ public class MissionServiceImpl implements MissionService {
private final SeniorRepository seniorRepository;
private final MemberService memberService;

private static final String ON_GOING_MISSION = "OnGoingMission";
private static final String HOT_MISSION = "HotMission";
private static final String TOTAL_MISSION = "TotalMission";
private static final String RECOMMEND_MISSION = "RecommendMission";

@Override
public MissionContentData getJuniorOngoingMissions(Long memberId) {
List<Mission> missions = missionRepository.getJuniorOnGoingMissions(memberId);
Expand All @@ -48,7 +54,7 @@ public MissionContentData getJuniorOngoingMissions(Long memberId) {
.map(mission -> MissionMapper.missionToMissionDto(
mission,
techTagPerMissionRepository.findTechTagsByMissionId(mission.getMissionId()),
"OnGoingMission"
ON_GOING_MISSION
))
.toList()
);
Expand All @@ -63,7 +69,25 @@ public MissionContentData getSeniorOngoingMissions(Long memberId) {
.map(mission -> MissionMapper.missionToMissionDto(
mission,
techTagPerMissionRepository.findTechTagsByMissionId(mission.getMissionId()),
"OnGoingMission"
ON_GOING_MISSION
))
.toList()
);
}

@Override
public MissionContentData getMostViewedMissions(Long memberId) {
List<Mission> missions = missionViewRepository.findByOrderByViewCountDesc(3)
.stream()
.map(missionView -> missionView.getMission())
.collect(Collectors.toList());

return MissionContentData.of(
missions.stream()
.map(mission -> MissionMapper.missionToMissionDto(
mission,
techTagPerMissionRepository.findTechTagsByMissionId(mission.getMissionId()),
HOT_MISSION
))
.toList()
);
Expand All @@ -73,7 +97,7 @@ public MissionContentData getSeniorOngoingMissions(Long memberId) {
public MissionContentData getTotalMissions(Long memberId) {
List<Mission> missions = missionRepository.getTotalMissions();

return getMissionContentData(memberId, missions, "TotalMission");
return getMissionContentData(memberId, missions, TOTAL_MISSION);
}

@Override
Expand All @@ -89,6 +113,9 @@ public MissionDetailViewResponse getMissionDetail(Long memberId, Long missionId)
MemberType memberType = memberService.getMemberType(memberId);
boolean isParticipated = checkMemberIsParticipated(memberId, missionId, memberType);

MissionView missionView = MissionView.from(mission, memberService.getMember(memberId));
missionViewRepository.save(missionView);

return missionAndMemberToDetailView(mission, isScraped, missionWriter, techTagList, currentPeopleNumber, memberType, isParticipated);
}

Expand All @@ -104,7 +131,7 @@ public MissionContentData getRecommendMissions(Long memberId) {
}
}

return getMissionContentData(memberId, missions, "RecommendMission");
return getMissionContentData(memberId, missions, RECOMMEND_MISSION);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@RequiredArgsConstructor
public enum ABTest {
HOME_SCREEN_SEQUENCE_TEST("homeScreenSequenceTest"), // 홈화면에서 어떤 view type들의 순서가 success metric 을 향상시키는지 테스트
HOT_MISSION_FEATURE_TEST("hotMissionFeatureTest"), // 홈화면에서 핫한 미션 기능이 success metric 을 향상시키는지 테스트
HOME_SCREEN_RECOMMENDATION_FEATURE_TEST("homeScreenRecommendationFeatureTest"), // 홈화면에서 어떤 추천 기능이 success metric 을 향상시키는지 테스트
;
private final String testName;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package swm.hkcc.LGTM.app.modules.serverDrivenUI.domain;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.Optional;

public interface ABTestUserGroupRepository extends JpaRepository<ABTestUserGroup, Long> {
@Cacheable(value = "ab_test_user_group", key = "#p0 +'::'+ #p1")
@Query("select a.groupName from ABTestUserGroup a where a.memberId = ?1 and a.testName = ?2")
Optional<String> findGroupNameByMemberIdAndTestName(Long memberId, String testName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,40 @@

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.domain.ABTestUserGroup;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.domain.ABTestUserGroupRepository;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.domain.groupAssignment.strategy.ModuloBasedGroupAssignmentStrategy;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.domain.groupAssignment.strategy.RandomGroupAssignmentStrategy;

import static swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ABTest.HOME_SCREEN_SEQUENCE_TEST;
import static swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ABTest.HOT_MISSION_FEATURE_TEST;

@Service
@RequiredArgsConstructor
public class HomeScreenABTestService implements ABTestService {
private final ABTestUserGroupRepository abTestUserGroupRepository;
private final ModuloBasedGroupAssignmentStrategy moduloBasedGroupAssignmentStrategy;
private final RandomGroupAssignmentStrategy randomGroupAssignmentStrategy;

// TODO: 테스트 이름에 따라서 group assignment strategy를 미리 정의하고 그에 맞게 group을 할당해주는 로직을 구현
@Override
public String getGroupName(Long memberId, String testName) {
if (HOME_SCREEN_SEQUENCE_TEST.getTestName().equals(testName)) {
return useModuloBasedGroupAssignmentStrategy(memberId);
return abTestUserGroupRepository.findGroupNameByMemberIdAndTestName(memberId, testName)
.orElseGet(() -> assignGroup(memberId, testName));
}

private String assignGroup(Long memberId, String testName) {
String groupName;
if (HOT_MISSION_FEATURE_TEST.getTestName().equals(testName)) {
groupName = useRandomGroupAssignmentStrategy(memberId);
} else if (HOME_SCREEN_SEQUENCE_TEST.getTestName().equals(testName)) {
groupName = useModuloBasedGroupAssignmentStrategy(memberId);
} else {
groupName = useRandomGroupAssignmentStrategy(memberId);
}

return useRandomGroupAssignmentStrategy(memberId);
saveABTestUserGroup(memberId, testName, groupName);
return groupName;
}

private String useModuloBasedGroupAssignmentStrategy(Long memberId) {
Expand All @@ -30,4 +45,13 @@ private String useModuloBasedGroupAssignmentStrategy(Long memberId) {
private String useRandomGroupAssignmentStrategy(Long memberId) {
return randomGroupAssignmentStrategy.assignGroup(memberId);
}

private void saveABTestUserGroup(Long memberId, String testName, String groupName) {
ABTestUserGroup abTestUserGroup = ABTestUserGroup.builder()
.memberId(memberId)
.testName(testName)
.groupName(groupName)
.build();
abTestUserGroupRepository.save(abTestUserGroup);
}
}

0 comments on commit 6c2c60b

Please sign in to comment.