-
Notifications
You must be signed in to change notification settings - Fork 308
2단계 - 수강신청(도메인 모델) #811
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
2단계 - 수강신청(도메인 모델) #811
Conversation
README.md - 2단계 문서 링크를 `02-lms-domain-model.md`로 올바르게 연결하도록 수정 docs/02-lms-domain-model.md - 수강신청 도메인 모델 요구사항 문서 신규 추가 - LMS 개요 및 기능 요구사항 구조화 - 프로그래밍 요구사항 및 TDD 원칙 명시
Course.java - generation 필드 추가 및 Getter 구현 - 생성자 오버로드 수정 및 generation 반영 - 기존 필드 포함한 신규 전체 생성자 정의 JdbcCourseRepository.java - insert 쿼리에 generation 컬럼 추가 - findById 조회 시 generation 매핑 추가 및 파라미터 인덱스 조정 schema.sql - course 테이블에 generation 컬럼 추가 (NOT NULL) CourseTest.java - generation 포함 생성자 정상 생성 테스트 추가 CourseRepositoryTest.java - generation 포함한 Course 저장/조회 검증 추가 - createdAt 값 비교 테스트 추가
ImageType.java - GIF, JPG, PNG, SVG enum 정의 - jpeg 입력 시 JPG로 매핑하는 from() 메서드 구현 - 미지원 타입은 기존 valueOf에서 예외 발생 ImageTypeTest.java - jpeg 입력 시 JPG 반환 테스트 추가 - 미지원 타입 입력 시 예외 발생 테스트 추가
ImageType.java - 확장자 입력값을 대문자로 변환 후 처리하도록 개선 - JPEG → JPG 매핑 조건을 대문자 기준으로 수정 - valueOf 호출 시 대문자 사용으로 케이스 민감도 문제 해결 ImageTypeTest.java - PNG 입력값에 대해 파라미터화된 변환 성공 테스트 추가(png/PNG) - 기존 JPEG → JPG 테스트 유지
CoverImage.java - CoverImage 도메인 클래스 신규 추가 - 파일 크기 최대 1MB 제한 상수 정의 - 생성 시 파일 크기 검증 수행 - 검증 실패 시 IllegalArgumentException 발생하도록 구현 CoverImageTest.java - 정상 생성 테스트 추가 - 파일 크기 초과 시 예외 발생 테스트 추가
CoverImage.java - 최소 너비/높이 상수(MIN_WIDTH=300, MIN_HEIGHT=200) 추가 - 생성 시 validateDimension 호출하여 해상도 검증 추가 - 너비/높이 부족 시 상세 메시지 포함 예외 발생 CoverImageTest.java - 너비 부족 시 예외 발생 테스트 추가 - 높이 부족 시 예외 발생 테스트 추가
CoverImage.java - WIDTH_RATIO=3, HEIGHT_RATIO=2 상수 추가 - 생성 시 validateAspectRatio 실행하도록 추가 - 비율 불일치 시 상세 메시지 포함 예외 발생 CoverImageTest.java - 3:2 비율이 아닐 경우 예외 발생 테스트 추가
CoverImage.java - filename 기반으로 확장자를 추출해 ImageType을 자동 결정하는 생성자 추가 - extractExtension 메서드 추가하여 확장자 누락/잘못된 형식 검증 - 기존 생성자 로직과 동일하게 파일 크기·해상도·비율 검증 수행 CoverImageTest.java - 확장자 미존재 시 예외 발생 테스트 추가
ImageDimension.java - 최소 너비(300), 최소 높이(200), 비율(3:2) 검증 로직 포함한 VO 추가 - 생성 시 해상도 조건 및 비율 조건 유효성 검증 수행 - width, height 불변 값으로 보관 ImageDimensionTest.java - 정상 생성 테스트 추가 - 너비 부족, 높이 부족, 비율 불일치 경우 각각 예외 검증 테스트 추가
ImageFile.java - 파일 크기 최대 1MB 제한 검증 추가 - 파일명에서 확장자를 추출하여 ImageType 자동 매핑 - 확장자 없음 또는 빈 확장자일 경우 예외 발생 ImageFileTest.java - 정상 생성 테스트 추가 - 파일 크기 초과 시 예외 발생 테스트 추가 - 확장자 누락 시 예외 발생 테스트 추가
CoverImage.java - 파일 검증 → ImageFile로 위임 - 해상도·비율 검증 → ImageDimension으로 위임 - CoverImage는 두 VO를 구성요소로 보유하도록 변경 - 기존 개별 검증 로직 및 생성자 삭제 CoverImageTest.java - 파일/해상도/비율 검증 책임이 개별 VO로 이동함에 따라 테스트 제거
SessionStatus.java - PREPARING, RECRUITING, CLOSED 상태 enum 정의 - 상태별 설명 필드 추가 - 모집중(RECRUITING)일 때만 수강신청 가능하도록 canEnroll() 메서드 구현 SessionStatusTest.java - 준비중/종료 상태에서는 신청 불가, 모집중일 때만 신청 가능함을 검증하는 테스트 추가
SessionPeriod.java - 시작일·종료일을 표현하는 VO 추가 - 종료일이 시작일보다 이전일 경우 예외 발생하도록 검증 로직 구현 SessionPeriodTest.java - 정상 생성 테스트 추가 - 종료일 < 시작일일 때 예외 발생 테스트 추가
Money.java - 음수 금액을 허용하지 않는 Money VO 추가 - 0 이상인지 검증하는 validate 로직 구현 MoneyTest.java - 0 및 양수 입력 시 정상 생성 테스트 추가 - 음수 입력 시 예외 발생 테스트 추가
Money.java - 필드명을 value → amount로 변경하여 의미 명확화 - 동일 금액 비교를 위한 isSameAs 메서드 추가 MoneyTest.java - isSameAs에 대한 비교 테스트(CsvSource 기반) 추가 - 동일 금액/상이한 금액에 대한 기대 결과 검증
ImageDimensionTest.java - 테스트 메서드명을 `생성자_*` 형태로 일관되게 변경 ImageFileTest.java - 테스트 메서드명을 `생성자_*` 형태로 일관되게 변경 SessionStatusTest.java - canEnroll 검증 메서드명을 `canEnroll_*` 형태로 명확하게 수정
Session.java - Session 엔티티 신규 추가 - 기본 생성 시 상태를 PREPARING으로 설정 - 수강신청 가능 여부를 SessionStatus에 위임하여 canEnroll 구현 SessionTest.java - 정상 생성 테스트 추가 - 기본 상태가 PREPARING인지 검증
FreeSession.java - Session을 상속한 무료 세션 도메인 추가 - 별도 제약 없이 기본 생성자만 제공 PaidSession.java - Session 확장하여 유료 세션 구현 - 최대 수강 인원(maxEnrollment) 및 수강료(fee) 보유 - maxEnrollment는 1명 이상이어야 하도록 검증 로직 추가 PaidSessionTest.java - 정상 생성 테스트 추가 - 최대 수강 인원이 0명 이하일 경우 예외 발생 테스트 추가
Session.java - Session 클래스를 abstract로 변경하여 FreeSession/PaidSession만 생성되도록 구조 개선 FreeSessionTest.java - 기존 SessionTest를 FreeSessionTest로 변경 - FreeSession 생성 테스트로 수정 - 기본 상태가 PREPARING인지 검증 PaidSessionTest.java - 정상 생성 테스트에서 객체 생성 후 기본 상태 PREPARING 검증하도록 수정
Enrollment.java - 수강신청 정보(id, studentId, createdAt)를 표현하는 엔티티 추가 - 기본 생성 시 id=0L로 초기화하는 생성자 제공 Enrollments.java - Enrollment 리스트를 관리하는 도메인 컬렉션 추가 - 불변 리스트 복사 후 add 메서드로 수강신청 추가 기능 제공
Enrollment.java - sessionId 필드 추가 - 생성자에 sessionId 포함하도록 변경 Session.java - Enrollments 컬렉션 필드 추가 - enroll(enrollment) 메서드 도입 - 모집 상태가 아닐 경우 예외 발생하도록 validateEnrollment 구현 - 기본 생성자는 빈 Enrollments로 초기화하도록 변경 SessionTest.java - 기본 생성 시 상태가 PREPARING인지 검증 - 모집중이 아닐 때 enroll 호출 시 예외 발생 테스트 추가
Enrollments.java - 내부 리스트명을 values로 변경 - count() 메서드 추가로 총 신청 인원 조회 가능 FreeSession.java - 상태를 지정해 생성할 수 있는 생성자 추가 Session.java - 상태 지정 생성자 추가 - enrollmentCount()로 신청 인원 조회 기능 제공 - enroll 시 모집중 상태가 아니면 예외 발생하도록 유지 SessionTest.java - 모집중(enrolling)일 때 수강신청 성공 테스트 추가 - PREPARING/CLOSED 상태에서 예외 발생하는지 파라미터화 테스트로 검증
PaidSession.java - 상태 지정 생성자 추가 - enroll(enrollment, payment) 메서드로 결제 금액 일치 여부 검증 후 부모 enroll 호출 - 결제 불일치 시 예외 발생하도록 validatePayment 추가 Session.java - enroll 시 상태 검증 메서드명을 validateStatus로 변경하여 의미 명확화 FreeSessionTest.java - FreeSession 정상 생성 테스트 명확화 PaidSessionTest.java - 공통 객체 setUp으로 정리 - 정상 enroll 테스트 추가 - 결제 금액 불일치 시 예외 발생 테스트 추가
PaidSession.java - enroll 시 결제 검증 후 정원 초과 여부 validateCapacity 추가 - 정원 도달 시 IllegalStateException 발생 PaidSessionTest.java - 최대 정원 초과 시 예외 발생 테스트 추가
Course.java - generation 필드 제거 - generation을 받던 생성자 삭제 및 기존 생성자 시그니처 정리 - findById 매핑 로직에서 generation 관련 코드 제거 JdbcCourseRepository.java - insert 쿼리에서 generation 제거 - select 쿼리 컬럼 목록에서 generation 제거 - RowMapper 매핑 인덱스 수정 schema.sql - course 테이블에서 generation 컬럼 삭제 CourseTest.java - 생성자 변경에 맞게 테스트 파라미터 수정 CourseRepositoryTest.java - generation 비교 제거 - 변경된 생성자 사용하도록 수정
Course.java - Sessions 컬렉션 필드 추가 - 기본 생성자 제거 및 sessions 초기화 포함한 생성자 구조로 변경 - 신규 생성자에서 Sessions를 주입받도록 확장 - 기존 필드 초기화 로직 정리 Sessions.java - Session 리스트를 보관하는 컬렉션 VO 추가 - add(), count() 기능 제공 - 기본 생성 시 빈 리스트로 초기화 JdbcCourseRepository.java - RowMapper에서 sessions는 아직 영속화 대상이 아니므로 null로 매핑 처리
Course.java - addSession() 메서드 추가로 코스에 세션 등록 기능 제공 - sessionCount() 메서드로 세션 수 조회 기능 추가 PaidSession.java - 상태 지정 생성자에서 maxEnrollment 유효성 검증 누락 문제를 수정하여 validateMaxEnrollment 호출 추가 Sessions.java - 내부 리스트를 방어적 복사(new ArrayList<>(values))로 초기화해 불변성 강화
PaidSession.java - maxEnrollment → capacity로 필드명 변경 - fee → price로 필드명 변경 - 생성자 파라미터 및 필드 초기화 로직을 새로운 용어에 맞게 수정 - 결제 검증 로직에서 fee → price로 업데이트 - 정원 검증 로직에서 maxEnrollment → capacity로 일관성 유지
Capacity.java - 최대 수강 인원을 표현하는 VO 추가 - 1명 이상인지 검증하는 로직 포함 - 0 이하 입력 시 IllegalArgumentException 발생 CapacityTest.java - 정상 생성 테스트 추가 - 0 이하 입력 시 예외 발생 테스트 추가
Capacity.java - isOver(currentCount) 메서드 추가로 현재 인원 대비 정원 초과 여부 판단 기능 제공 CapacityTest.java - isOver에 대한 파라미터화 테스트(CsvSource) 추가 - 정원 == 현재 인원 → 초과(true), 현재 인원 < 정원 → 미초과(false) 검증
Capacity.java - getValue() 추가하여 정원 출력 가능하도록 확장 PaidSession.java - 정원(capacity)와 수강료(price)를 VO(Capacity, Money)로 교체 - 기본 생성자에서 int 입력을 받아 VO로 변환하도록 변경 - 상태지정 생성자 역시 VO 기반 생성자로 연결 - validateCapacity에서 Capacity.isOver 사용하도록 변경 - 오류 메시지에서 capacity.getValue() 활용 PaidSessionTest.java - 생성자 테스트를 VO 적용 후 시그니처 변경에 맞게 수정 - fee 제거 후 price 정수 기반 생성 구조로 변경 - enroll 관련 테스트 모두 업데이트된 시그니처에 맞게 수정
Course.java - createdAt, updatedAt 필드를 final로 변경하여 생성 시점 이후 변경 불가하도록 개선 Enrollment.java - id, sessionId, studentId, createdAt 필드를 final로 변경해 불변 객체로 전환 - 생성자 외에는 상태가 변하지 않도록 구조 명확화
Session.java - id, coverImage, period, status 필드를 final로 변경하여 불변성 강화 - 생성 후 변경 불가능한 도메인 모델로 구조 개선 CapacityTest.java - 불필요한 주석 제거로 테스트 코드 간결화
- 규칙 7(인스턴스 변수 3개 제한)을 명시적으로 문서에 포함해 설계 기준 강화 - PR 전 점검 섹션 추가 및 체크리스트 파일 링크 연결 - 전체 도메인 기능 구현 목록을 항목별로 정리해 진행 상황을 명확하게 표현 - Course, Session, Value Objects, 일급 컬렉션, Enum 등 모든 구성 요소의 요구 기능을 체크리스트 형태로 문서화
Enrollment.java - studentId 조회를 위한 getter 추가 Enrollments.java - add 시 중복 수강 신청 여부 검증 추가 - 동일 studentId 존재 시 예외 발생 처리 EnrollmentsTest.java - 정상 추가 테스트 작성 - 중복 신청 시 예외 검증 테스트 추가
FreeSession.java - 결제/정원 정책 메서드 빈 구현 추가(무료 세션 특성 반영) Money.java - ZERO 상수 추가로 무료 결제 표현 단순화 PaidSession.java - 결제 검증을 validatePaymentPolicy로 이관 - 정원 검증을 validateCapacityPolicy로 이관 Session.java - enroll 메서드 final 처리 및 템플릿 메서드 패턴 적용 - validatePaymentPolicy, validateCapacityPolicy 추상 메서드 정의 SessionTest.java - 무료 세션 enroll 테스트에서 Money.ZERO 사용하도록 수정 - 상태 검증 테스트도 동일하게 Money.ZERO 적용
FreeSessionTest.java - 공통 픽스처 setUp 추가 - 모집중 수강 신청 성공 테스트 추가 - 상태별(준비중/종료) 수강 신청 실패 테스트 추가 - 기존 생성자 검증 로직 유지 및 보강 SessionTest.java - FreeSessionTest로 통합됨에 따라 클래스 삭제
FreeSessionTest - 생성자 검증을 한 줄로 축약하여 가독성 개선 PaidSessionTest - 생성자 검증을 한 줄 표현으로 간결화 - 정상 수강 신청 테스트에서 payment 변수 제거 → 즉시 new Money() 전달 - 모집중이 아닐 때 실패 테스트 추가 (`EnumSource` 활용)
02-lms-domain-model.md - Session을 추상 클래스 + 템플릿 메서드 패턴 적용 방식으로 상세화 - FreeSession/PaidSession의 정책 검증 구조와 구현 방식 설명 추가 - Enrollments의 중복 수강 신청 검증 항목 추가
javajigi
left a comment
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.
2단계 객체 설계 넘 잘 했네요. 💯
템플릿 메서드 패턴 적용도 👍
바로 merge하려다 pr 본문에 있는 고민한 지점과 관련한 제안을 하나 해봤는데요.
db 매핑하기 전인 지금 단계에서 고민해 보면 좋겠다는 생각이 들어 request changes로 피드백해요.
| boolean exists = values.stream().anyMatch(e -> e.getStudentId().equals(enrollment.getStudentId())); | ||
| if (exists) { |
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.
| boolean exists = values.stream().anyMatch(e -> e.getStudentId().equals(enrollment.getStudentId())); | |
| if (exists) { | |
| if (values.contains(enrollment)) { |
위와 같이 구현할 수 있지 않을까?
| @@ -0,0 +1,51 @@ | |||
| package nextstep.courses.domain; | |||
|
|
|||
| public abstract class Session { | |||
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.
템플릿 메서드 패턴 적용 👍
현재 방식도 좋은데 Session의 필드 수가 많아지는 점을 고려해 봤을 때 다음과 같이 설계해 보는 것은 어떨까?
Session 객체는 현재와 같이 강의와 관련하 전체 필드를 가지는 객체로..
현재 Session에서 템플릿 메서드 패턴으로 구현한 부분(수강 신청 가능 여부를 판단하는)만 분리해 보면 어떨까?
예를 들어
Session -> 수강신청 담당 부모 객체(추상 클래스) 에 의존
수강신청 담당 부모 객체(추상 클래스) - 템플릿 메서드
-> 무료 강의에 대한 수강 신청 담당
-> 유료 강의에 대한 수강 신청 담당
한번 고민해 보면 좋겠다는 생각이 들어 피드백 남겨봄
Enrollments.java - 중복 검증을 stream anyMatch → values.contains(enrollment) 방식으로 단순화
EnrollmentPolicy.java - 템플릿 메서드 패턴 기반의 공통 검증 구조 도입 - 결제·정원 검증 메서드를 추상화하여 정책별 구현 가능하도록 설계 FreeEnrollmentPolicy.java - 무료 강의용 정책 구현 - 결제·정원 검증을 비활성화 - FREE 타입 반환하도록 구현 SessionType.java - FREE/PAID 세션 타입 정의 및 설명 필드 추가 FreeEnrollmentPolicyTest.java - 무료 정책 검증 시 항상 성공하는지 테스트 추가 - 정책 타입이 FREE인지 검증하는 테스트 추가
PaidEnrollmentPolicy.java - 유료 강의용 EnrollmentPolicy 구현 - 결제 금액 검증 로직 추가 (price.isSameAs) - 정원 초과 검증 로직 추가 (capacity.isOver) - PAID 타입 반환 PaidEnrollmentPolicyTest.java - 정상 결제·정원 입력 시 성공 테스트 추가 - 결제 금액 불일치 시 예외 발생 테스트 추가 - 정원 초과 시 예외 발생 테스트 추가 - PAID 타입 반환 검증
FreeSession.java / PaidSession.java - Session 하위 구현 제거 - 정책 기반 구조로 대체됨에 따라 클래스 삭제 Session.java - 추상 클래스 → 단일 클래스로 변경 - EnrollmentPolicy 의존성 추가하여 결제·정원 검증을 정책에 위임 - enroll 로직에서 상태 검증 후 정책 검증 호출하도록 통합 - getType() 추가하여 세션 타입 조회 가능 PaidSessionTest.java - 정책 기반 구조로 변경됨에 따라 기존 PaidSession 테스트 삭제 SessionTest.java - FreeSessionTest → SessionTest로 변경 - Session + FreeEnrollmentPolicy 조합으로 생성/수강 신청 테스트하도록 수정 - 모집 상태 검증 로직 유지
피드백 반영고민한 구조들1. 기존 구조: Session 상속 (FreeSession / PaidSession)장점
단점
2. 정책 인터페이스 (EnrollmentPolicy interface)장점
단점
3. 정책 추상 클래스 (EnrollmentPolicy abstract class) - 최종 선택장점
단점
비교 정리
최종 결정: 정책 추상 클래스선택 이유
피드백 덕분에 세 가지 구조의 트레이드오프를 깊이 고민해볼 수 있었습니다. 감사합니다! |
02-lms-domain-model.md - Session을 추상 클래스 기반 → 정책 위임 방식으로 변경 - EnrollmentPolicy 중심 구조(템플릿 메서드) 설명 추가 - Free/Paid 정책의 역할 및 검증 규칙 명확히 정리 - Session 타입 조회가 Policy 반환값 기반임을 명시
javajigi
left a comment
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.
아니 이렇게 빠르게 피드백 반영하다니 👍
출근 안하시나요? ㅋㅋㅋㅋ
빠르게 피드백 반영한 부분도 인상적이지만 각각의 장단점을 파악한 후에 최종 결정하고 본인의 의지에 따라 피드백을 반영한 부분이 더 인상적이네요.
도메인 객체를 작은 단위로 분리하다보니 domain 패키지에 너무 많은 객체가 존재하는 것 같아요.
3단계 진행할 때 패키지 분리도 도전해 보면 어떨까요?
3단계 db 매핑은 최대한 현재 도메인 객체 구조를 유지하면서 매핑에 성공하기를 바래 보겠습니다.
|
ㅋㅋㅋㅋ 회사에 7시10분에 도착해서 카페에서 개인공부중이었습니다 피드백감사합니다! |
수강신청(도메인모델) 과제 제출
안녕하세요!
수강신청(도메인모델) 과제 제출합니다.
미션 진행 방식
바텀업(Bottom-Up) 설계로 진행했습니다.
DB 테이블이 아닌 도메인 모델부터 시작하라는 요구사항에 따라,
가장 작은 단위의 VO부터 설계하고 점진적으로 상위 객체를 조립하는 방식으로 진행했습니다.
설계 의도
이번 구현에서는 Session 도메인을 추상 클래스 기반 + 템플릿 메서드 패턴으로 설계했습니다.
수강 신청 기능은 “무료/유료 여부와 관계없이 동일하게 따라야 하는 공통 흐름”이 존재하기 때문에,
public final void enroll(...)메서드 안에서 아래 절차를 단일한 흐름으로 고정하고,세부 정책만 하위 클래스에 위임하는 구조를 채택했습니다.
이를 통해 수강신청의 핵심 비즈니스 규칙이 상위 클래스에서 안전하게 통제되며,
하위 클래스는 정말 필요한 정책 부분만 구현하도록 단순화되었습니다.
또한
Session은 period, status, coverImage, enrollments 등모든 Session이 공통적으로 가져야 하는 필드가 있기 때문에,
필드/생성자를 보유할 수 없는 인터페이스보다 추상 클래스가 도메인 표현에 더 적합하다고 판단했습니다.
고민한 지점
무료/유료 외에도 세션 유형이 다양해지거나,
결제 정책/정원 정책 로직이 동적으로 교체되는 요구사항이 생긴다면
현재 구조는 하위 클래스가 많아질 수 있습니다.
실제 서비스라면 결제 정책과 정원 정책을 별도 Policy 객체로 분리해
전략 패턴 기반으로 조립하는 구조도 고려할 수 있습니다
다만 현재 과제 범위에서는
“명확히 구분되는 두 종류의 세션(유료/무료)”을 가장 자연스럽고 일관되게 표현하기 위해
현재 구조가 적절한 선택이라고 판단했습니다.
배운 점
리뷰 잘 부탁드립니다!