-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 스터디 V2 팩토리 구현 #855
feat: 스터디 V2 팩토리 구현 #855
Changes from all commits
f120218
d2548ef
8448cc3
f6a536b
760a4fb
63dec71
87caf45
ac5503d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.gdschongik.gdsc.domain.studyv2.domain; | ||
|
||
public interface AttendanceNumberGenerator { | ||
int generate(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.gdschongik.gdsc.domain.studyv2.domain; | ||
|
||
import java.security.SecureRandom; | ||
import lombok.SneakyThrows; | ||
|
||
/** | ||
* 네 자리의 랜덤한 출석번호를 생성합니다. | ||
*/ | ||
public class RandomAttendanceNumberGenerator implements AttendanceNumberGenerator { | ||
|
||
public static final int MIN_ORIGIN = 1000; | ||
public static final int MAX_BOUND = 10000; | ||
|
||
@Override | ||
@SneakyThrows | ||
public int generate() { | ||
return SecureRandom.getInstanceStrong() | ||
.ints(MIN_ORIGIN, MAX_BOUND) | ||
.findFirst() | ||
.orElseThrow(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.gdschongik.gdsc.domain.studyv2.domain; | ||
|
||
import com.gdschongik.gdsc.domain.common.vo.Period; | ||
import com.gdschongik.gdsc.domain.common.vo.Semester; | ||
import com.gdschongik.gdsc.domain.member.domain.Member; | ||
import com.gdschongik.gdsc.domain.study.domain.StudyType; | ||
import com.gdschongik.gdsc.global.annotation.DomainFactory; | ||
import java.time.DayOfWeek; | ||
import java.time.LocalTime; | ||
import java.util.stream.IntStream; | ||
|
||
@DomainFactory | ||
public class StudyFactory { | ||
|
||
/** | ||
* 스터디 및 스터디회차를 생성합니다. | ||
* 스터디회차의 경우 총 회차 수만큼 생성되며, 생성 순서에 따라 position 값이 지정됩니다. | ||
*/ | ||
public StudyV2 create( | ||
StudyType type, | ||
String title, | ||
String description, | ||
String descriptionNotionLink, | ||
Semester semester, | ||
Integer totalRound, | ||
DayOfWeek dayOfWeek, | ||
LocalTime startTime, | ||
LocalTime endTime, | ||
Period applicationPeriod, | ||
String discordChannelId, | ||
String discordRoleId, | ||
Member mentor, | ||
AttendanceNumberGenerator attendanceNumberGenerator) { | ||
StudyV2 study = StudyV2.create( | ||
type, | ||
title, | ||
description, | ||
descriptionNotionLink, | ||
semester, | ||
totalRound, | ||
dayOfWeek, | ||
startTime, | ||
endTime, | ||
applicationPeriod, | ||
discordChannelId, | ||
discordRoleId, | ||
mentor); | ||
|
||
IntStream.rangeClosed(1, totalRound) | ||
.forEach(round -> StudySessionV2.createEmpty(round, attendanceNumberGenerator.generate(), study)); | ||
|
||
return study; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -30,6 +30,9 @@ public class StudySessionV2 extends BaseEntity { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Column(name = "study_session_v2_id") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private Long id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Comment("회차 순서") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private Integer position; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Comment("회차 제목") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private String title; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -70,13 +73,15 @@ public class StudySessionV2 extends BaseEntity { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
@Builder(access = AccessLevel.PRIVATE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private StudySessionV2( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Integer position, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
String title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
String description, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Integer lessonAttendanceNumber, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Period lessonPeriod, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
String assignmentDescriptionLink, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Period assignmentPeriod, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
StudyV2 studyV2) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.position = position; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.title = title; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.description = description; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
this.lessonAttendanceNumber = lessonAttendanceNumber; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -87,21 +92,10 @@ private StudySessionV2( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
studyV2.getStudySessions().add(this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static void create( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
String title, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
String description, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Integer lessonAttendanceNumber, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Period lessonPeriod, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
String assignmentDescriptionLink, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Period assignmentPeriod, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
StudyV2 studyV2) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static void createEmpty(Integer position, Integer lessonAttendanceNumber, StudyV2 studyV2) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
StudySessionV2.builder() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.title(title) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.description(description) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.position(position) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.lessonAttendanceNumber(lessonAttendanceNumber) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.lessonPeriod(lessonPeriod) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.assignmentDescriptionLink(assignmentDescriptionLink) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.assignmentPeriod(assignmentPeriod) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.studyV2(studyV2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.build(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+95
to
101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 팩토리 메서드의 반환 값과 유효성 검사가 필요합니다.
다음과 같이 수정하는 것을 제안합니다: - public static void createEmpty(Integer position, Integer lessonAttendanceNumber, StudyV2 studyV2) {
- StudySessionV2.builder()
- .position(position)
- .lessonAttendanceNumber(lessonAttendanceNumber)
- .studyV2(studyV2)
- .build();
+ public static StudySessionV2 createEmpty(Integer position, Integer lessonAttendanceNumber, StudyV2 studyV2) {
+ if (position == null || position <= 0) {
+ throw new IllegalArgumentException("회차 순서는 0보다 커야 합니다.");
+ }
+ if (lessonAttendanceNumber == null) {
+ throw new IllegalArgumentException("출석 번호는 null일 수 없습니다.");
+ }
+ if (studyV2 == null) {
+ throw new IllegalArgumentException("스터디 정보는 null일 수 없습니다.");
+ }
+
+ return StudySessionV2.builder()
+ .position(position)
+ .lessonAttendanceNumber(lessonAttendanceNumber)
+ .studyV2(studyV2)
+ .build();
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package com.gdschongik.gdsc.domain.studyv2.domain; | ||
|
||
import static com.gdschongik.gdsc.global.common.constant.StudyConstant.*; | ||
import static org.assertj.core.api.Assertions.*; | ||
|
||
import com.gdschongik.gdsc.domain.member.domain.Member; | ||
import com.gdschongik.gdsc.domain.study.domain.StudyType; | ||
import com.gdschongik.gdsc.helper.FixtureHelper; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class StudyFactoryTest { | ||
|
||
FixtureHelper fixtureHelper = new FixtureHelper(); | ||
StudyFactory studyFactory = new StudyFactory(); | ||
|
||
static class FixedAttendanceNumberGenerator implements AttendanceNumberGenerator { | ||
@Override | ||
public int generate() { | ||
return 1000; | ||
} | ||
} | ||
|
||
@Test | ||
void 스터디_생성시_설정한_총_회차만큼_스터디회차가_생성된다() { | ||
// given | ||
Member mentor = fixtureHelper.createMentor(1L); | ||
AttendanceNumberGenerator generator = new FixedAttendanceNumberGenerator(); | ||
int totalRound = 8; | ||
|
||
// when | ||
StudyV2 study = studyFactory.create( | ||
StudyType.OFFLINE, | ||
STUDY_TITLE, | ||
STUDY_DESCRIPTION, | ||
STUDY_DESCRIPTION_NOTION_LINK, | ||
STUDY_SEMESTER, | ||
totalRound, | ||
DAY_OF_WEEK, | ||
STUDY_START_TIME, | ||
STUDY_END_TIME, | ||
STUDY_APPLICATION_PERIOD, | ||
STUDY_DISCORD_CHANNEL_ID, | ||
STUDY_DISCORD_ROLE_ID, | ||
mentor, | ||
generator); | ||
|
||
// then | ||
assertThat(study.getStudySessions()).hasSize(8); | ||
} | ||
Comment on lines
+23
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 테스트 케이스 보완이 필요합니다. 현재 테스트는 기본적인 케이스만 다루고 있습니다. 경계값이나 예외 상황에 대한 테스트가 누락되어 있습니다. 다음과 같은 테스트 케이스 추가를 제안드립니다: @Test
void 총_회차가_0이하일_경우_예외가_발생한다() {
// given
Member mentor = fixtureHelper.createMentor(1L);
AttendanceNumberGenerator generator = new TestAttendanceNumberGenerator(1000);
int invalidTotalRound = 0;
// when & then
assertThatThrownBy(() -> studyFactory.create(
StudyType.OFFLINE,
STUDY_TITLE,
STUDY_DESCRIPTION,
STUDY_DESCRIPTION_NOTION_LINK,
STUDY_SEMESTER,
invalidTotalRound,
DAY_OF_WEEK,
STUDY_START_TIME,
STUDY_END_TIME,
STUDY_APPLICATION_PERIOD,
STUDY_DISCORD_CHANNEL_ID,
STUDY_DISCORD_ROLE_ID,
mentor,
generator))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("총 회차는 1 이상이어야 합니다");
} Also applies to: 51-79, 81-108 |
||
|
||
@Test | ||
void 스터디_생성시_스터디회차는_순서대로_position이_지정되어_생성된다() { | ||
// given | ||
Member mentor = fixtureHelper.createMentor(1L); | ||
AttendanceNumberGenerator generator = new FixedAttendanceNumberGenerator(); | ||
int totalRound = 8; | ||
|
||
// when | ||
StudyV2 study = studyFactory.create( | ||
StudyType.OFFLINE, | ||
STUDY_TITLE, | ||
STUDY_DESCRIPTION, | ||
STUDY_DESCRIPTION_NOTION_LINK, | ||
STUDY_SEMESTER, | ||
totalRound, | ||
DAY_OF_WEEK, | ||
STUDY_START_TIME, | ||
STUDY_END_TIME, | ||
STUDY_APPLICATION_PERIOD, | ||
STUDY_DISCORD_CHANNEL_ID, | ||
STUDY_DISCORD_ROLE_ID, | ||
mentor, | ||
generator); | ||
|
||
// then | ||
assertThat(study.getStudySessions()) | ||
.extracting(StudySessionV2::getPosition) | ||
.containsExactly(1, 2, 3, 4, 5, 6, 7, 8); | ||
} | ||
|
||
@Test | ||
void 스터디_생성시_각_스터디회차에_출석번호가_생성된다() { | ||
// given | ||
Member mentor = fixtureHelper.createMentor(1L); | ||
AttendanceNumberGenerator generator = new FixedAttendanceNumberGenerator(); | ||
|
||
// when | ||
StudyV2 study = studyFactory.create( | ||
StudyType.OFFLINE, | ||
STUDY_TITLE, | ||
STUDY_DESCRIPTION, | ||
STUDY_DESCRIPTION_NOTION_LINK, | ||
STUDY_SEMESTER, | ||
TOTAL_ROUND, | ||
DAY_OF_WEEK, | ||
STUDY_START_TIME, | ||
STUDY_END_TIME, | ||
STUDY_APPLICATION_PERIOD, | ||
STUDY_DISCORD_CHANNEL_ID, | ||
STUDY_DISCORD_ROLE_ID, | ||
mentor, | ||
generator); | ||
|
||
// then | ||
assertThat(study.getStudySessions()) | ||
.extracting(StudySessionV2::getLessonAttendanceNumber) | ||
.containsOnly(1000); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
package com.gdschongik.gdsc.global.common.constant; | ||
|
||
import com.gdschongik.gdsc.domain.common.model.SemesterType; | ||
import com.gdschongik.gdsc.domain.common.vo.Period; | ||
import com.gdschongik.gdsc.domain.common.vo.Semester; | ||
import com.gdschongik.gdsc.domain.study.domain.StudyType; | ||
import java.time.DayOfWeek; | ||
import java.time.LocalDateTime; | ||
|
@@ -10,12 +12,20 @@ public class StudyConstant { | |
private StudyConstant() {} | ||
|
||
public static final String STUDY_TITLE = "스터디 제목"; | ||
public static final String STUDY_DESCRIPTION = "스터디 설명"; | ||
public static final String STUDY_DESCRIPTION_NOTION_LINK = "https://gdschongik.com/2025-1-backend-study"; | ||
public static final Semester STUDY_SEMESTER = Semester.of(2025, SemesterType.FIRST); | ||
public static final Long TOTAL_WEEK = 8L; | ||
public static final int TOTAL_ROUND = 8; | ||
public static final StudyType ONLINE_STUDY = StudyType.ONLINE; | ||
public static final StudyType ASSIGNMENT_STUDY = StudyType.ASSIGNMENT; | ||
public static final DayOfWeek DAY_OF_WEEK = DayOfWeek.FRIDAY; | ||
public static final LocalTime STUDY_START_TIME = LocalTime.of(19, 0, 0); | ||
public static final LocalTime STUDY_END_TIME = LocalTime.of(20, 0, 0); | ||
public static final Period STUDY_APPLICATION_PERIOD = | ||
Period.of(LocalDateTime.of(2025, 3, 1, 0, 0), LocalDateTime.of(2025, 3, 14, 23, 59)); | ||
public static final String STUDY_DISCORD_CHANNEL_ID = "12345678"; | ||
public static final String STUDY_DISCORD_ROLE_ID = "12345678"; | ||
Comment on lines
+27
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discord ID와 같은 민감한 정보는 환경 변수로 관리하는 것이 좋습니다. Discord 채널 ID와 역할 ID를 상수로 하드코딩하는 것은 보안상 위험할 수 있습니다. 테스트용 값이라도 실제 ID 형식을 따르는 것이 좋으며, 프로덕션 환경에서는 환경 변수로 관리하는 것을 권장드립니다. |
||
|
||
// StudyDetail | ||
public static final String ATTENDANCE_NUMBER = "1234"; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
매개변수가 너무 많습니다.
현재
create
메서드는 14개의 매개변수를 가지고 있어 가독성과 유지보수성이 떨어집니다.다음과 같이 DTO를 사용하는 것을 제안합니다:
그리고 메서드 시그니처를 다음과 같이 변경하세요: