Skip to content
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단계 - 수강신청(도메인 모델) #667

Merged
merged 6 commits into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/main/java/nextstep/courses/domain/Course.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public class Course {

private Long creatorId;

private final Sessions sessions = new Sessions();

private LocalDateTime createdAt;

private LocalDateTime updatedAt;
Expand Down Expand Up @@ -40,6 +42,11 @@ public LocalDateTime getCreatedAt() {
return createdAt;
}

public void addSession(Session session) {
session.toCourse(this);
sessions.add(session);
}

@Override
public String toString() {
return "Course{" +
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/nextstep/courses/domain/CoverImage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nextstep.courses.domain;

public class CoverImage {

private final CoverImageFileSize size;
private final CoverImageType type;
private final CoverImageResolution resolution;
Comment on lines +3 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


CoverImage(CoverImageType type, long size, int width, int height) {
this.size = new CoverImageFileSize(size);
this.type = type;
this.resolution = new CoverImageResolution(width, height);
}
}
9 changes: 9 additions & 0 deletions src/main/java/nextstep/courses/domain/CoverImageFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package nextstep.courses.domain;

public class CoverImageFactory {

public static CoverImage ofGif(long size, int width, int height) {
return new CoverImage(CoverImageType.GIF, size, width, height);
}

}
21 changes: 21 additions & 0 deletions src/main/java/nextstep/courses/domain/CoverImageFileSize.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package nextstep.courses.domain;

public class CoverImageFileSize {
private static final long MAX_SIZE = 1024 * 1024; // 1MB
private final long size;

public CoverImageFileSize(long size) {
validate(size);
this.size = size;
}

private static void validate(long size) {
if (size <= 0 || size > MAX_SIZE) {
throw new IllegalArgumentException("이미지 크기는 0보다 크고 1MB 이하여야 합니다.");
}
}

public long getSize() {
return size;
}
}
38 changes: 38 additions & 0 deletions src/main/java/nextstep/courses/domain/CoverImageResolution.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package nextstep.courses.domain;

public class CoverImageResolution {
private static final int MIN_WIDTH = 300;
private static final int MIN_HEIGHT = 200;
private static final int ASPECT_RATIO_WIDTH = 3;
private static final int ASPECT_RATIO_HEIGHT = 2;
Comment on lines +3 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미지의 width는 300픽셀, height는 200픽셀 이상이어야 하며, width와 height의 비율은 3:2여야 한다. 👍


private final int width;
private final int height;

public CoverImageResolution(int width, int height) {
validate(width, height);
this.width = width;
this.height = height;
}

private static void validate(int width, int height) {
validateWidthAndHeight(width, height);
validateAspectRatioValid(width, height);
}

private static void validateWidthAndHeight(int width, int height) {
boolean isValid = width >= MIN_WIDTH && height >= MIN_HEIGHT;

if (!isValid) {
throw new IllegalArgumentException("이미지의 width는 300픽셀, height는 200픽셀 이상이어야 합니다.");
}
}

private static void validateAspectRatioValid(int width, int height) {
boolean isValid = width * ASPECT_RATIO_HEIGHT == height * ASPECT_RATIO_WIDTH;

if (!isValid) {
throw new IllegalArgumentException("width와 height의 비율은 3:2여야 합니다.");
}
}
Comment on lines +18 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
8 changes: 8 additions & 0 deletions src/main/java/nextstep/courses/domain/CoverImageType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nextstep.courses.domain;

public enum CoverImageType {
GIF,
JPG,
PNG,
SVG
}
Comment on lines +3 to +8
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enum 👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package nextstep.courses.domain;

public class FreeRegistrationPolicy implements RegistrationPolicy {

@Override
public void validateRegistration(Session session, Money paymentAmount) {
// 무조건 패스
}
}
28 changes: 28 additions & 0 deletions src/main/java/nextstep/courses/domain/Money.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package nextstep.courses.domain;

import java.util.Objects;

public class Money {
private final NaturalNumber value;
Comment on lines +5 to +6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

규칙 3: 모든 원시값과 문자열을 포장한다.

원시값포장을 전부 하셨군요 👍


public Money(long amount) {
this.value = new NaturalNumber(amount);
}

public long getAmount() {
return value.getValue();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return Objects.equals(value, money.value);
}

@Override
public int hashCode() {
return Objects.hashCode(value);
}
}
50 changes: 50 additions & 0 deletions src/main/java/nextstep/courses/domain/NaturalNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package nextstep.courses.domain;

import java.util.Objects;

public class NaturalNumber implements Comparable<NaturalNumber> {

private final long value;

public NaturalNumber(long value) {
validate(value);
this.value = value;
}

private static void validate(long value) {
if (value < 0) {
throw new IllegalArgumentException("0을 포함한 자연수만 허용 가능합니다.");
}
}

public long getValue() {
return value;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NaturalNumber that = (NaturalNumber) o;
return value == that.value;
}

@Override
public int hashCode() {
return Objects.hashCode(value);
}

@Override
public String toString() {
return getValue() + "";
}

@Override
public int compareTo(NaturalNumber o) {
return Long.compare(getValue(), o.getValue());
}

public int compareTo(int o) {
return compareTo(new NaturalNumber(o));
}
}
24 changes: 24 additions & 0 deletions src/main/java/nextstep/courses/domain/PaidRegistrationPolicy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package nextstep.courses.domain;

public class PaidRegistrationPolicy implements RegistrationPolicy {

private final Money sessionFee;
private final NaturalNumber maxStudentCount;

public PaidRegistrationPolicy(int sessionFee, int maxStudentCount) {
this.sessionFee = new Money(sessionFee);
this.maxStudentCount = new NaturalNumber(maxStudentCount);
}

@Override
public void validateRegistration(Session session, Money paymentAmount) {
if (!session.isStudentCountLessThan((int) maxStudentCount.getValue())) {
throw new IllegalArgumentException("강의 최대 수강 인원을 초과할 수 없습니다.");
}
Comment on lines +15 to +17
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그래서.. canAcceptMoreStudents과 같은 추상적인 이름을 가지는것 보다는 isStudentCountLessThan와 같은 구체적인 이름으로 작성하여 캡슐화의 목적도 이루고 RegistrationPolicy가 해당 책임을 가져가게끔 처리할 수 있을 것 같습니다.
이견 있으시면 말씀주세요.

동의합니다 👍


if (!sessionFee.equals(paymentAmount)) {
throw new IllegalArgumentException("수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능합니다.");
}
}

}
5 changes: 5 additions & 0 deletions src/main/java/nextstep/courses/domain/RegistrationPolicy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package nextstep.courses.domain;

public interface RegistrationPolicy {
void validateRegistration(Session session, Money paymentAmount);
}
Comment on lines +3 to +5
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유/무료 정책을 이렇게 구현해주셨군요 👍 👏

45 changes: 45 additions & 0 deletions src/main/java/nextstep/courses/domain/Session.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package nextstep.courses.domain;

import nextstep.payments.domain.Payment;
import nextstep.users.domain.NsUser;
import nextstep.users.domain.NsUsers;

public class Session {
private Long id;
private Course course;
private final NsUsers nsUsers = new NsUsers();
private CoverImage coverImage;
private SessionStatus sessionStatus;
private RegistrationPolicy registrationPolicy;
private SessionPeriod sessionPeriod;

Session(long id, CoverImage coverImage, SessionStatus sessionStatus, RegistrationPolicy registrationPolicy, SessionPeriod sessionPeriod) {
this.id = id;
this.coverImage = coverImage;
this.sessionStatus = sessionStatus;
this.registrationPolicy = registrationPolicy;
this.sessionPeriod = sessionPeriod;
}

public void toCourse(Course course) {
this.course = course;
}

public boolean isStudentCountLessThan(int count) {
return nsUsers.getSize() < count;
}

public Payment register(NsUser nsUser, Money paymentAmount) {
if (!sessionStatus.isRegistrable()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

throw new IllegalStateException("수강신청이 불가능한 상태입니다.");
}

registrationPolicy.validateRegistration(this, paymentAmount);

nsUsers.add(nsUser);

return new Payment("", this, nsUser, paymentAmount);
}


}
15 changes: 15 additions & 0 deletions src/main/java/nextstep/courses/domain/SessionPeriod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package nextstep.courses.domain;

import java.time.LocalDateTime;

public class SessionPeriod {

private final LocalDateTime startedAt;
private final LocalDateTime endedAt;

public SessionPeriod(LocalDateTime startedAt, LocalDateTime endedAt) {
this.startedAt = startedAt;
this.endedAt = endedAt;
}

}
11 changes: 11 additions & 0 deletions src/main/java/nextstep/courses/domain/SessionStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.courses.domain;

public enum SessionStatus {
PREPARING,
RECRUITING,
ENDED;

public boolean isRegistrable() {
return this == RECRUITING;
}
}
24 changes: 24 additions & 0 deletions src/main/java/nextstep/courses/domain/Sessions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package nextstep.courses.domain;

import java.util.ArrayList;
import java.util.List;

public class Sessions {
private final List<Session> sessions;

public Sessions(List<Session> sessions) {
this.sessions = sessions;
}

public Sessions() {
this(new ArrayList<>());
}

public void add(Session session) {
sessions.add(session);
}

public List<Session> getSessions() {
return sessions;
}
}
32 changes: 24 additions & 8 deletions src/main/java/nextstep/payments/domain/Payment.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,44 @@

import java.time.LocalDateTime;

import nextstep.courses.domain.Money;
import nextstep.courses.domain.Session;
import nextstep.users.domain.NsUser;

public class Payment {
private String id;

// 결제한 강의 아이디
private Long sessionId;
// 결제한 강의
private Session session;

// 결제한 사용자 아이디
private Long nsUserId;
// 결제한 사용자
private NsUser nsUser;

// 결제 금액
private Long amount;
private Money amount;

private LocalDateTime createdAt;

public Payment() {
}

public Payment(String id, Long sessionId, Long nsUserId, Long amount) {
public Payment(String id, Session session, NsUser nsUser, Money amount) {
this.id = id;
this.sessionId = sessionId;
this.nsUserId = nsUserId;
this.session = session;
this.nsUser = nsUser;
this.amount = amount;
this.createdAt = LocalDateTime.now();
}

public Session getSession() {
return session;
}

public NsUser getNsUser() {
return nsUser;
}

public Money getAmount() {
return amount;
}
}
2 changes: 1 addition & 1 deletion src/main/java/nextstep/qna/domain/Answer.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public DeleteHistory delete(NsUser loginUser) {
validateDelete(loginUser);
this.deleted = true;
this.updatedDate = LocalDateTime.now();
return new DeleteHistory(ContentType.ANSWER, getId(), getWriter(), LocalDateTime.now());
return DeleteHistoryFactory.ofAnswer(getId(), loginUser, LocalDateTime.now());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

@Override
Expand Down
Loading