-
Notifications
You must be signed in to change notification settings - Fork 308
2단계 - 수강신청(도메인 모델) #806
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단계 - 수강신청(도메인 모델) #806
Changes from all commits
94f5920
2dadcc4
c1cff29
00ac3bd
48e786b
89f625d
1f5d6ad
631f0de
928f9d4
ed462e2
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,7 @@ | ||
| package nextstep.courses; | ||
|
|
||
| public class InvalidImageFileException extends RuntimeException { | ||
| public InvalidImageFileException(String message) { | ||
| super(message); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package nextstep.courses; | ||
|
|
||
| public class SessionUnregistrableException extends RuntimeException{ | ||
| public SessionUnregistrableException(){ | ||
| } | ||
|
|
||
| public SessionUnregistrableException(String message) { | ||
| super(message); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package nextstep.courses.domain; | ||
|
|
||
| import nextstep.courses.domain.session.Period; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| public class BaseEntity { | ||
| private final Long id; | ||
| private final String title; | ||
| private final Period period; | ||
|
|
||
| public BaseEntity(Long id, String title, LocalDate startDate, LocalDate endDate) { | ||
| this(id, title, new Period(startDate, endDate)); | ||
| } | ||
|
|
||
| public BaseEntity(Long id, String title, Period period) { | ||
| this.id = id; | ||
| this.title = title; | ||
| this.period = period; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package nextstep.courses.domain; | ||
| package nextstep.courses.domain.course; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package nextstep.courses.domain.image; | ||
|
|
||
| public class Image { | ||
| private final ImageSize size; | ||
| private final ImageDimension imageDimension; | ||
| private final ImageFileExtension fileExtension; | ||
|
|
||
| public Image(int size, int width, int height, String fileExtension) { | ||
| this.size = new ImageSize(size); | ||
| this.imageDimension = new ImageDimension(width, height); | ||
| this.fileExtension = ImageFileExtension.from(fileExtension); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package nextstep.courses.domain.image; | ||
|
|
||
| import nextstep.courses.InvalidImageFileException; | ||
|
|
||
| public class ImageDimension { | ||
| private final static int MIN_WIDTH = 300; // 300 px | ||
| private final static int MIN_HEIGHT = 200; // 200 px | ||
| private final static double ASPECT_RATIO = 1.5; // width : height = 3 : 2 | ||
|
|
||
| private final int width; | ||
| private final int height; | ||
|
|
||
| public ImageDimension(int width, int height) { | ||
| if (!isValidWidthAndHeight(width, height)) { | ||
| throw new InvalidImageFileException(String.format("이미지는 가로 %spx 이상, 세로 %spx 이상, 가로 세로 비율이 %s:1 이어야 합니다.", MIN_WIDTH, MIN_HEIGHT, ASPECT_RATIO)); | ||
| } | ||
| this.width = width; | ||
| this.height = height; | ||
| } | ||
|
|
||
| private boolean isValidWidthAndHeight(int width, int height) { | ||
| return width >= MIN_WIDTH && height >= MIN_HEIGHT && isValidRatio(width, height); | ||
| } | ||
|
|
||
| private boolean isValidRatio(int width, int height) { | ||
| return (double) width / height == ASPECT_RATIO; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package nextstep.courses.domain.image; | ||
|
|
||
| import nextstep.courses.InvalidImageFileException; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public enum ImageFileExtension { | ||
| GIF("gif"), | ||
| JPG("jpg"), | ||
| JPEG("jpeg"), | ||
| PNG("png"), | ||
| SVG("svg"); | ||
|
|
||
| private final String value; | ||
|
|
||
| ImageFileExtension(String value) { | ||
| this.value = value; | ||
| } | ||
|
|
||
| private String value() { | ||
| return value; | ||
| } | ||
|
|
||
| static ImageFileExtension from(String value) { | ||
| return Arrays.stream(ImageFileExtension.values()) | ||
| .filter(fileExtension -> fileExtension.value.equals(value)) | ||
| .findFirst() | ||
| .orElseThrow(() -> new InvalidImageFileException(String.format("이미지 파일은 %s 중에 하나의 형태여야 합니다.", possibleFileExtensions()))); | ||
| } | ||
|
|
||
| private static String possibleFileExtensions() { | ||
| return Arrays.stream(ImageFileExtension.values()) | ||
| .map(ImageFileExtension::value) | ||
| .collect(Collectors.joining(", ")); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package nextstep.courses.domain.image; | ||
|
|
||
| import nextstep.courses.InvalidImageFileException; | ||
|
|
||
| class ImageSize { | ||
| private final static int MAX_SIZE = 1_024; // 1024 Byte | ||
| private final int size; | ||
|
|
||
| ImageSize(int size) { | ||
| if (!isValidSize(size)) { | ||
| throw new InvalidImageFileException(String.format("이미지 사이즈는 %sB 이하여야 합니다.", MAX_SIZE)); | ||
| } | ||
| this.size = size; | ||
| } | ||
|
|
||
| private boolean isValidSize(int size) { | ||
| return size <= MAX_SIZE; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package nextstep.courses.domain.session; | ||
|
|
||
| public class Capacity { | ||
| private final static int UNLIMITED = -1; | ||
| private final int capacity; | ||
|
|
||
| public Capacity() { | ||
| this(UNLIMITED); | ||
| } | ||
|
|
||
| public Capacity(int capacity) { | ||
| this.capacity = capacity; | ||
| } | ||
|
|
||
| public boolean isUnlimited() { | ||
| return capacity == UNLIMITED; | ||
| } | ||
|
|
||
| public boolean isGreaterThan(int value) { | ||
| return capacity > value; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package nextstep.courses.domain.session; | ||
|
|
||
| import nextstep.payments.domain.Payment; | ||
| import nextstep.users.domain.NsUser; | ||
|
|
||
| /** | ||
| * 개인별 수강 신청 가능 조건 값을 관리하는 도메인 | ||
| */ | ||
| public class EnrollmentCondition { | ||
| private final NsUser user; | ||
| private final Payment payment; | ||
|
|
||
| public EnrollmentCondition(NsUser user, Payment payment) { | ||
| if (!paidBySameUser(user, payment)) { | ||
| throw new IllegalArgumentException("수강 신청자와 결제자 정보가 불일치합니다."); | ||
| } | ||
| this.user = user; | ||
| this.payment = payment; | ||
| } | ||
|
|
||
| private boolean paidBySameUser(NsUser user, Payment payment) { | ||
| return payment.isPaidBy(user.getId()); | ||
| } | ||
|
|
||
| public NsUser getUser() { | ||
| return user; | ||
| } | ||
|
|
||
| public boolean hasPaid(Long sessionId, long price) { | ||
| return payment.isPaidFor(sessionId) && payment.isSameAmount(price); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package nextstep.courses.domain.session; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| public class Period { | ||
| private final LocalDate startDate; | ||
| private final LocalDate endDate; | ||
|
|
||
| public Period(LocalDate startDate, LocalDate endDate) { | ||
| this.startDate = startDate; | ||
| this.endDate = endDate; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package nextstep.courses.domain.session; | ||
|
|
||
| import nextstep.courses.SessionUnregistrableException; | ||
| import nextstep.courses.domain.*; | ||
| import nextstep.courses.domain.image.Image; | ||
| import nextstep.courses.domain.session.type.Free; | ||
| import nextstep.courses.domain.session.type.Paid; | ||
| import nextstep.courses.domain.session.type.SessionType; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| public class Session { | ||
| private final BaseEntity baseEntity; | ||
| private final Image image; | ||
| private SessionStatus status; | ||
| private final SessionType type; | ||
|
|
||
| public Session(Long id, String title, Image image, SessionStatus status, SessionType type, LocalDate startDate, LocalDate endDate) { | ||
| this.baseEntity = new BaseEntity(id, title, new Period(startDate, endDate)); | ||
| this.image = image; | ||
| this.status = status; | ||
| this.type = type; | ||
| } | ||
|
|
||
| public void openRecruiting() { | ||
| this.status = SessionStatus.RECRUITING; | ||
| } | ||
|
|
||
| public boolean isRecruiting() { | ||
| return this.status == SessionStatus.RECRUITING; | ||
| } | ||
|
|
||
| public boolean canEnroll(EnrollmentCondition request) { | ||
| return this.type.canEnroll(request); | ||
| } | ||
|
|
||
| public String currentStatusToHumanReadable() { | ||
| return this.status.name(); | ||
| } | ||
|
|
||
| public String typeToHumanReadable() { | ||
| return this.type.toHumanReadableTypeName(); | ||
| } | ||
|
|
||
| public static Session createFreeSession(String title, Image image) { | ||
| return new Session(null, title, image, SessionStatus.PREPARING, new Free(), LocalDate.now(), LocalDate.MAX); | ||
| } | ||
|
|
||
| public static Session createPaidSession(long id, String title, Image image, long price) { | ||
| return new Session(null, title, image, SessionStatus.PREPARING, new Paid(id, price), LocalDate.now(), LocalDate.MAX); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package nextstep.courses.domain.session; | ||
|
|
||
| import nextstep.courses.SessionUnregistrableException; | ||
| import nextstep.users.domain.NsUsers; | ||
|
|
||
| /** | ||
| * 강의 정원 및 현재 등록 인원을 관리하는 도메인 | ||
| */ | ||
| public class SessionEnrollment { | ||
|
Contributor
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. 👍 |
||
| private final Capacity maxCapacity; | ||
| private final NsUsers enrolledUsers; | ||
| private final Session session; | ||
|
Contributor
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. Session과 의존관계를 가지기보다 수강 신청 가능 여부를 판단할 때 필요한 아래 두 값을 가지도록 구현하는 것은 어떨까? Session에 의존할 경우 테스트 코드 구현할 때 Session 객체를 생성하는 등의 번거로운 작업도 많을 것으로 보여짐 |
||
|
|
||
| public SessionEnrollment(Session session) { | ||
| this(new Capacity(), new NsUsers(), session); | ||
| } | ||
|
|
||
| public SessionEnrollment(int maxCapacity, Session session) { | ||
| this(new Capacity(maxCapacity), new NsUsers(), session); | ||
| } | ||
|
|
||
| public SessionEnrollment(Capacity maxCapacity, NsUsers enrolledUsers, Session session) { | ||
| this.maxCapacity = maxCapacity; | ||
| this.enrolledUsers = enrolledUsers; | ||
| this.session = session; | ||
| } | ||
|
|
||
| public void enroll(EnrollmentCondition condition) { | ||
| checkEnrollable(condition); | ||
| this.enrolledUsers.addUser(condition.getUser()); | ||
| } | ||
|
|
||
| private void checkEnrollable(EnrollmentCondition condition) { | ||
| if (!session.isRecruiting()) { | ||
| throw new SessionUnregistrableException(String.format("%s 상태인 강의는 수강신청할 수 없습니다.", session.currentStatusToHumanReadable())); | ||
| } | ||
|
|
||
| if (!session.canEnroll(condition)) { | ||
| throw new SessionUnregistrableException(String.format("%s 강의 수강 신청 조건 미달로 신청할 수 없습니다.", session.typeToHumanReadable())); | ||
| } | ||
|
|
||
| if (!hasCapacity()) { | ||
| throw new SessionUnregistrableException("정원 초과로 수강신청할 수 없습니다."); | ||
| } | ||
| } | ||
|
|
||
| private boolean hasCapacity() { | ||
| if (maxCapacity.isUnlimited()) { | ||
| return true; | ||
| } | ||
| return enrolledUsers.isLessThan(maxCapacity); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package nextstep.courses.domain.session; | ||
|
|
||
| public enum SessionStatus { | ||
| PREPARING, RECRUITING, COMPLETED | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package nextstep.courses.domain.session.type; | ||
|
|
||
| import nextstep.courses.domain.session.EnrollmentCondition; | ||
|
|
||
| public class Free implements SessionType { | ||
| public Free() { | ||
| } | ||
|
|
||
| @Override | ||
| public boolean canEnroll(EnrollmentCondition request) { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public String toHumanReadableTypeName() { | ||
| return "무료"; | ||
| } | ||
| } |
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.
👍