Skip to content

Commit cabc814

Browse files
authored
[Refactor] fetching member attendances (#69)
* refactor : 데이터 합산 로직 DTO에서 `MemberAttendanceAggregator`로 분리 * refactor : MemberService에서 단순 조회 로직 Finder로 분리, Aggregator 로직 적용 및 리팩토링 * refactor : controller의 request를 service 레이어에서 사용하는 Command로 변환하여 전달하도록 변경 * test : MemberControllerTest에서 Command를 전달하도록 수정 * test : MemberServiceTest에서 Finder로직 적용 * test : MemberFinderTest 작성
1 parent d76de09 commit cabc814

17 files changed

+395
-175
lines changed

src/main/java/gdsc/konkuk/platformcore/application/attendance/dtos/MemberAttendanceInfo.java

+10
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,14 @@ public class MemberAttendanceInfo {
1717
private AttendanceType attendanceType;
1818
private LocalDateTime attendanceDate;
1919
private Long participantId;
20+
21+
public static MemberAttendanceInfo from(MemberAttendanceQueryDto attendanceInfo) {
22+
return MemberAttendanceInfo.builder()
23+
.memberId(attendanceInfo.getMemberId())
24+
.attendanceDate(attendanceInfo.getAttendanceDate())
25+
.participantId(attendanceInfo.getParticipantId())
26+
.attendanceId(attendanceInfo.getAttendanceId())
27+
.attendanceType(attendanceInfo.getAttendanceType())
28+
.build();
29+
}
2030
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package gdsc.konkuk.platformcore.application.member;
2+
3+
import gdsc.konkuk.platformcore.application.attendance.dtos.MemberAttendanceInfo;
4+
import gdsc.konkuk.platformcore.application.attendance.dtos.MemberAttendanceQueryDto;
5+
import gdsc.konkuk.platformcore.application.member.dtos.MemberAttendanceAggregate;
6+
import gdsc.konkuk.platformcore.application.member.dtos.MemberAttendanceInfos;
7+
8+
import java.util.HashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
public class MemberAttendanceAggregator {
13+
public static List<MemberAttendanceAggregate> process(final List<MemberAttendanceQueryDto> queryDtos) {
14+
Map<Long, MemberAttendanceAggregate> memberAttendanceAggregates = new HashMap<>();
15+
Map<Long, MemberAttendanceInfos> memberAttendanceInfos = new HashMap<>();
16+
aggregateMemberAttendances(memberAttendanceAggregates, memberAttendanceInfos, queryDtos);
17+
validateAggregation(memberAttendanceAggregates, memberAttendanceInfos);
18+
return mergeIntoList(memberAttendanceAggregates, memberAttendanceInfos);
19+
}
20+
21+
/***
22+
* 사용자별 출석 정보를 집계하는 함수로 사용자별 출석 통계 정보와 출석 상세 정보를 집계한다.
23+
* @param memberAttendanceAggregates 출석 통계 정보
24+
* @param memberAttendanceInfos 출석 상세 정보
25+
* @param queries 출석정보 쿼리 dto
26+
*/
27+
private static void aggregateMemberAttendances(
28+
final Map<Long, MemberAttendanceAggregate> memberAttendanceAggregates,
29+
final Map<Long, MemberAttendanceInfos> memberAttendanceInfos,
30+
final List<MemberAttendanceQueryDto> queries) {
31+
for (MemberAttendanceQueryDto queryDto : queries) {
32+
memberAttendanceAggregates.putIfAbsent(queryDto.getMemberId(), MemberAttendanceAggregate.from(queryDto));
33+
addToMemberAttendanceRecords(memberAttendanceInfos, queryDto);
34+
}
35+
}
36+
37+
// query 데이터를 읽고 사용자별 기록에 추가하는 함수
38+
private static void addToMemberAttendanceRecords(final Map<Long, MemberAttendanceInfos> attendanceRecordsMap,
39+
final MemberAttendanceQueryDto queryDto) {
40+
MemberAttendanceInfos records = attendanceRecordsMap
41+
.computeIfAbsent(queryDto.getMemberId(), key -> new MemberAttendanceInfos());
42+
records.addAttendanceInfo(MemberAttendanceInfo.from(queryDto));
43+
}
44+
45+
private static void validateAggregation(final Map<Long, MemberAttendanceAggregate> memberAttendanceSummary,
46+
final Map<Long, MemberAttendanceInfos> memberAttendanceDetails) {
47+
if(memberAttendanceSummary.size() != memberAttendanceDetails.size()) {
48+
throw new IllegalStateException("Member attendance summary and details are not matched");
49+
}
50+
}
51+
52+
/***
53+
* 사용자별 출석 통계 정보에 출석 상세 정보들을 추가하고 리스트로 반환하는 함수
54+
* @param memberAttendanceAggregates 출석 통계 정보
55+
* @param memberAttendanceInfos 출석 상세 정보
56+
* @return 사용자별 출석 통계 정보 리스트
57+
*/
58+
private static List<MemberAttendanceAggregate> mergeIntoList(final Map<Long, MemberAttendanceAggregate> memberAttendanceAggregates,
59+
final Map<Long, MemberAttendanceInfos> memberAttendanceInfos) {
60+
for (Map.Entry<Long, MemberAttendanceInfos> memberAttendance : memberAttendanceInfos.entrySet()) {
61+
MemberAttendanceAggregate aggregate = memberAttendanceAggregates.get(memberAttendance.getKey());
62+
aggregate.setAttendanceInfoList(memberAttendance.getValue().getAttendanceInfoList());
63+
aggregate.setTotalAttendances(memberAttendance.getValue().getTotalAttendances());
64+
aggregate.setActualAttendances(memberAttendance.getValue().getActualAttendances());
65+
}
66+
return memberAttendanceAggregates.values().stream().toList();
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package gdsc.konkuk.platformcore.application.member;
2+
3+
import gdsc.konkuk.platformcore.application.member.exceptions.MemberErrorCode;
4+
import gdsc.konkuk.platformcore.application.member.exceptions.UserNotFoundException;
5+
import gdsc.konkuk.platformcore.domain.member.entity.Member;
6+
import gdsc.konkuk.platformcore.domain.member.repository.MemberRepository;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.stereotype.Component;
9+
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Optional;
13+
14+
import static java.util.function.Function.identity;
15+
import static java.util.stream.Collectors.toMap;
16+
17+
@Component
18+
@RequiredArgsConstructor
19+
public class MemberFinder {
20+
21+
private final MemberRepository memberRepository;
22+
23+
public Member fetchMemberById(Long memberId) {
24+
return memberRepository
25+
.findById(memberId)
26+
.orElseThrow(() -> UserNotFoundException.of(MemberErrorCode.USER_NOT_FOUND));
27+
}
28+
29+
public Map<Long, Member> fetchMembersByIdsAndBatch(List<Long> memberIds, String batch) {
30+
List<Member> members = memberRepository.findAllByIdsAndBatch(memberIds, batch);
31+
if (members.size() != memberIds.size()) {
32+
throw UserNotFoundException.of(MemberErrorCode.USER_NOT_FOUND);
33+
}
34+
return members.stream().collect(toMap(Member::getId, identity()));
35+
}
36+
37+
public boolean checkMemberExistWithStudentId(String studentId) {
38+
Optional<Member> member = memberRepository.findByStudentId(studentId);
39+
return member.isPresent();
40+
}
41+
}

src/main/java/gdsc/konkuk/platformcore/application/member/MemberService.java

+40-44
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
import gdsc.konkuk.platformcore.application.attendance.dtos.MemberAttendanceQueryDto;
77
import gdsc.konkuk.platformcore.application.attendance.exceptions.AttendanceErrorCode;
88
import gdsc.konkuk.platformcore.application.attendance.exceptions.ParticipantNotFoundException;
9-
import gdsc.konkuk.platformcore.application.member.dtos.MemberAttendances;
9+
import gdsc.konkuk.platformcore.application.member.dtos.MemberAttendanceAggregate;
10+
import gdsc.konkuk.platformcore.application.member.dtos.MemberCreateCommand;
11+
import gdsc.konkuk.platformcore.application.member.dtos.MemberUpdateCommand;
1012
import gdsc.konkuk.platformcore.application.member.exceptions.MemberErrorCode;
1113
import gdsc.konkuk.platformcore.application.member.exceptions.UserAlreadyExistException;
1214
import gdsc.konkuk.platformcore.application.member.exceptions.UserNotFoundException;
13-
import gdsc.konkuk.platformcore.controller.member.dtos.AttendanceUpdateInfo;
14-
import gdsc.konkuk.platformcore.controller.member.dtos.MemberRegisterRequest;
15-
import gdsc.konkuk.platformcore.controller.member.dtos.MemberUpdateInfo;
15+
import gdsc.konkuk.platformcore.application.member.dtos.AttendanceUpdateCommand;
1616
import gdsc.konkuk.platformcore.domain.attendance.entity.Participant;
1717
import gdsc.konkuk.platformcore.domain.attendance.repository.AttendanceRepository;
1818
import gdsc.konkuk.platformcore.domain.attendance.repository.ParticipantRepository;
@@ -23,7 +23,6 @@
2323
import java.time.LocalTime;
2424
import java.util.List;
2525
import java.util.Map;
26-
import java.util.Optional;
2726
import lombok.RequiredArgsConstructor;
2827
import org.springframework.stereotype.Service;
2928
import org.springframework.transaction.annotation.Transactional;
@@ -33,6 +32,7 @@
3332
@Transactional(readOnly = true)
3433
public class MemberService {
3534

35+
private final MemberFinder memberFinder;
3636
private final MemberRepository memberRepository;
3737
private final AttendanceRepository attendanceRepository;
3838
private final ParticipantRepository participantRepository;
@@ -42,73 +42,74 @@ public List<Member> getMembersInBatch(String batch) {
4242
}
4343

4444
@Transactional
45-
public Member register(MemberRegisterRequest registerRequest) {
46-
if (checkMemberExistWithStudentId(registerRequest.getStudentId())) {
45+
public Member register(MemberCreateCommand memberCreateCommand) {
46+
if (memberFinder.checkMemberExistWithStudentId(memberCreateCommand.getStudentId())) {
4747
throw UserAlreadyExistException.of(MemberErrorCode.USER_ALREADY_EXISTS);
4848
}
49-
return memberRepository.save(MemberRegisterRequest.toEntity(registerRequest));
49+
return memberRepository.save(MemberCreateCommand
50+
.toEntity(memberCreateCommand));
5051
}
5152

5253
@Transactional
5354
public void withdraw(Long currentId) {
54-
Member member =
55-
memberRepository
56-
.findById(currentId)
57-
.orElseThrow(
58-
() -> UserNotFoundException.of(MemberErrorCode.USER_NOT_FOUND));
55+
Member member = memberFinder.fetchMemberById(currentId);
5956
member.withdraw();
6057
}
6158

6259
@Transactional
63-
public void updateMembers(String batch, @Valid List<MemberUpdateInfo> updateInfos) {
64-
List<Long> memberIds = updateInfos.stream().map(MemberUpdateInfo::getMemberId).toList();
65-
Map<Long, Member> memberMap = fetchMembers(memberIds, batch);
60+
public void updateMembers(String batch, @Valid List<MemberUpdateCommand> updateInfos) {
61+
List<Long> memberIds = updateInfos.stream().map(MemberUpdateCommand::getMemberId).toList();
62+
Map<Long, Member> memberMap = memberFinder.fetchMembersByIdsAndBatch(memberIds, batch);
6663
updateMembers(memberMap, updateInfos);
6764
}
6865

69-
public List<MemberAttendances> getMemberAttendanceWithBatchAndPeriod(String batch,
70-
LocalDate month) {
66+
/**
67+
* {batch} 기수에 속한 멤버들의 {month}간 출석 정보를 조회하는 메소드
68+
* @param batch
69+
* @param month
70+
* @return List<MemberAttendances> 멤버별 출석 정보, 통계 dto 리스트
71+
* */
72+
public List<MemberAttendanceAggregate> getMemberAttendanceWithBatchAndPeriod(String batch,
73+
LocalDate month) {
7174
List<MemberAttendanceQueryDto> attendanceInfoList =
7275
attendanceRepository.findAllAttendanceInfoByBatchAndPeriod(
7376
batch,
7477
month.withDayOfMonth(1).atStartOfDay(),
7578
month.withDayOfMonth(month.lengthOfMonth()).atTime(LocalTime.MAX));
76-
return MemberAttendances.from(attendanceInfoList);
79+
return MemberAttendanceAggregator.process(attendanceInfoList);
7780
}
7881

82+
/***
83+
* {batch} 기수에 속한 멤버들의 {month}간 출석 정보를 업데이트하는 메소드
84+
* @param batch 소속 기수
85+
* @param month 출석 정보를 업데이트할 월
86+
* @param attendanceUpdateCommandList 업데이트할 출석 정보 리스트
87+
*/
7988
@Transactional
8089
public void updateAttendances(
81-
String batch, LocalDate month, List<AttendanceUpdateInfo> attendanceUpdateInfoList) {
90+
String batch, LocalDate month, List<AttendanceUpdateCommand> attendanceUpdateCommandList) {
8291
Map<Long, Participant> participantMap = fetchParticipants(batch, month);
83-
updateAttendanceStatuses(participantMap, attendanceUpdateInfoList);
92+
updateAttendanceStatuses(participantMap, attendanceUpdateCommandList);
8493
}
8594

86-
private void updateMembers(Map<Long, Member> memberMap, List<MemberUpdateInfo> updateInfos) {
87-
for (MemberUpdateInfo memberUpdateInfo : updateInfos) {
88-
if (!memberMap.containsKey(memberUpdateInfo.getMemberId())) {
95+
private void updateMembers(Map<Long, Member> memberMap, List<MemberUpdateCommand> updateCommands) {
96+
for (MemberUpdateCommand memberUpdateCommand : updateCommands) {
97+
if (!memberMap.containsKey(memberUpdateCommand.getMemberId())) {
8998
throw UserNotFoundException.of(MemberErrorCode.USER_NOT_FOUND);
9099
}
91-
Member member = memberMap.get(memberUpdateInfo.getMemberId());
92-
member.update(memberUpdateInfo.toCommand());
100+
Member member = memberMap.get(memberUpdateCommand.getMemberId());
101+
member.update(memberUpdateCommand);
93102
}
94103
}
95104

96-
private Map<Long, Member> fetchMembers(List<Long> memberIds, String batch) {
97-
List<Member> members = memberRepository.findAllByIdsAndBatch(memberIds, batch);
98-
if (members.size() != memberIds.size()) {
99-
throw UserNotFoundException.of(MemberErrorCode.USER_NOT_FOUND);
100-
}
101-
return members.stream().collect(toMap(Member::getId, identity()));
102-
}
103-
104105
private void updateAttendanceStatuses(Map<Long, Participant> participants,
105-
List<AttendanceUpdateInfo> updateInfos) {
106-
for (AttendanceUpdateInfo attendanceUpdateInfo : updateInfos) {
107-
if (!participants.containsKey(attendanceUpdateInfo.getParticipantId())) {
106+
List<AttendanceUpdateCommand> updateCommands) {
107+
for (AttendanceUpdateCommand attendanceUpdateCommand : updateCommands) {
108+
if (!participants.containsKey(attendanceUpdateCommand.getParticipantId())) {
108109
throw ParticipantNotFoundException.of(AttendanceErrorCode.PARTICIPANT_NOT_FOUND);
109110
}
110-
Participant participant = participants.get(attendanceUpdateInfo.getParticipantId());
111-
participant.updateAttendanceStatus(attendanceUpdateInfo.getAttendanceType());
111+
Participant participant = participants.get(attendanceUpdateCommand.getParticipantId());
112+
participant.updateAttendanceStatus(attendanceUpdateCommand.getAttendanceType());
112113
}
113114
}
114115

@@ -121,9 +122,4 @@ private Map<Long, Participant> fetchParticipants(String batch, LocalDate month)
121122
.stream()
122123
.collect(toMap(Participant::getId, identity()));
123124
}
124-
125-
private boolean checkMemberExistWithStudentId(String studentId) {
126-
Optional<Member> member = memberRepository.findByStudentId(studentId);
127-
return member.isPresent();
128-
}
129125
}

src/main/java/gdsc/konkuk/platformcore/controller/member/dtos/AttendanceUpdateInfo.java src/main/java/gdsc/konkuk/platformcore/application/member/dtos/AttendanceUpdateCommand.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package gdsc.konkuk.platformcore.controller.member.dtos;
1+
package gdsc.konkuk.platformcore.application.member.dtos;
22

33
import gdsc.konkuk.platformcore.domain.attendance.entity.AttendanceType;
44
import jakarta.validation.constraints.NotNull;
@@ -9,7 +9,7 @@
99
@Getter
1010
@Setter
1111
@Builder
12-
public class AttendanceUpdateInfo {
12+
public class AttendanceUpdateCommand {
1313

1414
@NotNull
1515
private Long participantId;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package gdsc.konkuk.platformcore.application.member.dtos;
2+
3+
import gdsc.konkuk.platformcore.application.attendance.dtos.MemberAttendanceInfo;
4+
import gdsc.konkuk.platformcore.application.attendance.dtos.MemberAttendanceQueryDto;
5+
import gdsc.konkuk.platformcore.domain.member.entity.MemberRole;
6+
import jakarta.validation.constraints.NotEmpty;
7+
import jakarta.validation.constraints.NotNull;
8+
9+
import lombok.AllArgsConstructor;
10+
import lombok.Builder;
11+
import lombok.Getter;
12+
import lombok.Setter;
13+
14+
import java.util.List;
15+
16+
@Getter
17+
@Setter
18+
@Builder
19+
@AllArgsConstructor
20+
public class MemberAttendanceAggregate {
21+
22+
@NotNull
23+
private List<MemberAttendanceInfo> attendanceInfoList;
24+
@NotNull
25+
private Long memberId;
26+
@NotEmpty
27+
private String memberName;
28+
@NotNull
29+
private MemberRole memberRole;
30+
@NotEmpty
31+
private String department;
32+
@NotEmpty
33+
private Long totalAttendances;
34+
@NotEmpty
35+
private Long actualAttendances;
36+
37+
public static MemberAttendanceAggregate from(
38+
MemberAttendanceQueryDto attendanceInfo) {
39+
return MemberAttendanceAggregate.builder()
40+
.memberId(attendanceInfo.getMemberId())
41+
.memberRole(attendanceInfo.getMemberRole())
42+
.memberName(attendanceInfo.getMemberName())
43+
.department(attendanceInfo.getMemberDepartment())
44+
.build();
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package gdsc.konkuk.platformcore.application.member.dtos;
2+
3+
import gdsc.konkuk.platformcore.application.attendance.dtos.MemberAttendanceInfo;
4+
import jakarta.validation.constraints.NotEmpty;
5+
import jakarta.validation.constraints.NotNull;
6+
import lombok.Getter;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
@Getter
12+
public class MemberAttendanceInfos {
13+
@NotNull
14+
private final List<MemberAttendanceInfo> attendanceInfoList = new ArrayList<>();
15+
@NotEmpty
16+
private Long totalAttendances;
17+
@NotEmpty
18+
private Long actualAttendances;
19+
20+
public MemberAttendanceInfos() {
21+
this.totalAttendances = 0L;
22+
this.actualAttendances = 0L;
23+
}
24+
25+
public void addAttendanceInfo(MemberAttendanceInfo attendanceInfo) {
26+
if (!attendanceInfo.getAttendanceType().isAbsent()) {
27+
actualAttendances++;
28+
}
29+
totalAttendances++;
30+
attendanceInfoList.add(attendanceInfo);
31+
}
32+
}

0 commit comments

Comments
 (0)