From 5bc91bde370503e7e747d1158621608f8aeeb057 Mon Sep 17 00:00:00 2001 From: eunjeong Park Date: Sun, 7 Dec 2025 16:10:44 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=9A=80=203=EB=8B=A8=EA=B3=84=20-=20?= =?UTF-8?q?=EC=88=98=EA=B0=95=EC=8B=A0=EC=B2=AD(DB=20=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/image/SessionCoverImage.java | 30 +++- .../domain/image/SessionImageDimension.java | 1 + .../domain/image/SessionImageRepository.java | 9 ++ .../domain/registration/Registration.java | 25 ++- .../registration/RegistrationRepository.java | 13 ++ .../domain/registration/Registrations.java | 41 +++-- .../courses/domain/session/Enrollment.java | 14 +- .../courses/domain/session/Session.java | 48 ++++-- .../courses/domain/session/SessionPeriod.java | 8 + .../domain/session/SessionRepository.java | 10 ++ .../courses/domain/session/type/FreeType.java | 20 ++- .../courses/domain/session/type/PaidType.java | 40 +++-- .../domain/session/type/SessionType.java | 6 +- .../JdbcRegistrationRepository.java | 66 ++++++++ .../JdbcSessionImageRepository.java | 60 ++++++++ .../infrastructure/JdbcSessionRepository.java | 142 ++++++++++++++++++ .../entity/RegistrationEntity.java | 33 ++++ .../entity/SessionCoverImageEntity.java | 43 ++++++ .../infrastructure/entity/SessionEntity.java | 72 +++++++++ .../mapper/RegistrationMapper.java | 37 +++++ .../mapper/SessionCoverImageMapper.java | 34 +++++ .../infrastructure/mapper/SessionMapper.java | 71 +++++++++ .../courses/service/CourseManageService.java | 24 +++ .../courses/service/RegistrationService.java | 29 ++++ .../courses/service/SessionEnrollService.java | 16 -- .../courses/service/SessionManageService.java | 49 ++++++ src/main/resources/schema.sql | 33 ++++ .../domain/image/SessionCoverImageTest.java | 7 +- .../registration/RegistrationsTest.java | 40 +++++ .../domain/session/SessionBuilder.java | 27 ++-- .../courses/domain/session/SessionTest.java | 80 +++++----- .../domain/session/type/FreeTypeTest.java | 20 +-- .../domain/session/type/PaidTypeTest.java | 16 +- 33 files changed, 1017 insertions(+), 147 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/image/SessionImageRepository.java create mode 100644 src/main/java/nextstep/courses/domain/registration/RegistrationRepository.java create mode 100644 src/main/java/nextstep/courses/domain/session/SessionRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcRegistrationRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/entity/RegistrationEntity.java create mode 100644 src/main/java/nextstep/courses/infrastructure/entity/SessionCoverImageEntity.java create mode 100644 src/main/java/nextstep/courses/infrastructure/entity/SessionEntity.java create mode 100644 src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java create mode 100644 src/main/java/nextstep/courses/infrastructure/mapper/SessionCoverImageMapper.java create mode 100644 src/main/java/nextstep/courses/infrastructure/mapper/SessionMapper.java create mode 100644 src/main/java/nextstep/courses/service/CourseManageService.java create mode 100644 src/main/java/nextstep/courses/service/RegistrationService.java delete mode 100644 src/main/java/nextstep/courses/service/SessionEnrollService.java create mode 100644 src/main/java/nextstep/courses/service/SessionManageService.java create mode 100644 src/test/java/nextstep/courses/domain/registration/RegistrationsTest.java diff --git a/src/main/java/nextstep/courses/domain/image/SessionCoverImage.java b/src/main/java/nextstep/courses/domain/image/SessionCoverImage.java index 814e160962..9c8504e88d 100644 --- a/src/main/java/nextstep/courses/domain/image/SessionCoverImage.java +++ b/src/main/java/nextstep/courses/domain/image/SessionCoverImage.java @@ -1,17 +1,41 @@ package nextstep.courses.domain.image; public class SessionCoverImage { + private final Long id; + private final Long sessionId; private final SessionImageDimension dimension; private final SessionImageExtension extension; private final SessionImageCapacity capacity; - public SessionCoverImage(int width, int height, String extension, long bytes) { - this(new SessionImageDimension(width, height), SessionImageExtension.from(extension), new SessionImageCapacity(bytes)); + public SessionCoverImage(Long sessionId, int width, int height, String extension, long bytes) { + this(null, sessionId, new SessionImageDimension(width, height), SessionImageExtension.from(extension), new SessionImageCapacity(bytes)); } - public SessionCoverImage(SessionImageDimension dimension, SessionImageExtension extension, SessionImageCapacity capacity) { + public SessionCoverImage(Long id, Long sessionId, SessionImageDimension dimension, SessionImageExtension extension, SessionImageCapacity capacity) { + this.id = id; + this.sessionId = sessionId; this.dimension = dimension; this.extension = extension; this.capacity = capacity; } + + public Long getId() { + return id; + } + + public Long getSessionId() { + return sessionId; + } + + public SessionImageDimension getDimension() { + return dimension; + } + + public SessionImageExtension getExtension() { + return extension; + } + + public SessionImageCapacity getCapacity() { + return capacity; + } } diff --git a/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java b/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java index f033bb6351..b3bfe8163f 100644 --- a/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java +++ b/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java @@ -15,6 +15,7 @@ public SessionImageDimension(int width, int height) { this.width = width; this.height = height; } + private void validateMinLength(int width, int height){ if(!(width >= MIN_WIDTH && height >= MIN_HEIGHT)) { throw new IllegalArgumentException("이미지는 가로 300이상, 세로 200 이상이어야 합니다."); diff --git a/src/main/java/nextstep/courses/domain/image/SessionImageRepository.java b/src/main/java/nextstep/courses/domain/image/SessionImageRepository.java new file mode 100644 index 0000000000..690f73aef6 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/image/SessionImageRepository.java @@ -0,0 +1,9 @@ +package nextstep.courses.domain.image; + +public interface SessionImageRepository { + int save(SessionCoverImage image); + + SessionCoverImage findById(Long id); + + SessionCoverImage findBySessionId(Long sessionId); +} diff --git a/src/main/java/nextstep/courses/domain/registration/Registration.java b/src/main/java/nextstep/courses/domain/registration/Registration.java index b9e25cda08..8de5695db3 100644 --- a/src/main/java/nextstep/courses/domain/registration/Registration.java +++ b/src/main/java/nextstep/courses/domain/registration/Registration.java @@ -1,23 +1,42 @@ package nextstep.courses.domain.registration; import java.time.LocalDateTime; +import java.util.Objects; public class Registration { + private final Long id; private final Long sessionId; private final Long studentId; private final LocalDateTime enrolledAt; public Registration(Long sessionId, Long studentId) { - this(sessionId, studentId, LocalDateTime.now()); + this(null, sessionId, studentId, LocalDateTime.now()); } - public Registration(Long sessionId, Long studentId, LocalDateTime enrolledAt) { + public Registration(Long id, Long sessionId, Long studentId, LocalDateTime enrolledAt) { + this.id = id; this.sessionId = sessionId; this.studentId = studentId; this.enrolledAt = enrolledAt; } + public boolean contains(Long studentId) { - return this.studentId == studentId; + return Objects.equals(this.studentId, studentId); + } + + public Long getId() { + return id; + } + + public Long getSessionId() { + return sessionId; + } + + public Long getStudentId() { + return studentId; } + public LocalDateTime getEnrolledAt() { + return enrolledAt; + } } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/registration/RegistrationRepository.java b/src/main/java/nextstep/courses/domain/registration/RegistrationRepository.java new file mode 100644 index 0000000000..6140af2387 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/registration/RegistrationRepository.java @@ -0,0 +1,13 @@ +package nextstep.courses.domain.registration; + +import java.util.List; + +public interface RegistrationRepository { + int save(Registration registration); + + Registration findById(Long id); + + List findBySessionId(Long sessionId); + + int countBySessionId(Long sessionId); +} diff --git a/src/main/java/nextstep/courses/domain/registration/Registrations.java b/src/main/java/nextstep/courses/domain/registration/Registrations.java index d85102f1ab..0eff631976 100644 --- a/src/main/java/nextstep/courses/domain/registration/Registrations.java +++ b/src/main/java/nextstep/courses/domain/registration/Registrations.java @@ -1,33 +1,56 @@ package nextstep.courses.domain.registration; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public class Registrations { + private static final int UNLIMITED = -1; + private final List registrations; + private final int maxCapacity; public Registrations() { - this(new ArrayList<>()); + this(new ArrayList<>(), UNLIMITED); + } + + public Registrations(int maxCapacity) { + this(new ArrayList<>(), maxCapacity); } - public Registrations(List registrations) { + public Registrations(List registrations, int maxCapacity) { this.registrations = registrations; + this.maxCapacity = maxCapacity; + } + + public static Registrations of(List registrations, int maxCapacity) { + return new Registrations(registrations, maxCapacity); } public Registrations add(Registration registration) { + validateCapacity(); List newList = new ArrayList<>(registrations); newList.add(registration); - return new Registrations(newList); + return new Registrations(newList, maxCapacity); } - public int count() { - return registrations.size(); + public void validateCapacity() { + if (isUnlimited()) { + return; + } + if (registrations.size() >= maxCapacity) { + throw new IllegalArgumentException("최대 수강 인원을 초과할 수 없습니다."); + } + } + + private boolean isUnlimited() { + return maxCapacity == UNLIMITED; } - public boolean contains(Long studentId) { - return registrations.stream() - .anyMatch(r -> r.contains(studentId)); + public int getMaxCapacity() { + return maxCapacity; } + public int count() { + return registrations.size(); + } } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/Enrollment.java b/src/main/java/nextstep/courses/domain/session/Enrollment.java index 8349d21eea..0479d1fc9f 100644 --- a/src/main/java/nextstep/courses/domain/session/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/session/Enrollment.java @@ -5,7 +5,7 @@ public class Enrollment { private SessionState state; - private SessionType type; + private final SessionType type; public Enrollment() { this(SessionState.PREPARING, new FreeType()); @@ -20,9 +20,9 @@ public Enrollment(SessionState state, SessionType type) { this.type = type; } - public void enroll(long payAmount) { + public void validateEnroll(long payAmount) { validateState(); - this.type = type.enroll(payAmount); + type.validateEnroll(payAmount); } public void open() { @@ -38,4 +38,12 @@ private void validateState() { throw new IllegalStateException("모집중인 강의만 수강신청이 가능합니다."); } } + + public SessionState getState() { + return state; + } + + public SessionType getType() { + return type; + } } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index a506fbbe29..cd1a6cce7a 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -1,31 +1,35 @@ package nextstep.courses.domain.session; import nextstep.courses.domain.BaseEntity; -import nextstep.courses.domain.course.Course; import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.session.type.SessionType; public class Session extends BaseEntity { - private final Course course; + private final Long courseId; private final Term term; - private final SessionCoverImage cover; private final SessionPeriod period; private final Enrollment enrollment; + private final SessionCoverImage coverImage; - public Session(Course course, int term, SessionCoverImage cover, String startDay, String endDay) { - this(null, course, new Term(term), cover, new SessionPeriod(startDay, endDay), new Enrollment()); + public Session(Long courseId, int term, String startDay, String endDay, SessionCoverImage coverImage) { + this(null, courseId, new Term(term), new SessionPeriod(startDay, endDay), new Enrollment(), coverImage); } - public Session(Long id, Course course, Term term, SessionCoverImage cover, SessionPeriod period, Enrollment enrollment) { + public Session(Long courseId, int term, String startDay, String endDay, SessionType type, SessionCoverImage coverImage) { + this(null, courseId, new Term(term), new SessionPeriod(startDay, endDay), new Enrollment(type), coverImage); + } + + public Session(Long id, Long courseId, Term term, SessionPeriod period, Enrollment enrollment, SessionCoverImage coverImage) { super(id); - this.course = course; + this.courseId = courseId; this.term = term; - this.cover = cover; this.period = period; this.enrollment = enrollment; + this.coverImage = coverImage; } - public void enroll(long payAmount) { - enrollment.enroll(payAmount); + public void validateEnroll(long payAmount) { + enrollment.validateEnroll(payAmount); } public void open() { @@ -35,4 +39,28 @@ public void open() { public void close() { enrollment.close(); } + + public Long getCourseId() { + return courseId; + } + + public Term getTerm() { + return term; + } + + public SessionPeriod getPeriod() { + return period; + } + + public Enrollment getEnrollment() { + return enrollment; + } + + public SessionState getState() { + return enrollment.getState(); + } + + public SessionCoverImage getCoverImage() { + return coverImage; + } } diff --git a/src/main/java/nextstep/courses/domain/session/SessionPeriod.java b/src/main/java/nextstep/courses/domain/session/SessionPeriod.java index af5089b4c3..2cbb6aa3a4 100644 --- a/src/main/java/nextstep/courses/domain/session/SessionPeriod.java +++ b/src/main/java/nextstep/courses/domain/session/SessionPeriod.java @@ -21,4 +21,12 @@ private void validate(LocalDate startDay, LocalDate endDay) { throw new IllegalArgumentException("시작일은 종료일보다 이전이어야 합니다."); } } + + public LocalDate startDay() { + return startDay; + } + + public LocalDate endDay() { + return endDay; + } } diff --git a/src/main/java/nextstep/courses/domain/session/SessionRepository.java b/src/main/java/nextstep/courses/domain/session/SessionRepository.java new file mode 100644 index 0000000000..ec68209ead --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionRepository.java @@ -0,0 +1,10 @@ +package nextstep.courses.domain.session; + +public interface SessionRepository { + + int save(Session session); + + Session findById(Long id); + + int updateState(Long id, SessionState state); +} diff --git a/src/main/java/nextstep/courses/domain/session/type/FreeType.java b/src/main/java/nextstep/courses/domain/session/type/FreeType.java index b6b425d1af..19bf953be1 100644 --- a/src/main/java/nextstep/courses/domain/session/type/FreeType.java +++ b/src/main/java/nextstep/courses/domain/session/type/FreeType.java @@ -1,9 +1,25 @@ package nextstep.courses.domain.session.type; +import nextstep.courses.domain.registration.Registrations; + public class FreeType implements SessionType { + private final Registrations registrations; + + public FreeType() { + this(new Registrations()); + } + + public FreeType(Registrations registrations) { + this.registrations = registrations; + } + + @Override + public void validateEnroll(long payAmount) { + // 무료 강의는 수강료 검증 없음, 정원 제한 없음 + } @Override - public SessionType enroll(long payAmount) { - return this; + public Registrations getRegistrations() { + return registrations; } } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/type/PaidType.java b/src/main/java/nextstep/courses/domain/session/type/PaidType.java index dc680bca29..ccf685d6dc 100644 --- a/src/main/java/nextstep/courses/domain/session/type/PaidType.java +++ b/src/main/java/nextstep/courses/domain/session/type/PaidType.java @@ -1,35 +1,25 @@ package nextstep.courses.domain.session.type; import java.util.Objects; +import nextstep.courses.domain.registration.Registrations; public class PaidType implements SessionType { - private final int maxCapacity; private final long tuitionFee; - private final int studentCount; + private final Registrations registrations; public PaidType(int maxCapacity, long tuitionFee) { - this(maxCapacity, tuitionFee, 0); + this(tuitionFee, new Registrations(maxCapacity)); } - public PaidType(int maxCapacity, long tuitionFee, int studentCount) { - validateCapacity(maxCapacity, studentCount); - this.maxCapacity = maxCapacity; + public PaidType(long tuitionFee, Registrations registrations) { this.tuitionFee = tuitionFee; - this.studentCount = studentCount; + this.registrations = registrations; } @Override - public SessionType enroll(long payAmount) { - int newCount = studentCount + 1; - validateCapacity(maxCapacity, newCount); + public void validateEnroll(long payAmount) { validateTuitionFee(payAmount); - return new PaidType(maxCapacity, tuitionFee, newCount); - } - - private void validateCapacity(int maxCapacity, int studentCount) { - if (maxCapacity < studentCount) { - throw new IllegalArgumentException("최대 수강 인원을 초과할 수 없습니다."); - } + registrations.validateCapacity(); } private void validateTuitionFee(long payAmount) { @@ -38,18 +28,26 @@ private void validateTuitionFee(long payAmount) { } } + public long getTuitionFee() { + return tuitionFee; + } + + @Override + public Registrations getRegistrations() { + return registrations; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PaidType that = (PaidType) o; - return maxCapacity == that.maxCapacity - && tuitionFee == that.tuitionFee - && studentCount == that.studentCount; + return tuitionFee == that.tuitionFee + && Objects.equals(registrations, that.registrations); } @Override public int hashCode() { - return Objects.hash(maxCapacity, tuitionFee, studentCount); + return Objects.hash(tuitionFee, registrations); } } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/type/SessionType.java b/src/main/java/nextstep/courses/domain/session/type/SessionType.java index 3998c88427..a162cc4eb6 100644 --- a/src/main/java/nextstep/courses/domain/session/type/SessionType.java +++ b/src/main/java/nextstep/courses/domain/session/type/SessionType.java @@ -1,5 +1,9 @@ package nextstep.courses.domain.session.type; +import nextstep.courses.domain.registration.Registrations; + public interface SessionType { - SessionType enroll(long payAmount); + void validateEnroll(long payAmount); + + Registrations getRegistrations(); } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcRegistrationRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcRegistrationRepository.java new file mode 100644 index 0000000000..3269636964 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcRegistrationRepository.java @@ -0,0 +1,66 @@ +package nextstep.courses.infrastructure; + +import java.sql.Timestamp; +import java.util.List; +import nextstep.courses.domain.registration.Registration; +import nextstep.courses.domain.registration.RegistrationRepository; +import nextstep.courses.infrastructure.entity.RegistrationEntity; +import nextstep.courses.infrastructure.mapper.RegistrationMapper; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +@Repository("registrationRepository") +public class JdbcRegistrationRepository implements RegistrationRepository { + private final JdbcOperations jdbcTemplate; + + public JdbcRegistrationRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public int save(Registration registration) { + RegistrationEntity entity = RegistrationMapper.toEntity(registration); + + String sql = "insert into registration (session_id, student_id, enrolled_at) values(?, ?, ?)"; + + return jdbcTemplate.update(sql, + entity.getSessionId(), + entity.getStudentId(), + Timestamp.valueOf(entity.getEnrolledAt()) + ); + } + + @Override + public Registration findById(Long id) { + String sql = "select id, session_id, student_id, enrolled_at from registration where id = ?"; + + RegistrationEntity entity = jdbcTemplate.queryForObject(sql, rowMapper(), id); + return RegistrationMapper.toDomain(entity); + } + + @Override + public List findBySessionId(Long sessionId) { + String sql = "select id, session_id, student_id, enrolled_at from registration where session_id = ?"; + + List entities = jdbcTemplate.query(sql, rowMapper(), sessionId); + return entities.stream() + .map(RegistrationMapper::toDomain) + .toList(); + } + + @Override + public int countBySessionId(Long sessionId) { + String sql = "select count(*) from registration where session_id = ?"; + return jdbcTemplate.queryForObject(sql, Integer.class, sessionId); + } + + private RowMapper rowMapper() { + return (rs, rowNum) -> new RegistrationEntity( + rs.getLong("id"), + rs.getLong("session_id"), + rs.getLong("student_id"), + rs.getTimestamp("enrolled_at").toLocalDateTime() + ); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java new file mode 100644 index 0000000000..0c0fbd501d --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java @@ -0,0 +1,60 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.image.SessionImageRepository; +import nextstep.courses.infrastructure.entity.SessionCoverImageEntity; +import nextstep.courses.infrastructure.mapper.SessionCoverImageMapper; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +@Repository("sessionImageRepository") +public class JdbcSessionImageRepository implements SessionImageRepository { + private final JdbcOperations jdbcTemplate; + + public JdbcSessionImageRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public int save(SessionCoverImage image) { + SessionCoverImageEntity entity = SessionCoverImageMapper.toEntity(image); + + String sql = "insert into session_cover_image (session_id, width, height, extension, capacity) values(?, ?, ?, ?, ?)"; + + return jdbcTemplate.update(sql, + entity.getSessionId(), + entity.getWidth(), + entity.getHeight(), + entity.getExtension(), + entity.getCapacity() + ); + } + + @Override + public SessionCoverImage findById(Long id) { + String sql = "select id, session_id, width, height, extension, capacity from session_cover_image where id = ?"; + + SessionCoverImageEntity entity = jdbcTemplate.queryForObject(sql, rowMapper(), id); + return SessionCoverImageMapper.toDomain(entity); + } + + @Override + public SessionCoverImage findBySessionId(Long sessionId) { + String sql = "select id, session_id, width, height, extension, capacity from session_cover_image where session_id = ?"; + + SessionCoverImageEntity entity = jdbcTemplate.queryForObject(sql, rowMapper(), sessionId); + return SessionCoverImageMapper.toDomain(entity); + } + + private RowMapper rowMapper() { + return (rs, rowNum) -> new SessionCoverImageEntity( + rs.getLong("id"), + rs.getLong("session_id"), + rs.getInt("width"), + rs.getInt("height"), + rs.getString("extension"), + rs.getLong("capacity") + ); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java new file mode 100644 index 0000000000..c734f2662f --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java @@ -0,0 +1,142 @@ +package nextstep.courses.infrastructure; + +import java.sql.Date; +import java.time.LocalDate; +import java.util.List; +import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.registration.Registrations; +import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionRepository; +import nextstep.courses.domain.session.SessionState; +import nextstep.courses.infrastructure.entity.RegistrationEntity; +import nextstep.courses.infrastructure.entity.SessionEntity; +import nextstep.courses.infrastructure.entity.SessionCoverImageEntity; +import nextstep.courses.infrastructure.mapper.RegistrationMapper; +import nextstep.courses.infrastructure.mapper.SessionMapper; +import nextstep.courses.infrastructure.mapper.SessionCoverImageMapper; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +@Repository("sessionRepository") +public class JdbcSessionRepository implements SessionRepository { + private final JdbcOperations jdbcTemplate; + + public JdbcSessionRepository(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public int save(Session session) { + SessionEntity entity = SessionMapper.toEntity(session); + + String sql = "insert into session (course_id, term, start_day, end_day, state, type, max_capacity, tuition_fee, created_at) values(?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + int result = jdbcTemplate.update(sql, + entity.getCourseId(), + entity.getTerm(), + Date.valueOf(entity.getStartDay()), + Date.valueOf(entity.getEndDay()), + entity.getState(), + entity.getType(), + entity.getMaxCapacity(), + entity.getTuitionFee(), + entity.getCreatedAt() + ); + + if (session.getCoverImage() != null) { + saveCoverImage(session.getCoverImage()); + } + + return result; + } + + private void saveCoverImage(SessionCoverImage image) { + SessionCoverImageEntity entity = SessionCoverImageMapper.toEntity(image); + String sql = "insert into session_cover_image (session_id, width, height, extension, capacity) values(?, ?, ?, ?, ?)"; + jdbcTemplate.update(sql, + entity.getSessionId(), + entity.getWidth(), + entity.getHeight(), + entity.getExtension(), + entity.getCapacity() + ); + } + + @Override + public Session findById(Long id) { + String sql = "select id, course_id, term, start_day, end_day, state, type, max_capacity, tuition_fee, created_at from session where id = ?"; + + SessionEntity entity = jdbcTemplate.queryForObject(sql, sessionRowMapper(), id); + Registrations registrations = findRegistrationsBySessionId(id, entity.getMaxCapacity()); + SessionCoverImage coverImage = findCoverImageBySessionId(id); + return SessionMapper.toDomain(entity, registrations, coverImage); + } + + private SessionCoverImage findCoverImageBySessionId(Long sessionId) { + String sql = "select id, session_id, width, height, extension, capacity from session_cover_image where session_id = ?"; + try { + SessionCoverImageEntity entity = jdbcTemplate.queryForObject(sql, coverImageRowMapper(), sessionId); + return SessionCoverImageMapper.toDomain(entity); + } catch (EmptyResultDataAccessException e) { + return null; + } + } + + private RowMapper coverImageRowMapper() { + return (rs, rowNum) -> new SessionCoverImageEntity( + rs.getLong("id"), + rs.getLong("session_id"), + rs.getInt("width"), + rs.getInt("height"), + rs.getString("extension"), + rs.getLong("capacity") + ); + } + + @Override + public int updateState(Long id, SessionState state) { + String sql = "update session set state = ? where id = ?"; + return jdbcTemplate.update(sql, state.name(), id); + } + + private Registrations findRegistrationsBySessionId(Long sessionId, Integer maxCapacity) { + String sql = "select id, session_id, student_id, enrolled_at from registration where session_id = ?"; + + List entities = jdbcTemplate.query(sql, registrationRowMapper(), sessionId); + int capacity = maxCapacity != null ? maxCapacity : -1; + return RegistrationMapper.toDomain(entities, capacity); + } + + private RowMapper sessionRowMapper() { + return (rs, rowNum) -> new SessionEntity( + rs.getLong("id"), + rs.getLong("course_id"), + rs.getInt("term"), + toLocalDate(rs.getDate("start_day")), + toLocalDate(rs.getDate("end_day")), + rs.getString("state"), + rs.getString("type"), + rs.getObject("max_capacity", Integer.class), + rs.getObject("tuition_fee", Long.class), + rs.getTimestamp("created_at") != null ? rs.getTimestamp("created_at").toLocalDateTime() : null + ); + } + + private RowMapper registrationRowMapper() { + return (rs, rowNum) -> new RegistrationEntity( + rs.getLong("id"), + rs.getLong("session_id"), + rs.getLong("student_id"), + rs.getTimestamp("enrolled_at").toLocalDateTime() + ); + } + + private LocalDate toLocalDate(Date date) { + if (date == null) { + return null; + } + return date.toLocalDate(); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/entity/RegistrationEntity.java b/src/main/java/nextstep/courses/infrastructure/entity/RegistrationEntity.java new file mode 100644 index 0000000000..418eac3ad5 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/entity/RegistrationEntity.java @@ -0,0 +1,33 @@ +package nextstep.courses.infrastructure.entity; + +import java.time.LocalDateTime; + +public class RegistrationEntity { + private final Long id; + private final Long sessionId; + private final Long studentId; + private final LocalDateTime enrolledAt; + + public RegistrationEntity(Long id, Long sessionId, Long studentId, LocalDateTime enrolledAt) { + this.id = id; + this.sessionId = sessionId; + this.studentId = studentId; + this.enrolledAt = enrolledAt; + } + + public Long getId() { + return id; + } + + public Long getSessionId() { + return sessionId; + } + + public Long getStudentId() { + return studentId; + } + + public LocalDateTime getEnrolledAt() { + return enrolledAt; + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/entity/SessionCoverImageEntity.java b/src/main/java/nextstep/courses/infrastructure/entity/SessionCoverImageEntity.java new file mode 100644 index 0000000000..5dba4f9985 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/entity/SessionCoverImageEntity.java @@ -0,0 +1,43 @@ +package nextstep.courses.infrastructure.entity; + +public class SessionCoverImageEntity { + private final Long id; + private final Long sessionId; + private final int width; + private final int height; + private final String extension; + private final long capacity; + + public SessionCoverImageEntity(Long id, Long sessionId, int width, int height, String extension, long capacity) { + this.id = id; + this.sessionId = sessionId; + this.width = width; + this.height = height; + this.extension = extension; + this.capacity = capacity; + } + + public Long getId() { + return id; + } + + public Long getSessionId() { + return sessionId; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public String getExtension() { + return extension; + } + + public long getCapacity() { + return capacity; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/entity/SessionEntity.java b/src/main/java/nextstep/courses/infrastructure/entity/SessionEntity.java new file mode 100644 index 0000000000..f64ae044b0 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/entity/SessionEntity.java @@ -0,0 +1,72 @@ +package nextstep.courses.infrastructure.entity; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public class SessionEntity { + private final Long id; + private final Long courseId; + private final int term; + private final LocalDate startDay; + private final LocalDate endDay; + private final String state; + private final String type; + private final Integer maxCapacity; + private final Long tuitionFee; + private final LocalDateTime createdAt; + + public SessionEntity(Long id, Long courseId, int term, LocalDate startDay, LocalDate endDay, + String state, String type, Integer maxCapacity, Long tuitionFee, + LocalDateTime createdAt) { + this.id = id; + this.courseId = courseId; + this.term = term; + this.startDay = startDay; + this.endDay = endDay; + this.state = state; + this.type = type; + this.maxCapacity = maxCapacity; + this.tuitionFee = tuitionFee; + this.createdAt = createdAt; + } + + public Long getId() { + return id; + } + + public Long getCourseId() { + return courseId; + } + + public int getTerm() { + return term; + } + + public LocalDate getStartDay() { + return startDay; + } + + public LocalDate getEndDay() { + return endDay; + } + + public String getState() { + return state; + } + + public String getType() { + return type; + } + + public Integer getMaxCapacity() { + return maxCapacity; + } + + public Long getTuitionFee() { + return tuitionFee; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java b/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java new file mode 100644 index 0000000000..bedf5a183a --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java @@ -0,0 +1,37 @@ +package nextstep.courses.infrastructure.mapper; + +import java.util.List; +import nextstep.courses.domain.registration.Registration; +import nextstep.courses.domain.registration.Registrations; +import nextstep.courses.infrastructure.entity.RegistrationEntity; + +public class RegistrationMapper { + + private RegistrationMapper() { + } + + public static RegistrationEntity toEntity(Registration registration) { + return new RegistrationEntity( + registration.getId(), + registration.getSessionId(), + registration.getStudentId(), + registration.getEnrolledAt() + ); + } + + public static Registration toDomain(RegistrationEntity entity) { + return new Registration( + entity.getId(), + entity.getSessionId(), + entity.getStudentId(), + entity.getEnrolledAt() + ); + } + + public static Registrations toDomain(List entities, int maxCapacity) { + List registrations = entities.stream() + .map(RegistrationMapper::toDomain) + .toList(); + return Registrations.of(registrations, maxCapacity); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/mapper/SessionCoverImageMapper.java b/src/main/java/nextstep/courses/infrastructure/mapper/SessionCoverImageMapper.java new file mode 100644 index 0000000000..58348743ac --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/mapper/SessionCoverImageMapper.java @@ -0,0 +1,34 @@ +package nextstep.courses.infrastructure.mapper; + +import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.image.SessionImageCapacity; +import nextstep.courses.domain.image.SessionImageDimension; +import nextstep.courses.domain.image.SessionImageExtension; +import nextstep.courses.infrastructure.entity.SessionCoverImageEntity; + +public class SessionCoverImageMapper { + + private SessionCoverImageMapper() { + } + + public static SessionCoverImageEntity toEntity(SessionCoverImage image) { + return new SessionCoverImageEntity( + image.getId(), + image.getSessionId(), + image.getDimension().width(), + image.getDimension().height(), + image.getExtension().name(), + image.getCapacity().bytes() + ); + } + + public static SessionCoverImage toDomain(SessionCoverImageEntity entity) { + return new SessionCoverImage( + entity.getId(), + entity.getSessionId(), + new SessionImageDimension(entity.getWidth(), entity.getHeight()), + SessionImageExtension.valueOf(entity.getExtension()), + new SessionImageCapacity(entity.getCapacity()) + ); + } +} diff --git a/src/main/java/nextstep/courses/infrastructure/mapper/SessionMapper.java b/src/main/java/nextstep/courses/infrastructure/mapper/SessionMapper.java new file mode 100644 index 0000000000..ec62b75e96 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/mapper/SessionMapper.java @@ -0,0 +1,71 @@ +package nextstep.courses.infrastructure.mapper; + +import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.registration.Registrations; +import nextstep.courses.domain.session.Enrollment; +import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionPeriod; +import nextstep.courses.domain.session.SessionState; +import nextstep.courses.domain.session.Term; +import nextstep.courses.domain.session.type.FreeType; +import nextstep.courses.domain.session.type.PaidType; +import nextstep.courses.domain.session.type.SessionType; +import nextstep.courses.infrastructure.entity.SessionEntity; + +public class SessionMapper { + + private SessionMapper() { + } + + public static SessionEntity toEntity(Session session) { + Enrollment enrollment = session.getEnrollment(); + SessionType type = enrollment.getType(); + + Integer maxCapacity = null; + Long tuitionFee = null; + String typeName = "FREE"; + + if (type instanceof PaidType) { + PaidType paidType = (PaidType) type; + maxCapacity = paidType.getRegistrations().getMaxCapacity(); + tuitionFee = paidType.getTuitionFee(); + typeName = "PAID"; + } + + return new SessionEntity( + session.getId(), + session.getCourseId(), + session.getTerm().getValue(), + session.getPeriod().startDay(), + session.getPeriod().endDay(), + enrollment.getState().name(), + typeName, + maxCapacity, + tuitionFee, + session.getCreatedAt() + ); + } + + public static Session toDomain(SessionEntity entity, Registrations registrations, SessionCoverImage coverImage) { + SessionPeriod period = new SessionPeriod(entity.getStartDay(), entity.getEndDay()); + SessionState state = SessionState.valueOf(entity.getState()); + SessionType type = createSessionType(entity, registrations); + Enrollment enrollment = new Enrollment(state, type); + + return new Session( + entity.getId(), + entity.getCourseId(), + new Term(entity.getTerm()), + period, + enrollment, + coverImage + ); + } + + private static SessionType createSessionType(SessionEntity entity, Registrations registrations) { + if ("PAID".equals(entity.getType())) { + return new PaidType(entity.getTuitionFee(), registrations); + } + return new FreeType(registrations); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/service/CourseManageService.java b/src/main/java/nextstep/courses/service/CourseManageService.java new file mode 100644 index 0000000000..6a3196416c --- /dev/null +++ b/src/main/java/nextstep/courses/service/CourseManageService.java @@ -0,0 +1,24 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.CourseRepository; +import org.springframework.stereotype.Service; + +@Service +public class CourseManageService { + + private final CourseRepository courseRepository; + + public CourseManageService(CourseRepository courseRepository) { + this.courseRepository = courseRepository; + } + + public void createCourse(String title, Long creatorId) { + Course course = new Course(title, creatorId); + courseRepository.save(course); + } + + public Course findById(Long id) { + return courseRepository.findById(id); + } +} diff --git a/src/main/java/nextstep/courses/service/RegistrationService.java b/src/main/java/nextstep/courses/service/RegistrationService.java new file mode 100644 index 0000000000..24040b316e --- /dev/null +++ b/src/main/java/nextstep/courses/service/RegistrationService.java @@ -0,0 +1,29 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.registration.Registration; +import nextstep.courses.domain.registration.RegistrationRepository; +import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionRepository; +import nextstep.payments.domain.Payment; +import org.springframework.stereotype.Service; + +@Service +public class RegistrationService { + + private final SessionRepository sessionRepository; + private final RegistrationRepository registrationRepository; + + public RegistrationService(SessionRepository sessionRepository, + RegistrationRepository registrationRepository) { + this.sessionRepository = sessionRepository; + this.registrationRepository = registrationRepository; + } + + public void register(Payment payment) { + Session session = sessionRepository.findById(payment.getSessionId()); + session.validateEnroll(payment.getAmount()); + + Registration registration = new Registration(payment.getSessionId(), payment.getNsUserId()); + registrationRepository.save(registration); + } +} diff --git a/src/main/java/nextstep/courses/service/SessionEnrollService.java b/src/main/java/nextstep/courses/service/SessionEnrollService.java deleted file mode 100644 index 5b12a385a7..0000000000 --- a/src/main/java/nextstep/courses/service/SessionEnrollService.java +++ /dev/null @@ -1,16 +0,0 @@ -package nextstep.courses.service; - -import nextstep.courses.domain.course.CourseRepository; -import nextstep.courses.domain.session.Session; -import nextstep.payments.domain.Payment; -import org.springframework.stereotype.Service; - -@Service -public class SessionEnrollService { - private CourseRepository courseRepository; - - public void enroll(Session session, Payment payment){ - session.enroll(payment.getAmount()); - } - -} diff --git a/src/main/java/nextstep/courses/service/SessionManageService.java b/src/main/java/nextstep/courses/service/SessionManageService.java new file mode 100644 index 0000000000..cb601b10e5 --- /dev/null +++ b/src/main/java/nextstep/courses/service/SessionManageService.java @@ -0,0 +1,49 @@ +package nextstep.courses.service; + +import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionRepository; +import nextstep.courses.domain.session.type.PaidType; +import org.springframework.stereotype.Service; + +@Service +public class SessionManageService { + + private final SessionRepository sessionRepository; + + public SessionManageService(SessionRepository sessionRepository) { + this.sessionRepository = sessionRepository; + } + + public void createFreeSession(Long courseId, int term, String startDay, String endDay, + int imageWidth, int imageHeight, String imageExtension, long imageBytes) { + SessionCoverImage coverImage = new SessionCoverImage(null, imageWidth, imageHeight, imageExtension, imageBytes); + Session session = new Session(courseId, term, startDay, endDay, coverImage); + sessionRepository.save(session); + } + + public void createPaidSession(Long courseId, int term, String startDay, String endDay, + int maxCapacity, long tuitionFee, + int imageWidth, int imageHeight, String imageExtension, long imageBytes) { + SessionCoverImage coverImage = new SessionCoverImage(null, imageWidth, imageHeight, imageExtension, imageBytes); + PaidType paidType = new PaidType(maxCapacity, tuitionFee); + Session session = new Session(courseId, term, startDay, endDay, paidType, coverImage); + sessionRepository.save(session); + } + + public void openSession(Long sessionId) { + Session session = sessionRepository.findById(sessionId); + session.open(); + sessionRepository.updateState(sessionId, session.getState()); + } + + public void closeSession(Long sessionId) { + Session session = sessionRepository.findById(sessionId); + session.close(); + sessionRepository.updateState(sessionId, session.getState()); + } + + public Session findById(Long sessionId) { + return sessionRepository.findById(sessionId); + } +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 8d5a988c8b..42c3963afb 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -48,3 +48,36 @@ create table delete_history ( deleted_by_id bigint, primary key (id) ); + +create table session ( + id bigint generated by default as identity, + course_id bigint not null, + term int not null, + start_day date not null, + end_day date not null, + state varchar(20) not null, + type varchar(10) not null, + max_capacity int, + tuition_fee bigint, + created_at timestamp not null, + updated_at timestamp, + primary key (id) +); + +create table session_cover_image ( + id bigint generated by default as identity, + session_id bigint not null, + width int not null, + height int not null, + extension varchar(10) not null, + capacity bigint not null, + primary key (id) +); + +create table registration ( + id bigint generated by default as identity, + session_id bigint not null, + student_id bigint not null, + enrolled_at timestamp not null, + primary key (id) +); diff --git a/src/test/java/nextstep/courses/domain/image/SessionCoverImageTest.java b/src/test/java/nextstep/courses/domain/image/SessionCoverImageTest.java index 795afcfeee..b5e5f57d4e 100644 --- a/src/test/java/nextstep/courses/domain/image/SessionCoverImageTest.java +++ b/src/test/java/nextstep/courses/domain/image/SessionCoverImageTest.java @@ -7,22 +7,21 @@ import org.junit.jupiter.params.provider.CsvSource; class SessionCoverImageTest { + private static final Long SESSION_ID = 1L; private static final int WIDTH = 300, HEIGHT = 200; private static final long VALID_BYTES = 1024 * 500; // 500KB @ParameterizedTest @CsvSource({"gif","jpg","jpeg", "png", "svg"}) void 허용된_확장자(String extension){ - assertDoesNotThrow(() -> new SessionCoverImage(WIDTH, HEIGHT, extension, VALID_BYTES)); + assertDoesNotThrow(() -> new SessionCoverImage(SESSION_ID, WIDTH, HEIGHT, extension, VALID_BYTES)); } @ParameterizedTest @CsvSource({"webp"}) void 허용하지않은_확장자는_예외(String extension){ - assertThatThrownBy(() -> new SessionCoverImage(WIDTH, HEIGHT, extension, VALID_BYTES)) + assertThatThrownBy(() -> new SessionCoverImage(SESSION_ID, WIDTH, HEIGHT, extension, VALID_BYTES)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("지원하지 않는 이미지 확장자입니다: " + extension); } - - } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/registration/RegistrationsTest.java b/src/test/java/nextstep/courses/domain/registration/RegistrationsTest.java new file mode 100644 index 0000000000..a85c3e112c --- /dev/null +++ b/src/test/java/nextstep/courses/domain/registration/RegistrationsTest.java @@ -0,0 +1,40 @@ +package nextstep.courses.domain.registration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class RegistrationsTest { + + @Test + void 최대수강인원_초과하면_예외() { + Registrations registrations = new Registrations(1); + registrations = registrations.add(new Registration(1L, 1L)); + + Registrations finalRegistrations = registrations; + assertThatThrownBy(() -> finalRegistrations.add(new Registration(1L, 2L))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("최대 수강 인원을 초과할 수 없습니다."); + } + + @Test + void 최대수강인원_이하면_등록_가능() { + Registrations registrations = new Registrations(2); + + assertThatCode(() -> registrations.add(new Registration(1L, 1L))) + .doesNotThrowAnyException(); + } + + @Test + void 무제한이면_수강인원_제한없음() { + Registrations registrations = new Registrations(); + + for (long i = 0; i < 1000; i++) { + registrations = registrations.add(new Registration(1L, i)); + } + + assertThat(registrations.count()).isEqualTo(1000); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/SessionBuilder.java b/src/test/java/nextstep/courses/domain/session/SessionBuilder.java index 76061345e8..745b3c27c8 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionBuilder.java +++ b/src/test/java/nextstep/courses/domain/session/SessionBuilder.java @@ -1,18 +1,18 @@ package nextstep.courses.domain.session; -import nextstep.courses.domain.course.Course; import nextstep.courses.domain.image.SessionCoverImage; import nextstep.courses.domain.session.type.FreeType; +import nextstep.courses.domain.session.type.PaidType; import nextstep.courses.domain.session.type.SessionType; public class SessionBuilder { private Long id = null; - private Course course = new Course("TDD", 1L); + private Long courseId = 1L; private Term term = new Term(1); - private SessionCoverImage cover = new SessionCoverImage(300, 200, "png", 1024 * 500); private SessionPeriod period = new SessionPeriod("2025-01-01", "2025-01-31"); private SessionState state = SessionState.PREPARING; private SessionType type = new FreeType(); + private SessionCoverImage coverImage = null; public static SessionBuilder aSession() { return new SessionBuilder(); @@ -23,8 +23,8 @@ public SessionBuilder withId(Long id) { return this; } - public SessionBuilder withCourse(Course course) { - this.course = course; + public SessionBuilder withCourseId(Long courseId) { + this.courseId = courseId; return this; } @@ -33,11 +33,6 @@ public SessionBuilder withTerm(int term) { return this; } - public SessionBuilder withCover(SessionCoverImage cover) { - this.cover = cover; - return this; - } - public SessionBuilder withPeriod(String startDay, String endDay) { this.period = new SessionPeriod(startDay, endDay); return this; @@ -53,13 +48,23 @@ public SessionBuilder withType(SessionType type) { return this; } + public SessionBuilder withCoverImage(SessionCoverImage coverImage) { + this.coverImage = coverImage; + return this; + } + public SessionBuilder recruiting() { this.state = SessionState.RECRUITING; return this; } + public SessionBuilder paid(int maxCapacity, long tuitionFee) { + this.type = new PaidType(maxCapacity, tuitionFee); + return this; + } + public Session build() { Enrollment enrollment = new Enrollment(state, type); - return new Session(id, course, term, cover, period, enrollment); + return new Session(id, courseId, term, period, enrollment, coverImage); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java index 21513ea3c1..f3414e7afc 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -8,37 +8,49 @@ class SessionTest { - @Test - void 모집중일때_수강신청_가능() { - Session session = aSession().recruiting().build(); - - assertDoesNotThrow(() -> session.enroll(0)); - } - - @Test - void 모집중이_아닐때_수강신청하면_예외() { - Session session = aSession().build(); - - assertThatThrownBy(() -> session.enroll(0)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("모집중인 강의만 수강신청이 가능합니다."); - } - - @Test - void 준비중이_아닐때_모집시작하면_예외() { - Session session = aSession().recruiting().build(); - - assertThatThrownBy(() -> session.open()) - .isInstanceOf(IllegalStateException.class) - .hasMessage("준비중인 강의만 모집을 시작할 수 있습니다."); - } - - @Test - void 모집중이_아닐때_종료하면_예외() { - Session session = aSession().build(); - - assertThatThrownBy(() -> session.close()) - .isInstanceOf(IllegalStateException.class) - .hasMessage("모집중인 강의만 종료할 수 있습니다."); - } -} \ No newline at end of file + @Test + void 모집중일때_수강신청_가능() { + Session session = aSession().recruiting().build(); + + assertDoesNotThrow(() -> session.validateEnroll(0)); + } + + @Test + void 모집중이_아닐때_수강신청하면_예외() { + Session session = aSession().build(); + + assertThatThrownBy(() -> session.validateEnroll(0)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("모집중인 강의만 수강신청이 가능합니다."); + } + + @Test + void 준비중이_아닐때_모집시작하면_예외() { + Session session = aSession().recruiting().build(); + + assertThatThrownBy(() -> session.open()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("준비중인 강의만 모집을 시작할 수 있습니다."); + } + + @Test + void 모집중이_아닐때_종료하면_예외() { + Session session = aSession().build(); + + assertThatThrownBy(() -> session.close()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("모집중인 강의만 종료할 수 있습니다."); + } + + @Test + void 유료강의_수강료_불일치시_예외() { + Session session = aSession() + .recruiting() + .paid(10, 10000L) + .build(); + + assertThatThrownBy(() -> session.validateEnroll(5000L)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("수강료와 지불한 금액이 정확히 일치해야 합니다."); + } +} diff --git a/src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java b/src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java index bc01653de8..3a424c0ebc 100644 --- a/src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java +++ b/src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java @@ -1,34 +1,20 @@ package nextstep.courses.domain.session.type; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import nextstep.courses.domain.session.type.FreeType; -import nextstep.courses.domain.session.type.SessionType; import org.junit.jupiter.api.Test; class FreeTypeTest { - @Test - void 수강인원_제한없음() { - SessionType type = new FreeType(); - - for (int i = 0; i < 1000; i++) { - type = type.enroll(0); - } - - assertThat(type).isInstanceOf(FreeType.class); - } - @Test void 금액_상관없이_수강가능() { SessionType type = new FreeType(); - assertThatCode(() -> type.enroll(0)) + assertThatCode(() -> type.validateEnroll(0)) .doesNotThrowAnyException(); - assertThatCode(() -> type.enroll(100)) + assertThatCode(() -> type.validateEnroll(100)) .doesNotThrowAnyException(); - assertThatCode(() -> type.enroll(999999)) + assertThatCode(() -> type.validateEnroll(999999)) .doesNotThrowAnyException(); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java b/src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java index da71011700..c57a819735 100644 --- a/src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java +++ b/src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java @@ -3,34 +3,24 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import nextstep.courses.domain.session.type.PaidType; import org.junit.jupiter.api.Test; class PaidTypeTest { - @Test - void 최대수강인원_초과하면_예외() { - PaidType type = new PaidType(300, 1000, 300); - - assertThatThrownBy(() -> type.enroll(1000)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("최대 수강 인원을 초과할 수 없습니다."); - } - @Test void 결제금액과_수강료가_동일하지_않으면_예외() { PaidType type = new PaidType(300, 1000); - assertThatThrownBy(() -> type.enroll(999)) + assertThatThrownBy(() -> type.validateEnroll(999)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("수강료와 지불한 금액이 정확히 일치해야 합니다."); } @Test - void 최대수강인원이하_결제금액과수강료동일하면_성공() { + void 결제금액과_수강료가_동일하면_성공() { PaidType type = new PaidType(300, 1000); - assertThatCode(() -> type.enroll(1000)) + assertThatCode(() -> type.validateEnroll(1000)) .doesNotThrowAnyException(); } } \ No newline at end of file From f649497a2ab2849a2113351a7ae009df56add674 Mon Sep 17 00:00:00 2001 From: eunjeong Park Date: Mon, 8 Dec 2025 20:50:45 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=EA=B3=BC=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=82=AC=EC=9A=A9=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20JDBC=EC=82=AC=EC=9A=A9=20=EC=88=A8?= =?UTF-8?q?=EA=B9=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/image/SessionImageCapacity.java | 3 + .../domain/image/SessionImageDimension.java | 6 ++ .../domain/registration/Registrations.java | 3 - .../courses/domain/session/Session.java | 4 + .../infrastructure/CourseRepositoryImpl.java | 29 ++++++ .../infrastructure/JdbcCourseRepository.java | 44 --------- .../RegistrationRepositoryImpl.java | 44 +++++++++ .../SessionImageRepositoryImpl.java | 35 +++++++ .../infrastructure/SessionRepositoryImpl.java | 54 +++++++++++ .../infrastructure/entity/CourseEntity.java | 39 ++++++++ .../infrastructure/jdbc/CourseJdbcDao.java | 44 +++++++++ .../RegistrationJdbcDao.java} | 37 +++----- .../SessionImageJdbcDao.java} | 34 +++---- .../SessionJdbcDao.java} | 95 +++++++------------ .../infrastructure/mapper/CourseMapper.java | 30 ++++++ .../mapper/RegistrationMapper.java | 5 +- .../courses/service/RegistrationService.java | 4 +- .../courses/service/SessionManageService.java | 2 +- .../infrastructure/CourseRepositoryTest.java | 4 +- 19 files changed, 354 insertions(+), 162 deletions(-) create mode 100644 src/main/java/nextstep/courses/infrastructure/CourseRepositoryImpl.java delete mode 100644 src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java create mode 100644 src/main/java/nextstep/courses/infrastructure/RegistrationRepositoryImpl.java create mode 100644 src/main/java/nextstep/courses/infrastructure/SessionImageRepositoryImpl.java create mode 100644 src/main/java/nextstep/courses/infrastructure/SessionRepositoryImpl.java create mode 100644 src/main/java/nextstep/courses/infrastructure/entity/CourseEntity.java create mode 100644 src/main/java/nextstep/courses/infrastructure/jdbc/CourseJdbcDao.java rename src/main/java/nextstep/courses/infrastructure/{JdbcRegistrationRepository.java => jdbc/RegistrationJdbcDao.java} (54%) rename src/main/java/nextstep/courses/infrastructure/{JdbcSessionImageRepository.java => jdbc/SessionImageJdbcDao.java} (53%) rename src/main/java/nextstep/courses/infrastructure/{JdbcSessionRepository.java => jdbc/SessionJdbcDao.java} (59%) create mode 100644 src/main/java/nextstep/courses/infrastructure/mapper/CourseMapper.java diff --git a/src/main/java/nextstep/courses/domain/image/SessionImageCapacity.java b/src/main/java/nextstep/courses/domain/image/SessionImageCapacity.java index d9477c35fc..2be95674ca 100644 --- a/src/main/java/nextstep/courses/domain/image/SessionImageCapacity.java +++ b/src/main/java/nextstep/courses/domain/image/SessionImageCapacity.java @@ -26,4 +26,7 @@ private void validate(long bytes) { } } + public long bytes() { + return bytes; + } } diff --git a/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java b/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java index b3bfe8163f..029bc31da0 100644 --- a/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java +++ b/src/main/java/nextstep/courses/domain/image/SessionImageDimension.java @@ -35,5 +35,11 @@ private int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } + public int width() { + return width; + } + public int height() { + return height; + } } diff --git a/src/main/java/nextstep/courses/domain/registration/Registrations.java b/src/main/java/nextstep/courses/domain/registration/Registrations.java index 0eff631976..6a75f755c5 100644 --- a/src/main/java/nextstep/courses/domain/registration/Registrations.java +++ b/src/main/java/nextstep/courses/domain/registration/Registrations.java @@ -22,9 +22,6 @@ public Registrations(List registrations, int maxCapacity) { this.maxCapacity = maxCapacity; } - public static Registrations of(List registrations, int maxCapacity) { - return new Registrations(registrations, maxCapacity); - } public Registrations add(Registration registration) { validateCapacity(); diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index cd1a6cce7a..e8146e77fd 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -1,8 +1,12 @@ package nextstep.courses.domain.session; +import java.util.List; import nextstep.courses.domain.BaseEntity; import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.registration.Registration; +import nextstep.courses.domain.registration.Registrations; import nextstep.courses.domain.session.type.SessionType; +import nextstep.payments.domain.Payment; public class Session extends BaseEntity { private final Long courseId; diff --git a/src/main/java/nextstep/courses/infrastructure/CourseRepositoryImpl.java b/src/main/java/nextstep/courses/infrastructure/CourseRepositoryImpl.java new file mode 100644 index 0000000000..c3754181bf --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/CourseRepositoryImpl.java @@ -0,0 +1,29 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.course.Course; +import nextstep.courses.domain.course.CourseRepository; +import nextstep.courses.infrastructure.entity.CourseEntity; +import nextstep.courses.infrastructure.jdbc.CourseJdbcDao; +import nextstep.courses.infrastructure.mapper.CourseMapper; +import org.springframework.stereotype.Repository; + +@Repository("courseRepository") +public class CourseRepositoryImpl implements CourseRepository { + private final CourseJdbcDao courseJdbcDao; + + public CourseRepositoryImpl(CourseJdbcDao courseJdbcDao) { + this.courseJdbcDao = courseJdbcDao; + } + + @Override + public int save(Course course) { + CourseEntity entity = CourseMapper.toEntity(course); + return courseJdbcDao.save(entity); + } + + @Override + public Course findById(Long id) { + CourseEntity entity = courseJdbcDao.findById(id); + return CourseMapper.toDomain(entity); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java deleted file mode 100644 index 7489afc31d..0000000000 --- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java +++ /dev/null @@ -1,44 +0,0 @@ -package nextstep.courses.infrastructure; - -import nextstep.courses.domain.course.Course; -import nextstep.courses.domain.course.CourseRepository; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Repository; - -import java.sql.Timestamp; -import java.time.LocalDateTime; - -@Repository("courseRepository") -public class JdbcCourseRepository implements CourseRepository { - private JdbcOperations jdbcTemplate; - - public JdbcCourseRepository(JdbcOperations jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - @Override - public int save(Course course) { - String sql = "insert into course (title, creator_id, created_at) values(?, ?, ?)"; - return jdbcTemplate.update(sql, course.getTitle(), course.getCreatorId(), course.getCreatedAt()); - } - - @Override - public Course findById(Long id) { - String sql = "select id, title, creator_id, created_at, updated_at from course where id = ?"; - RowMapper rowMapper = (rs, rowNum) -> new Course( - rs.getLong(1), - rs.getString(2), - rs.getLong(3), - toLocalDateTime(rs.getTimestamp(4)), - toLocalDateTime(rs.getTimestamp(5))); - return jdbcTemplate.queryForObject(sql, rowMapper, id); - } - - private LocalDateTime toLocalDateTime(Timestamp timestamp) { - if (timestamp == null) { - return null; - } - return timestamp.toLocalDateTime(); - } -} diff --git a/src/main/java/nextstep/courses/infrastructure/RegistrationRepositoryImpl.java b/src/main/java/nextstep/courses/infrastructure/RegistrationRepositoryImpl.java new file mode 100644 index 0000000000..d3fa91bd78 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/RegistrationRepositoryImpl.java @@ -0,0 +1,44 @@ +package nextstep.courses.infrastructure; + +import java.util.List; +import java.util.stream.Collectors; +import nextstep.courses.domain.registration.Registration; +import nextstep.courses.domain.registration.RegistrationRepository; +import nextstep.courses.infrastructure.entity.RegistrationEntity; +import nextstep.courses.infrastructure.jdbc.RegistrationJdbcDao; +import nextstep.courses.infrastructure.mapper.RegistrationMapper; +import org.springframework.stereotype.Repository; + +@Repository("registrationRepository") +public class RegistrationRepositoryImpl implements RegistrationRepository { + private final RegistrationJdbcDao registrationJdbcDao; + + public RegistrationRepositoryImpl(RegistrationJdbcDao registrationJdbcDao) { + this.registrationJdbcDao = registrationJdbcDao; + } + + @Override + public int save(Registration registration) { + RegistrationEntity entity = RegistrationMapper.toEntity(registration); + return registrationJdbcDao.save(entity); + } + + @Override + public Registration findById(Long id) { + RegistrationEntity entity = registrationJdbcDao.findById(id); + return RegistrationMapper.toDomain(entity); + } + + @Override + public List findBySessionId(Long sessionId) { + List entities = registrationJdbcDao.findBySessionId(sessionId); + return entities.stream() + .map(RegistrationMapper::toDomain) + .collect(Collectors.toList()); + } + + @Override + public int countBySessionId(Long sessionId) { + return registrationJdbcDao.countBySessionId(sessionId); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/SessionImageRepositoryImpl.java b/src/main/java/nextstep/courses/infrastructure/SessionImageRepositoryImpl.java new file mode 100644 index 0000000000..715ff6bd2a --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/SessionImageRepositoryImpl.java @@ -0,0 +1,35 @@ +package nextstep.courses.infrastructure; + +import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.image.SessionImageRepository; +import nextstep.courses.infrastructure.entity.SessionCoverImageEntity; +import nextstep.courses.infrastructure.jdbc.SessionImageJdbcDao; +import nextstep.courses.infrastructure.mapper.SessionCoverImageMapper; +import org.springframework.stereotype.Repository; + +@Repository("sessionImageRepository") +public class SessionImageRepositoryImpl implements SessionImageRepository { + private final SessionImageJdbcDao sessionImageJdbcDao; + + public SessionImageRepositoryImpl(SessionImageJdbcDao sessionImageJdbcDao) { + this.sessionImageJdbcDao = sessionImageJdbcDao; + } + + @Override + public int save(SessionCoverImage image) { + SessionCoverImageEntity entity = SessionCoverImageMapper.toEntity(image); + return sessionImageJdbcDao.save(entity); + } + + @Override + public SessionCoverImage findById(Long id) { + SessionCoverImageEntity entity = sessionImageJdbcDao.findById(id); + return SessionCoverImageMapper.toDomain(entity); + } + + @Override + public SessionCoverImage findBySessionId(Long sessionId) { + SessionCoverImageEntity entity = sessionImageJdbcDao.findBySessionId(sessionId); + return SessionCoverImageMapper.toDomain(entity); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/SessionRepositoryImpl.java b/src/main/java/nextstep/courses/infrastructure/SessionRepositoryImpl.java new file mode 100644 index 0000000000..d90608703a --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/SessionRepositoryImpl.java @@ -0,0 +1,54 @@ +package nextstep.courses.infrastructure; + +import java.util.List; +import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.registration.Registrations; +import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionRepository; +import nextstep.courses.domain.session.SessionState; +import nextstep.courses.infrastructure.entity.RegistrationEntity; +import nextstep.courses.infrastructure.entity.SessionCoverImageEntity; +import nextstep.courses.infrastructure.entity.SessionEntity; +import nextstep.courses.infrastructure.jdbc.SessionJdbcDao; +import nextstep.courses.infrastructure.mapper.RegistrationMapper; +import nextstep.courses.infrastructure.mapper.SessionCoverImageMapper; +import nextstep.courses.infrastructure.mapper.SessionMapper; +import org.springframework.stereotype.Repository; + +@Repository("sessionRepository") +public class SessionRepositoryImpl implements SessionRepository { + private final SessionJdbcDao sessionJdbcDao; + + public SessionRepositoryImpl(SessionJdbcDao sessionJdbcDao) { + this.sessionJdbcDao = sessionJdbcDao; + } + + @Override + public int save(Session session) { + SessionEntity entity = SessionMapper.toEntity(session); + return sessionJdbcDao.save(entity); + } + + @Override + public Session findById(Long id) { + SessionEntity entity = sessionJdbcDao.findById(id); + List registrationEntities = sessionJdbcDao.findRegistrationsBySessionId(id); + int capacity = entity.getMaxCapacity() != null ? entity.getMaxCapacity() : -1; + Registrations registrations = RegistrationMapper.toDomain(registrationEntities, capacity); + SessionCoverImage coverImage = findCoverImageBySessionId(id); + return SessionMapper.toDomain(entity, registrations, coverImage); + } + + private SessionCoverImage findCoverImageBySessionId(Long sessionId) { + SessionCoverImageEntity entity = sessionJdbcDao.findCoverImageBySessionId(sessionId); + if (entity == null) { + return null; + } + return SessionCoverImageMapper.toDomain(entity); + } + + @Override + public int updateState(Long id, SessionState state) { + return sessionJdbcDao.updateState(id, state); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/entity/CourseEntity.java b/src/main/java/nextstep/courses/infrastructure/entity/CourseEntity.java new file mode 100644 index 0000000000..ef47e4582d --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/entity/CourseEntity.java @@ -0,0 +1,39 @@ +package nextstep.courses.infrastructure.entity; + +import java.time.LocalDateTime; + +public class CourseEntity { + private final Long id; + private final String title; + private final Long creatorId; + private final LocalDateTime createdAt; + private final LocalDateTime updatedAt; + + public CourseEntity(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.title = title; + this.creatorId = creatorId; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public Long getCreatorId() { + return creatorId; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/jdbc/CourseJdbcDao.java b/src/main/java/nextstep/courses/infrastructure/jdbc/CourseJdbcDao.java new file mode 100644 index 0000000000..1955e03da9 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/jdbc/CourseJdbcDao.java @@ -0,0 +1,44 @@ +package nextstep.courses.infrastructure.jdbc; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import nextstep.courses.infrastructure.entity.CourseEntity; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; + +@Component +public class CourseJdbcDao { + private final JdbcOperations jdbcTemplate; + + public CourseJdbcDao(JdbcOperations jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public int save(CourseEntity entity) { + String sql = "insert into course (title, creator_id, created_at) values(?, ?, ?)"; + return jdbcTemplate.update(sql, entity.getTitle(), entity.getCreatorId(), entity.getCreatedAt()); + } + + public CourseEntity findById(Long id) { + String sql = "select id, title, creator_id, created_at, updated_at from course where id = ?"; + return jdbcTemplate.queryForObject(sql, rowMapper(), id); + } + + private RowMapper rowMapper() { + return (rs, rowNum) -> new CourseEntity( + rs.getLong("id"), + rs.getString("title"), + rs.getLong("creator_id"), + toLocalDateTime(rs.getTimestamp("created_at")), + toLocalDateTime(rs.getTimestamp("updated_at")) + ); + } + + private LocalDateTime toLocalDateTime(Timestamp timestamp) { + if (timestamp == null) { + return null; + } + return timestamp.toLocalDateTime(); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcRegistrationRepository.java b/src/main/java/nextstep/courses/infrastructure/jdbc/RegistrationJdbcDao.java similarity index 54% rename from src/main/java/nextstep/courses/infrastructure/JdbcRegistrationRepository.java rename to src/main/java/nextstep/courses/infrastructure/jdbc/RegistrationJdbcDao.java index 3269636964..8112b75060 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcRegistrationRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/jdbc/RegistrationJdbcDao.java @@ -1,27 +1,21 @@ -package nextstep.courses.infrastructure; +package nextstep.courses.infrastructure.jdbc; import java.sql.Timestamp; import java.util.List; -import nextstep.courses.domain.registration.Registration; -import nextstep.courses.domain.registration.RegistrationRepository; import nextstep.courses.infrastructure.entity.RegistrationEntity; -import nextstep.courses.infrastructure.mapper.RegistrationMapper; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Component; -@Repository("registrationRepository") -public class JdbcRegistrationRepository implements RegistrationRepository { +@Component +public class RegistrationJdbcDao { private final JdbcOperations jdbcTemplate; - public JdbcRegistrationRepository(JdbcOperations jdbcTemplate) { + public RegistrationJdbcDao(JdbcOperations jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - @Override - public int save(Registration registration) { - RegistrationEntity entity = RegistrationMapper.toEntity(registration); - + public int save(RegistrationEntity entity) { String sql = "insert into registration (session_id, student_id, enrolled_at) values(?, ?, ?)"; return jdbcTemplate.update(sql, @@ -31,25 +25,16 @@ public int save(Registration registration) { ); } - @Override - public Registration findById(Long id) { + public RegistrationEntity findById(Long id) { String sql = "select id, session_id, student_id, enrolled_at from registration where id = ?"; - - RegistrationEntity entity = jdbcTemplate.queryForObject(sql, rowMapper(), id); - return RegistrationMapper.toDomain(entity); + return jdbcTemplate.queryForObject(sql, rowMapper(), id); } - @Override - public List findBySessionId(Long sessionId) { + public List findBySessionId(Long sessionId) { String sql = "select id, session_id, student_id, enrolled_at from registration where session_id = ?"; - - List entities = jdbcTemplate.query(sql, rowMapper(), sessionId); - return entities.stream() - .map(RegistrationMapper::toDomain) - .toList(); + return jdbcTemplate.query(sql, rowMapper(), sessionId); } - @Override public int countBySessionId(Long sessionId) { String sql = "select count(*) from registration where session_id = ?"; return jdbcTemplate.queryForObject(sql, Integer.class, sessionId); @@ -63,4 +48,4 @@ private RowMapper rowMapper() { rs.getTimestamp("enrolled_at").toLocalDateTime() ); } -} +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java b/src/main/java/nextstep/courses/infrastructure/jdbc/SessionImageJdbcDao.java similarity index 53% rename from src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java rename to src/main/java/nextstep/courses/infrastructure/jdbc/SessionImageJdbcDao.java index 0c0fbd501d..195f27c611 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionImageRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/jdbc/SessionImageJdbcDao.java @@ -1,25 +1,19 @@ -package nextstep.courses.infrastructure; +package nextstep.courses.infrastructure.jdbc; -import nextstep.courses.domain.image.SessionCoverImage; -import nextstep.courses.domain.image.SessionImageRepository; import nextstep.courses.infrastructure.entity.SessionCoverImageEntity; -import nextstep.courses.infrastructure.mapper.SessionCoverImageMapper; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Component; -@Repository("sessionImageRepository") -public class JdbcSessionImageRepository implements SessionImageRepository { +@Component +public class SessionImageJdbcDao { private final JdbcOperations jdbcTemplate; - public JdbcSessionImageRepository(JdbcOperations jdbcTemplate) { + public SessionImageJdbcDao(JdbcOperations jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - @Override - public int save(SessionCoverImage image) { - SessionCoverImageEntity entity = SessionCoverImageMapper.toEntity(image); - + public int save(SessionCoverImageEntity entity) { String sql = "insert into session_cover_image (session_id, width, height, extension, capacity) values(?, ?, ?, ?, ?)"; return jdbcTemplate.update(sql, @@ -31,20 +25,14 @@ public int save(SessionCoverImage image) { ); } - @Override - public SessionCoverImage findById(Long id) { + public SessionCoverImageEntity findById(Long id) { String sql = "select id, session_id, width, height, extension, capacity from session_cover_image where id = ?"; - - SessionCoverImageEntity entity = jdbcTemplate.queryForObject(sql, rowMapper(), id); - return SessionCoverImageMapper.toDomain(entity); + return jdbcTemplate.queryForObject(sql, rowMapper(), id); } - @Override - public SessionCoverImage findBySessionId(Long sessionId) { + public SessionCoverImageEntity findBySessionId(Long sessionId) { String sql = "select id, session_id, width, height, extension, capacity from session_cover_image where session_id = ?"; - - SessionCoverImageEntity entity = jdbcTemplate.queryForObject(sql, rowMapper(), sessionId); - return SessionCoverImageMapper.toDomain(entity); + return jdbcTemplate.queryForObject(sql, rowMapper(), sessionId); } private RowMapper rowMapper() { @@ -57,4 +45,4 @@ private RowMapper rowMapper() { rs.getLong("capacity") ); } -} +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java b/src/main/java/nextstep/courses/infrastructure/jdbc/SessionJdbcDao.java similarity index 59% rename from src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java rename to src/main/java/nextstep/courses/infrastructure/jdbc/SessionJdbcDao.java index c734f2662f..6cc9987a21 100644 --- a/src/main/java/nextstep/courses/infrastructure/JdbcSessionRepository.java +++ b/src/main/java/nextstep/courses/infrastructure/jdbc/SessionJdbcDao.java @@ -1,39 +1,29 @@ -package nextstep.courses.infrastructure; +package nextstep.courses.infrastructure.jdbc; import java.sql.Date; import java.time.LocalDate; import java.util.List; -import nextstep.courses.domain.image.SessionCoverImage; -import nextstep.courses.domain.registration.Registrations; -import nextstep.courses.domain.session.Session; -import nextstep.courses.domain.session.SessionRepository; import nextstep.courses.domain.session.SessionState; import nextstep.courses.infrastructure.entity.RegistrationEntity; -import nextstep.courses.infrastructure.entity.SessionEntity; import nextstep.courses.infrastructure.entity.SessionCoverImageEntity; -import nextstep.courses.infrastructure.mapper.RegistrationMapper; -import nextstep.courses.infrastructure.mapper.SessionMapper; -import nextstep.courses.infrastructure.mapper.SessionCoverImageMapper; +import nextstep.courses.infrastructure.entity.SessionEntity; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.RowMapper; -import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Component; -@Repository("sessionRepository") -public class JdbcSessionRepository implements SessionRepository { +@Component +public class SessionJdbcDao { private final JdbcOperations jdbcTemplate; - public JdbcSessionRepository(JdbcOperations jdbcTemplate) { + public SessionJdbcDao(JdbcOperations jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } - @Override - public int save(Session session) { - SessionEntity entity = SessionMapper.toEntity(session); - + public int save(SessionEntity entity) { String sql = "insert into session (course_id, term, start_day, end_day, state, type, max_capacity, tuition_fee, created_at) values(?, ?, ?, ?, ?, ?, ?, ?, ?)"; - int result = jdbcTemplate.update(sql, + return jdbcTemplate.update(sql, entity.getCourseId(), entity.getTerm(), Date.valueOf(entity.getStartDay()), @@ -44,71 +34,43 @@ public int save(Session session) { entity.getTuitionFee(), entity.getCreatedAt() ); - - if (session.getCoverImage() != null) { - saveCoverImage(session.getCoverImage()); - } - - return result; } - private void saveCoverImage(SessionCoverImage image) { - SessionCoverImageEntity entity = SessionCoverImageMapper.toEntity(image); + public int saveCoverImage(SessionCoverImageEntity image) { String sql = "insert into session_cover_image (session_id, width, height, extension, capacity) values(?, ?, ?, ?, ?)"; - jdbcTemplate.update(sql, - entity.getSessionId(), - entity.getWidth(), - entity.getHeight(), - entity.getExtension(), - entity.getCapacity() + return jdbcTemplate.update(sql, + image.getSessionId(), + image.getWidth(), + image.getHeight(), + image.getExtension(), + image.getCapacity() ); } - @Override - public Session findById(Long id) { + public SessionEntity findById(Long id) { String sql = "select id, course_id, term, start_day, end_day, state, type, max_capacity, tuition_fee, created_at from session where id = ?"; - - SessionEntity entity = jdbcTemplate.queryForObject(sql, sessionRowMapper(), id); - Registrations registrations = findRegistrationsBySessionId(id, entity.getMaxCapacity()); - SessionCoverImage coverImage = findCoverImageBySessionId(id); - return SessionMapper.toDomain(entity, registrations, coverImage); + return jdbcTemplate.queryForObject(sql, sessionRowMapper(), id); } - private SessionCoverImage findCoverImageBySessionId(Long sessionId) { + public SessionCoverImageEntity findCoverImageBySessionId(Long sessionId) { String sql = "select id, session_id, width, height, extension, capacity from session_cover_image where session_id = ?"; try { - SessionCoverImageEntity entity = jdbcTemplate.queryForObject(sql, coverImageRowMapper(), sessionId); - return SessionCoverImageMapper.toDomain(entity); + return jdbcTemplate.queryForObject(sql, coverImageRowMapper(), sessionId); } catch (EmptyResultDataAccessException e) { return null; } } - private RowMapper coverImageRowMapper() { - return (rs, rowNum) -> new SessionCoverImageEntity( - rs.getLong("id"), - rs.getLong("session_id"), - rs.getInt("width"), - rs.getInt("height"), - rs.getString("extension"), - rs.getLong("capacity") - ); + public List findRegistrationsBySessionId(Long sessionId) { + String sql = "select id, session_id, student_id, enrolled_at from registration where session_id = ?"; + return jdbcTemplate.query(sql, registrationRowMapper(), sessionId); } - @Override public int updateState(Long id, SessionState state) { String sql = "update session set state = ? where id = ?"; return jdbcTemplate.update(sql, state.name(), id); } - private Registrations findRegistrationsBySessionId(Long sessionId, Integer maxCapacity) { - String sql = "select id, session_id, student_id, enrolled_at from registration where session_id = ?"; - - List entities = jdbcTemplate.query(sql, registrationRowMapper(), sessionId); - int capacity = maxCapacity != null ? maxCapacity : -1; - return RegistrationMapper.toDomain(entities, capacity); - } - private RowMapper sessionRowMapper() { return (rs, rowNum) -> new SessionEntity( rs.getLong("id"), @@ -124,6 +86,17 @@ private RowMapper sessionRowMapper() { ); } + private RowMapper coverImageRowMapper() { + return (rs, rowNum) -> new SessionCoverImageEntity( + rs.getLong("id"), + rs.getLong("session_id"), + rs.getInt("width"), + rs.getInt("height"), + rs.getString("extension"), + rs.getLong("capacity") + ); + } + private RowMapper registrationRowMapper() { return (rs, rowNum) -> new RegistrationEntity( rs.getLong("id"), @@ -139,4 +112,4 @@ private LocalDate toLocalDate(Date date) { } return date.toLocalDate(); } -} +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/mapper/CourseMapper.java b/src/main/java/nextstep/courses/infrastructure/mapper/CourseMapper.java new file mode 100644 index 0000000000..bbbb938b83 --- /dev/null +++ b/src/main/java/nextstep/courses/infrastructure/mapper/CourseMapper.java @@ -0,0 +1,30 @@ +package nextstep.courses.infrastructure.mapper; + +import nextstep.courses.domain.course.Course; +import nextstep.courses.infrastructure.entity.CourseEntity; + +public class CourseMapper { + + private CourseMapper() { + } + + public static CourseEntity toEntity(Course course) { + return new CourseEntity( + course.getId(), + course.getTitle(), + course.getCreatorId(), + course.getCreatedAt(), + course.getUpdatedAt() + ); + } + + public static Course toDomain(CourseEntity entity) { + return new Course( + entity.getId(), + entity.getTitle(), + entity.getCreatorId(), + entity.getCreatedAt(), + entity.getUpdatedAt() + ); + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java b/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java index bedf5a183a..67911cc886 100644 --- a/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java +++ b/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java @@ -1,6 +1,7 @@ package nextstep.courses.infrastructure.mapper; import java.util.List; +import java.util.stream.Collectors; import nextstep.courses.domain.registration.Registration; import nextstep.courses.domain.registration.Registrations; import nextstep.courses.infrastructure.entity.RegistrationEntity; @@ -31,7 +32,7 @@ public static Registration toDomain(RegistrationEntity entity) { public static Registrations toDomain(List entities, int maxCapacity) { List registrations = entities.stream() .map(RegistrationMapper::toDomain) - .toList(); - return Registrations.of(registrations, maxCapacity); + .collect(Collectors.toList()); + return new Registrations(registrations, maxCapacity); } } diff --git a/src/main/java/nextstep/courses/service/RegistrationService.java b/src/main/java/nextstep/courses/service/RegistrationService.java index 24040b316e..189b934579 100644 --- a/src/main/java/nextstep/courses/service/RegistrationService.java +++ b/src/main/java/nextstep/courses/service/RegistrationService.java @@ -1,7 +1,9 @@ package nextstep.courses.service; +import java.util.List; import nextstep.courses.domain.registration.Registration; import nextstep.courses.domain.registration.RegistrationRepository; +import nextstep.courses.domain.session.Enrollment; import nextstep.courses.domain.session.Session; import nextstep.courses.domain.session.SessionRepository; import nextstep.payments.domain.Payment; @@ -26,4 +28,4 @@ public void register(Payment payment) { Registration registration = new Registration(payment.getSessionId(), payment.getNsUserId()); registrationRepository.save(registration); } -} +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/service/SessionManageService.java b/src/main/java/nextstep/courses/service/SessionManageService.java index cb601b10e5..c98cfdd8c4 100644 --- a/src/main/java/nextstep/courses/service/SessionManageService.java +++ b/src/main/java/nextstep/courses/service/SessionManageService.java @@ -46,4 +46,4 @@ public void closeSession(Long sessionId) { public Session findById(Long sessionId) { return sessionRepository.findById(sessionId); } -} +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java index 530375d3e1..6791a0195f 100644 --- a/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java +++ b/src/test/java/nextstep/courses/infrastructure/CourseRepositoryTest.java @@ -2,6 +2,7 @@ import nextstep.courses.domain.course.Course; import nextstep.courses.domain.course.CourseRepository; +import nextstep.courses.infrastructure.jdbc.CourseJdbcDao; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -23,7 +24,8 @@ public class CourseRepositoryTest { @BeforeEach void setUp() { - courseRepository = new JdbcCourseRepository(jdbcTemplate); + CourseJdbcDao courseJdbcDao = new CourseJdbcDao(jdbcTemplate); + courseRepository = new CourseRepositoryImpl(courseJdbcDao); } @Test From d9ec6afbe4e9131ff90b22d96ea150c3f15397a0 Mon Sep 17 00:00:00 2001 From: eunjeong Park Date: Wed, 10 Dec 2025 22:26:51 +0900 Subject: [PATCH 3/3] =?UTF-8?q?register=20=EB=A1=9C=EC=A7=81=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/registration/Registrations.java | 67 +++++------- .../courses/domain/session/Enrollment.java | 52 ++++----- .../courses/domain/session/Session.java | 100 +++++++++--------- .../courses/domain/session/SessionPolicy.java | 57 ++++++++++ .../policy/capacity/CapacityPolicy.java | 7 ++ .../policy/capacity/LimitedCapacity.java | 21 ++++ .../policy/capacity/UnlimitedCapacity.java | 10 ++ .../session/policy/tuition/FreeTuition.java | 11 ++ .../session/policy/tuition/PaidTuition.java | 20 ++++ .../session/policy/tuition/TuitionPolicy.java | 5 + .../courses/domain/session/type/FreeType.java | 25 ----- .../courses/domain/session/type/PaidType.java | 53 ---------- .../domain/session/type/SessionType.java | 9 -- .../infrastructure/SessionRepositoryImpl.java | 9 +- .../mapper/RegistrationMapper.java | 6 -- .../infrastructure/mapper/SessionMapper.java | 59 +++++++---- .../courses/service/RegistrationService.java | 7 +- .../courses/service/SessionManageService.java | 9 +- .../registration/RegistrationsTest.java | 20 ++-- .../domain/session/SessionBuilder.java | 21 ++-- .../courses/domain/session/SessionTest.java | 14 ++- .../session/policy/FreeTuitionTest.java | 22 ++++ .../PaidTuitionTest.java} | 13 +-- .../policy/capacity/LimitedCapacityTest.java | 45 ++++++++ .../capacity/UnlimitedCapacityTest.java | 25 +++++ .../domain/session/type/FreeTypeTest.java | 20 ---- 26 files changed, 411 insertions(+), 296 deletions(-) create mode 100644 src/main/java/nextstep/courses/domain/session/SessionPolicy.java create mode 100644 src/main/java/nextstep/courses/domain/session/policy/capacity/CapacityPolicy.java create mode 100644 src/main/java/nextstep/courses/domain/session/policy/capacity/LimitedCapacity.java create mode 100644 src/main/java/nextstep/courses/domain/session/policy/capacity/UnlimitedCapacity.java create mode 100644 src/main/java/nextstep/courses/domain/session/policy/tuition/FreeTuition.java create mode 100644 src/main/java/nextstep/courses/domain/session/policy/tuition/PaidTuition.java create mode 100644 src/main/java/nextstep/courses/domain/session/policy/tuition/TuitionPolicy.java delete mode 100644 src/main/java/nextstep/courses/domain/session/type/FreeType.java delete mode 100644 src/main/java/nextstep/courses/domain/session/type/PaidType.java delete mode 100644 src/main/java/nextstep/courses/domain/session/type/SessionType.java create mode 100644 src/test/java/nextstep/courses/domain/session/policy/FreeTuitionTest.java rename src/test/java/nextstep/courses/domain/session/{type/PaidTypeTest.java => policy/PaidTuitionTest.java} (60%) create mode 100644 src/test/java/nextstep/courses/domain/session/policy/capacity/LimitedCapacityTest.java create mode 100644 src/test/java/nextstep/courses/domain/session/policy/capacity/UnlimitedCapacityTest.java delete mode 100644 src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java diff --git a/src/main/java/nextstep/courses/domain/registration/Registrations.java b/src/main/java/nextstep/courses/domain/registration/Registrations.java index 6a75f755c5..9413270d13 100644 --- a/src/main/java/nextstep/courses/domain/registration/Registrations.java +++ b/src/main/java/nextstep/courses/domain/registration/Registrations.java @@ -4,50 +4,41 @@ import java.util.List; public class Registrations { - private static final int UNLIMITED = -1; - - private final List registrations; - private final int maxCapacity; - - public Registrations() { - this(new ArrayList<>(), UNLIMITED); - } - - public Registrations(int maxCapacity) { - this(new ArrayList<>(), maxCapacity); - } - - public Registrations(List registrations, int maxCapacity) { - this.registrations = registrations; - this.maxCapacity = maxCapacity; - } + private final List registrations; + public Registrations() { + this(new ArrayList<>()); + } - public Registrations add(Registration registration) { - validateCapacity(); - List newList = new ArrayList<>(registrations); - newList.add(registration); - return new Registrations(newList, maxCapacity); - } + public Registrations(List registrations) { + this.registrations = new ArrayList<>(registrations); + } - public void validateCapacity() { - if (isUnlimited()) { - return; + public Registrations add(Registration registration) { + validateDuplicateRegistration(registration); + List newList = new ArrayList<>(registrations); + newList.add(registration); + return new Registrations(newList); } - if (registrations.size() >= maxCapacity) { - throw new IllegalArgumentException("최대 수강 인원을 초과할 수 없습니다."); + + private void validateDuplicateRegistration(Registration registration) { + if (isAlreadyRegistered(registration.getStudentId())) { + throw new IllegalArgumentException("이미 수강신청한 학생입니다."); + } } - } - private boolean isUnlimited() { - return maxCapacity == UNLIMITED; - } + public boolean isAlreadyRegistered(long studentId) { + return registrations.stream() + .anyMatch(registration -> registration.getStudentId().equals(studentId)); + } - public int getMaxCapacity() { - return maxCapacity; - } + public void validateCapacity(int maxCapacity) { + if (count() > maxCapacity) { + throw new IllegalStateException("최대 수강 인원을 초과할 수 없습니다."); + } + } - public int count() { - return registrations.size(); - } + public int count() { + return registrations.size(); + } } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/Enrollment.java b/src/main/java/nextstep/courses/domain/session/Enrollment.java index 0479d1fc9f..65adea88d8 100644 --- a/src/main/java/nextstep/courses/domain/session/Enrollment.java +++ b/src/main/java/nextstep/courses/domain/session/Enrollment.java @@ -1,49 +1,35 @@ package nextstep.courses.domain.session; -import nextstep.courses.domain.session.type.FreeType; -import nextstep.courses.domain.session.type.SessionType; +import nextstep.courses.domain.registration.Registration; +import nextstep.courses.domain.registration.Registrations; public class Enrollment { - private SessionState state; - private final SessionType type; - - public Enrollment() { - this(SessionState.PREPARING, new FreeType()); - } - - public Enrollment(SessionType type) { - this(SessionState.PREPARING, type); - } - - public Enrollment(SessionState state, SessionType type) { + private final Session session; + private final SessionState state; + private final SessionPolicy policy; + private final Registrations registrations; + + public Enrollment(Session session, SessionState state, SessionPolicy policy, Registrations registrations) { + validateState(state); + this.session = session; this.state = state; - this.type = type; + this.policy = policy; + this.registrations = registrations; } - public void validateEnroll(long payAmount) { - validateState(); - type.validateEnroll(payAmount); - } + public Registration enroll(long payAmount, long userId) { + if (registrations.isAlreadyRegistered(userId)) { + throw new IllegalArgumentException("이미 수강신청한 학생입니다."); + } - public void open() { - this.state = state.open(); - } + policy.validate(payAmount, registrations); - public void close() { - this.state = state.close(); + return new Registration(session.getId(), userId); } - private void validateState() { + private void validateState(SessionState state) { if (!state.canEnroll()) { throw new IllegalStateException("모집중인 강의만 수강신청이 가능합니다."); } } - - public SessionState getState() { - return state; - } - - public SessionType getType() { - return type; - } } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/Session.java b/src/main/java/nextstep/courses/domain/session/Session.java index e8146e77fd..be99c04c5f 100644 --- a/src/main/java/nextstep/courses/domain/session/Session.java +++ b/src/main/java/nextstep/courses/domain/session/Session.java @@ -5,66 +5,70 @@ import nextstep.courses.domain.image.SessionCoverImage; import nextstep.courses.domain.registration.Registration; import nextstep.courses.domain.registration.Registrations; -import nextstep.courses.domain.session.type.SessionType; -import nextstep.payments.domain.Payment; public class Session extends BaseEntity { - private final Long courseId; - private final Term term; - private final SessionPeriod period; - private final Enrollment enrollment; - private final SessionCoverImage coverImage; + private final Long courseId; + private final Term term; + private final SessionPeriod period; + private final SessionCoverImage coverImage; + private final SessionPolicy sessionPolicy; + private SessionState state; - public Session(Long courseId, int term, String startDay, String endDay, SessionCoverImage coverImage) { - this(null, courseId, new Term(term), new SessionPeriod(startDay, endDay), new Enrollment(), coverImage); - } + public Session(Long courseId, int term, String startDay, String endDay, SessionCoverImage coverImage) { + this(null, courseId, new Term(term), new SessionPeriod(startDay, endDay), SessionState.PREPARING, new SessionPolicy(), coverImage); + } - public Session(Long courseId, int term, String startDay, String endDay, SessionType type, SessionCoverImage coverImage) { - this(null, courseId, new Term(term), new SessionPeriod(startDay, endDay), new Enrollment(type), coverImage); - } + public Session(Long courseId, int term, String startDay, String endDay, SessionPolicy sessionPolicy, SessionCoverImage coverImage) { + this(null, courseId, new Term(term), new SessionPeriod(startDay, endDay), SessionState.PREPARING, sessionPolicy, coverImage); + } - public Session(Long id, Long courseId, Term term, SessionPeriod period, Enrollment enrollment, SessionCoverImage coverImage) { - super(id); - this.courseId = courseId; - this.term = term; - this.period = period; - this.enrollment = enrollment; - this.coverImage = coverImage; - } + public Session(Long id, Long courseId, Term term, SessionPeriod period, SessionState state, SessionPolicy sessionPolicy, SessionCoverImage coverImage) { + super(id); + this.courseId = courseId; + this.term = term; + this.period = period; + this.state = state; + this.sessionPolicy = sessionPolicy; + this.coverImage = coverImage; + } - public void validateEnroll(long payAmount) { - enrollment.validateEnroll(payAmount); - } + public Enrollment enrollment(Registrations registrations) { + return new Enrollment(this, state, sessionPolicy, registrations); + } - public void open() { - enrollment.open(); - } + public Enrollment enrollment(List registrations) { + return new Enrollment(this, state, sessionPolicy, new Registrations(registrations)); + } - public void close() { - enrollment.close(); - } + public void open() { + this.state = state.open(); + } - public Long getCourseId() { - return courseId; - } + public void close() { + this.state = state.close(); + } - public Term getTerm() { - return term; - } + public Long getCourseId() { + return courseId; + } - public SessionPeriod getPeriod() { - return period; - } + public Term getTerm() { + return term; + } - public Enrollment getEnrollment() { - return enrollment; - } + public SessionPeriod getPeriod() { + return period; + } - public SessionState getState() { - return enrollment.getState(); - } + public SessionState getState() { + return state; + } - public SessionCoverImage getCoverImage() { - return coverImage; - } + public SessionPolicy getSessionPolicy() { + return sessionPolicy; + } + + public SessionCoverImage getCoverImage() { + return coverImage; + } } diff --git a/src/main/java/nextstep/courses/domain/session/SessionPolicy.java b/src/main/java/nextstep/courses/domain/session/SessionPolicy.java new file mode 100644 index 0000000000..691625e9b9 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/SessionPolicy.java @@ -0,0 +1,57 @@ +package nextstep.courses.domain.session; + +import nextstep.courses.domain.image.SessionCoverImage; +import nextstep.courses.domain.registration.Registrations; +import nextstep.courses.domain.session.policy.capacity.CapacityPolicy; +import nextstep.courses.domain.session.policy.capacity.LimitedCapacity; +import nextstep.courses.domain.session.policy.capacity.UnlimitedCapacity; +import nextstep.courses.domain.session.policy.tuition.FreeTuition; +import nextstep.courses.domain.session.policy.tuition.PaidTuition; +import nextstep.courses.domain.session.policy.tuition.TuitionPolicy; + +public class SessionPolicy { + private final TuitionPolicy tuitionPolicy; + private final CapacityPolicy capacityPolicy; + + public SessionPolicy() { + this(new FreeTuition(), new UnlimitedCapacity()); + } + + public SessionPolicy(long tuitionFee, int maxCapacity) { + this(new PaidTuition(tuitionFee), new LimitedCapacity(maxCapacity)); + } + + public SessionPolicy(TuitionPolicy tuitionPolicy, CapacityPolicy capacityPolicy) { + this.tuitionPolicy = tuitionPolicy; + this.capacityPolicy = capacityPolicy; + } + + public Session createSession(Long courseId, int term, String startDay, String endDay, SessionCoverImage coverImage) { + return new Session(courseId, term, startDay, endDay, this, coverImage); + } + + public Session createSession(Long id, Long courseId, Term term, SessionPeriod period, SessionState state, SessionCoverImage coverImage) { + return new Session(id, courseId, term, period, state, this, coverImage); + } + + public static SessionPolicy free() { + return new SessionPolicy(); + } + + public static SessionPolicy paid(long tuitionFee, int maxCapacity) { + return new SessionPolicy(tuitionFee, maxCapacity); + } + + public void validate(long payAmount, Registrations registrations) { + tuitionPolicy.validate(payAmount); + capacityPolicy.validate(registrations); + } + + public TuitionPolicy getTuitionPolicy() { + return tuitionPolicy; + } + + public CapacityPolicy getCapacityPolicy() { + return capacityPolicy; + } +} diff --git a/src/main/java/nextstep/courses/domain/session/policy/capacity/CapacityPolicy.java b/src/main/java/nextstep/courses/domain/session/policy/capacity/CapacityPolicy.java new file mode 100644 index 0000000000..28d04fc4fd --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/policy/capacity/CapacityPolicy.java @@ -0,0 +1,7 @@ +package nextstep.courses.domain.session.policy.capacity; + +import nextstep.courses.domain.registration.Registrations; + +public interface CapacityPolicy { + void validate(Registrations registrations); +} diff --git a/src/main/java/nextstep/courses/domain/session/policy/capacity/LimitedCapacity.java b/src/main/java/nextstep/courses/domain/session/policy/capacity/LimitedCapacity.java new file mode 100644 index 0000000000..4c24396aee --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/policy/capacity/LimitedCapacity.java @@ -0,0 +1,21 @@ +package nextstep.courses.domain.session.policy.capacity; + +import nextstep.courses.domain.registration.Registrations; + +public class LimitedCapacity implements CapacityPolicy { + private final int maxCapacity; + + public LimitedCapacity(int maxCapacity) { + this.maxCapacity = maxCapacity; + } + + @Override + public void validate(Registrations registrations) { + registrations.validateCapacity(maxCapacity); + } + + public int getMaxCapacity() { + return maxCapacity; + } +} + diff --git a/src/main/java/nextstep/courses/domain/session/policy/capacity/UnlimitedCapacity.java b/src/main/java/nextstep/courses/domain/session/policy/capacity/UnlimitedCapacity.java new file mode 100644 index 0000000000..704f6afe49 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/policy/capacity/UnlimitedCapacity.java @@ -0,0 +1,10 @@ +package nextstep.courses.domain.session.policy.capacity; + +import nextstep.courses.domain.registration.Registrations; + +public class UnlimitedCapacity implements CapacityPolicy { + + @Override + public void validate(Registrations registrations) { + } +} diff --git a/src/main/java/nextstep/courses/domain/session/policy/tuition/FreeTuition.java b/src/main/java/nextstep/courses/domain/session/policy/tuition/FreeTuition.java new file mode 100644 index 0000000000..2d5b1f2556 --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/policy/tuition/FreeTuition.java @@ -0,0 +1,11 @@ +package nextstep.courses.domain.session.policy.tuition; + + +public class FreeTuition implements TuitionPolicy { + + @Override + public void validate(long payAmount) { + // 무료 강의는 수강료 검증 없음, 정원 제한 없음 + } + +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/policy/tuition/PaidTuition.java b/src/main/java/nextstep/courses/domain/session/policy/tuition/PaidTuition.java new file mode 100644 index 0000000000..66c4f65a5a --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/policy/tuition/PaidTuition.java @@ -0,0 +1,20 @@ +package nextstep.courses.domain.session.policy.tuition; + +public class PaidTuition implements TuitionPolicy { + private final long tuitionFee; + + public PaidTuition(long tuitionFee) { + this.tuitionFee = tuitionFee; + } + + @Override + public void validate(long payAmount) { + if (payAmount != tuitionFee) { + throw new IllegalArgumentException("수강료와 지불한 금액이 정확히 일치해야 합니다."); + } + } + + public long getTuitionFee() { + return tuitionFee; + } +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/policy/tuition/TuitionPolicy.java b/src/main/java/nextstep/courses/domain/session/policy/tuition/TuitionPolicy.java new file mode 100644 index 0000000000..57f416186c --- /dev/null +++ b/src/main/java/nextstep/courses/domain/session/policy/tuition/TuitionPolicy.java @@ -0,0 +1,5 @@ +package nextstep.courses.domain.session.policy.tuition; + +public interface TuitionPolicy { + void validate(long payAmount); +} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/type/FreeType.java b/src/main/java/nextstep/courses/domain/session/type/FreeType.java deleted file mode 100644 index 19bf953be1..0000000000 --- a/src/main/java/nextstep/courses/domain/session/type/FreeType.java +++ /dev/null @@ -1,25 +0,0 @@ -package nextstep.courses.domain.session.type; - -import nextstep.courses.domain.registration.Registrations; - -public class FreeType implements SessionType { - private final Registrations registrations; - - public FreeType() { - this(new Registrations()); - } - - public FreeType(Registrations registrations) { - this.registrations = registrations; - } - - @Override - public void validateEnroll(long payAmount) { - // 무료 강의는 수강료 검증 없음, 정원 제한 없음 - } - - @Override - public Registrations getRegistrations() { - return registrations; - } -} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/type/PaidType.java b/src/main/java/nextstep/courses/domain/session/type/PaidType.java deleted file mode 100644 index ccf685d6dc..0000000000 --- a/src/main/java/nextstep/courses/domain/session/type/PaidType.java +++ /dev/null @@ -1,53 +0,0 @@ -package nextstep.courses.domain.session.type; - -import java.util.Objects; -import nextstep.courses.domain.registration.Registrations; - -public class PaidType implements SessionType { - private final long tuitionFee; - private final Registrations registrations; - - public PaidType(int maxCapacity, long tuitionFee) { - this(tuitionFee, new Registrations(maxCapacity)); - } - - public PaidType(long tuitionFee, Registrations registrations) { - this.tuitionFee = tuitionFee; - this.registrations = registrations; - } - - @Override - public void validateEnroll(long payAmount) { - validateTuitionFee(payAmount); - registrations.validateCapacity(); - } - - private void validateTuitionFee(long payAmount) { - if (payAmount != tuitionFee) { - throw new IllegalArgumentException("수강료와 지불한 금액이 정확히 일치해야 합니다."); - } - } - - public long getTuitionFee() { - return tuitionFee; - } - - @Override - public Registrations getRegistrations() { - return registrations; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PaidType that = (PaidType) o; - return tuitionFee == that.tuitionFee - && Objects.equals(registrations, that.registrations); - } - - @Override - public int hashCode() { - return Objects.hash(tuitionFee, registrations); - } -} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/domain/session/type/SessionType.java b/src/main/java/nextstep/courses/domain/session/type/SessionType.java deleted file mode 100644 index a162cc4eb6..0000000000 --- a/src/main/java/nextstep/courses/domain/session/type/SessionType.java +++ /dev/null @@ -1,9 +0,0 @@ -package nextstep.courses.domain.session.type; - -import nextstep.courses.domain.registration.Registrations; - -public interface SessionType { - void validateEnroll(long payAmount); - - Registrations getRegistrations(); -} \ No newline at end of file diff --git a/src/main/java/nextstep/courses/infrastructure/SessionRepositoryImpl.java b/src/main/java/nextstep/courses/infrastructure/SessionRepositoryImpl.java index d90608703a..f0cda8b5c4 100644 --- a/src/main/java/nextstep/courses/infrastructure/SessionRepositoryImpl.java +++ b/src/main/java/nextstep/courses/infrastructure/SessionRepositoryImpl.java @@ -1,16 +1,12 @@ package nextstep.courses.infrastructure; -import java.util.List; import nextstep.courses.domain.image.SessionCoverImage; -import nextstep.courses.domain.registration.Registrations; import nextstep.courses.domain.session.Session; import nextstep.courses.domain.session.SessionRepository; import nextstep.courses.domain.session.SessionState; -import nextstep.courses.infrastructure.entity.RegistrationEntity; import nextstep.courses.infrastructure.entity.SessionCoverImageEntity; import nextstep.courses.infrastructure.entity.SessionEntity; import nextstep.courses.infrastructure.jdbc.SessionJdbcDao; -import nextstep.courses.infrastructure.mapper.RegistrationMapper; import nextstep.courses.infrastructure.mapper.SessionCoverImageMapper; import nextstep.courses.infrastructure.mapper.SessionMapper; import org.springframework.stereotype.Repository; @@ -32,11 +28,8 @@ public int save(Session session) { @Override public Session findById(Long id) { SessionEntity entity = sessionJdbcDao.findById(id); - List registrationEntities = sessionJdbcDao.findRegistrationsBySessionId(id); - int capacity = entity.getMaxCapacity() != null ? entity.getMaxCapacity() : -1; - Registrations registrations = RegistrationMapper.toDomain(registrationEntities, capacity); SessionCoverImage coverImage = findCoverImageBySessionId(id); - return SessionMapper.toDomain(entity, registrations, coverImage); + return SessionMapper.toDomain(entity, coverImage); } private SessionCoverImage findCoverImageBySessionId(Long sessionId) { diff --git a/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java b/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java index 67911cc886..e2780568a8 100644 --- a/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java +++ b/src/main/java/nextstep/courses/infrastructure/mapper/RegistrationMapper.java @@ -29,10 +29,4 @@ public static Registration toDomain(RegistrationEntity entity) { ); } - public static Registrations toDomain(List entities, int maxCapacity) { - List registrations = entities.stream() - .map(RegistrationMapper::toDomain) - .collect(Collectors.toList()); - return new Registrations(registrations, maxCapacity); - } } diff --git a/src/main/java/nextstep/courses/infrastructure/mapper/SessionMapper.java b/src/main/java/nextstep/courses/infrastructure/mapper/SessionMapper.java index ec62b75e96..de1e935ba2 100644 --- a/src/main/java/nextstep/courses/infrastructure/mapper/SessionMapper.java +++ b/src/main/java/nextstep/courses/infrastructure/mapper/SessionMapper.java @@ -1,15 +1,17 @@ package nextstep.courses.infrastructure.mapper; import nextstep.courses.domain.image.SessionCoverImage; -import nextstep.courses.domain.registration.Registrations; -import nextstep.courses.domain.session.Enrollment; import nextstep.courses.domain.session.Session; import nextstep.courses.domain.session.SessionPeriod; +import nextstep.courses.domain.session.SessionPolicy; import nextstep.courses.domain.session.SessionState; import nextstep.courses.domain.session.Term; -import nextstep.courses.domain.session.type.FreeType; -import nextstep.courses.domain.session.type.PaidType; -import nextstep.courses.domain.session.type.SessionType; +import nextstep.courses.domain.session.policy.capacity.CapacityPolicy; +import nextstep.courses.domain.session.policy.capacity.LimitedCapacity; +import nextstep.courses.domain.session.policy.capacity.UnlimitedCapacity; +import nextstep.courses.domain.session.policy.tuition.FreeTuition; +import nextstep.courses.domain.session.policy.tuition.PaidTuition; +import nextstep.courses.domain.session.policy.tuition.TuitionPolicy; import nextstep.courses.infrastructure.entity.SessionEntity; public class SessionMapper { @@ -18,27 +20,32 @@ private SessionMapper() { } public static SessionEntity toEntity(Session session) { - Enrollment enrollment = session.getEnrollment(); - SessionType type = enrollment.getType(); + SessionPolicy sessionPolicy = session.getSessionPolicy(); + TuitionPolicy tuitionPolicy = sessionPolicy.getTuitionPolicy(); + CapacityPolicy capacityPolicy = sessionPolicy.getCapacityPolicy(); Integer maxCapacity = null; Long tuitionFee = null; String typeName = "FREE"; - if (type instanceof PaidType) { - PaidType paidType = (PaidType) type; - maxCapacity = paidType.getRegistrations().getMaxCapacity(); - tuitionFee = paidType.getTuitionFee(); + if (tuitionPolicy instanceof PaidTuition) { + PaidTuition paidTuition = (PaidTuition) tuitionPolicy; + tuitionFee = paidTuition.getTuitionFee(); typeName = "PAID"; } + if (capacityPolicy instanceof LimitedCapacity) { + LimitedCapacity limitedCapacity = (LimitedCapacity) capacityPolicy; + maxCapacity = limitedCapacity.getMaxCapacity(); + } + return new SessionEntity( session.getId(), session.getCourseId(), session.getTerm().getValue(), session.getPeriod().startDay(), session.getPeriod().endDay(), - enrollment.getState().name(), + session.getState().name(), typeName, maxCapacity, tuitionFee, @@ -46,26 +53,38 @@ public static SessionEntity toEntity(Session session) { ); } - public static Session toDomain(SessionEntity entity, Registrations registrations, SessionCoverImage coverImage) { + public static Session toDomain(SessionEntity entity, SessionCoverImage coverImage) { SessionPeriod period = new SessionPeriod(entity.getStartDay(), entity.getEndDay()); SessionState state = SessionState.valueOf(entity.getState()); - SessionType type = createSessionType(entity, registrations); - Enrollment enrollment = new Enrollment(state, type); + SessionPolicy sessionPolicy = createSessionPolicy(entity); - return new Session( + return sessionPolicy.createSession( entity.getId(), entity.getCourseId(), new Term(entity.getTerm()), period, - enrollment, + state, coverImage ); } - private static SessionType createSessionType(SessionEntity entity, Registrations registrations) { + private static SessionPolicy createSessionPolicy(SessionEntity entity) { + TuitionPolicy tuitionPolicy = createTuitionPolicy(entity); + CapacityPolicy capacityPolicy = createCapacityPolicy(entity); + return new SessionPolicy(tuitionPolicy, capacityPolicy); + } + + private static TuitionPolicy createTuitionPolicy(SessionEntity entity) { if ("PAID".equals(entity.getType())) { - return new PaidType(entity.getTuitionFee(), registrations); + return new PaidTuition(entity.getTuitionFee()); + } + return new FreeTuition(); + } + + private static CapacityPolicy createCapacityPolicy(SessionEntity entity) { + if (entity.getMaxCapacity() != null) { + return new LimitedCapacity(entity.getMaxCapacity()); } - return new FreeType(registrations); + return new UnlimitedCapacity(); } } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/service/RegistrationService.java b/src/main/java/nextstep/courses/service/RegistrationService.java index 189b934579..3698239750 100644 --- a/src/main/java/nextstep/courses/service/RegistrationService.java +++ b/src/main/java/nextstep/courses/service/RegistrationService.java @@ -3,6 +3,7 @@ import java.util.List; import nextstep.courses.domain.registration.Registration; import nextstep.courses.domain.registration.RegistrationRepository; +import nextstep.courses.domain.registration.Registrations; import nextstep.courses.domain.session.Enrollment; import nextstep.courses.domain.session.Session; import nextstep.courses.domain.session.SessionRepository; @@ -23,9 +24,11 @@ public RegistrationService(SessionRepository sessionRepository, public void register(Payment payment) { Session session = sessionRepository.findById(payment.getSessionId()); - session.validateEnroll(payment.getAmount()); + List registered = registrationRepository.findBySessionId(session.getId()); + + Enrollment enrollment = session.enrollment(registered); + Registration registration = enrollment.enroll(payment.getAmount(), payment.getNsUserId()); - Registration registration = new Registration(payment.getSessionId(), payment.getNsUserId()); registrationRepository.save(registration); } } \ No newline at end of file diff --git a/src/main/java/nextstep/courses/service/SessionManageService.java b/src/main/java/nextstep/courses/service/SessionManageService.java index c98cfdd8c4..096faf9241 100644 --- a/src/main/java/nextstep/courses/service/SessionManageService.java +++ b/src/main/java/nextstep/courses/service/SessionManageService.java @@ -2,8 +2,8 @@ import nextstep.courses.domain.image.SessionCoverImage; import nextstep.courses.domain.session.Session; +import nextstep.courses.domain.session.SessionPolicy; import nextstep.courses.domain.session.SessionRepository; -import nextstep.courses.domain.session.type.PaidType; import org.springframework.stereotype.Service; @Service @@ -18,7 +18,8 @@ public SessionManageService(SessionRepository sessionRepository) { public void createFreeSession(Long courseId, int term, String startDay, String endDay, int imageWidth, int imageHeight, String imageExtension, long imageBytes) { SessionCoverImage coverImage = new SessionCoverImage(null, imageWidth, imageHeight, imageExtension, imageBytes); - Session session = new Session(courseId, term, startDay, endDay, coverImage); + SessionPolicy sessionPolicy = SessionPolicy.free(); + Session session = sessionPolicy.createSession(courseId, term, startDay, endDay, coverImage); sessionRepository.save(session); } @@ -26,8 +27,8 @@ public void createPaidSession(Long courseId, int term, String startDay, String e int maxCapacity, long tuitionFee, int imageWidth, int imageHeight, String imageExtension, long imageBytes) { SessionCoverImage coverImage = new SessionCoverImage(null, imageWidth, imageHeight, imageExtension, imageBytes); - PaidType paidType = new PaidType(maxCapacity, tuitionFee); - Session session = new Session(courseId, term, startDay, endDay, paidType, coverImage); + SessionPolicy sessionPolicy = SessionPolicy.paid(tuitionFee, maxCapacity); + Session session = sessionPolicy.createSession(courseId, term, startDay, endDay, coverImage); sessionRepository.save(session); } diff --git a/src/test/java/nextstep/courses/domain/registration/RegistrationsTest.java b/src/test/java/nextstep/courses/domain/registration/RegistrationsTest.java index a85c3e112c..e094c5d87e 100644 --- a/src/test/java/nextstep/courses/domain/registration/RegistrationsTest.java +++ b/src/test/java/nextstep/courses/domain/registration/RegistrationsTest.java @@ -1,7 +1,6 @@ package nextstep.courses.domain.registration; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; @@ -9,26 +8,27 @@ class RegistrationsTest { @Test - void 최대수강인원_초과하면_예외() { - Registrations registrations = new Registrations(1); + void 중복_등록하면_예외() { + Registrations registrations = new Registrations(); registrations = registrations.add(new Registration(1L, 1L)); Registrations finalRegistrations = registrations; - assertThatThrownBy(() -> finalRegistrations.add(new Registration(1L, 2L))) + assertThatThrownBy(() -> finalRegistrations.add(new Registration(1L, 1L))) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("최대 수강 인원을 초과할 수 없습니다."); + .hasMessage("이미 수강신청한 학생입니다."); } @Test - void 최대수강인원_이하면_등록_가능() { - Registrations registrations = new Registrations(2); + void 새로운_학생_등록_가능() { + Registrations registrations = new Registrations(); - assertThatCode(() -> registrations.add(new Registration(1L, 1L))) - .doesNotThrowAnyException(); + Registrations updated = registrations.add(new Registration(1L, 1L)); + + assertThat(updated.count()).isEqualTo(1); } @Test - void 무제한이면_수강인원_제한없음() { + void 여러_학생_등록_가능() { Registrations registrations = new Registrations(); for (long i = 0; i < 1000; i++) { diff --git a/src/test/java/nextstep/courses/domain/session/SessionBuilder.java b/src/test/java/nextstep/courses/domain/session/SessionBuilder.java index 745b3c27c8..593953ca13 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionBuilder.java +++ b/src/test/java/nextstep/courses/domain/session/SessionBuilder.java @@ -1,9 +1,6 @@ package nextstep.courses.domain.session; import nextstep.courses.domain.image.SessionCoverImage; -import nextstep.courses.domain.session.type.FreeType; -import nextstep.courses.domain.session.type.PaidType; -import nextstep.courses.domain.session.type.SessionType; public class SessionBuilder { private Long id = null; @@ -11,7 +8,7 @@ public class SessionBuilder { private Term term = new Term(1); private SessionPeriod period = new SessionPeriod("2025-01-01", "2025-01-31"); private SessionState state = SessionState.PREPARING; - private SessionType type = new FreeType(); + private SessionPolicy sessionPolicy = new SessionPolicy(); private SessionCoverImage coverImage = null; public static SessionBuilder aSession() { @@ -43,8 +40,8 @@ public SessionBuilder withState(SessionState state) { return this; } - public SessionBuilder withType(SessionType type) { - this.type = type; + public SessionBuilder withSessionPolicy(SessionPolicy sessionPolicy) { + this.sessionPolicy = sessionPolicy; return this; } @@ -58,13 +55,17 @@ public SessionBuilder recruiting() { return this; } - public SessionBuilder paid(int maxCapacity, long tuitionFee) { - this.type = new PaidType(maxCapacity, tuitionFee); + public SessionBuilder paid(long tuitionFee, int maxCapacity) { + this.sessionPolicy = SessionPolicy.paid(tuitionFee, maxCapacity); + return this; + } + + public SessionBuilder free() { + this.sessionPolicy = SessionPolicy.free(); return this; } public Session build() { - Enrollment enrollment = new Enrollment(state, type); - return new Session(id, courseId, term, period, enrollment, coverImage); + return sessionPolicy.createSession(id, courseId, term, period, state, coverImage); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/SessionTest.java b/src/test/java/nextstep/courses/domain/session/SessionTest.java index f3414e7afc..6d0e8b05ad 100644 --- a/src/test/java/nextstep/courses/domain/session/SessionTest.java +++ b/src/test/java/nextstep/courses/domain/session/SessionTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import nextstep.courses.domain.registration.Registrations; import org.junit.jupiter.api.Test; class SessionTest { @@ -11,15 +12,18 @@ class SessionTest { @Test void 모집중일때_수강신청_가능() { Session session = aSession().recruiting().build(); + Registrations registrations = new Registrations(); + Enrollment enrollment = session.enrollment(registrations); - assertDoesNotThrow(() -> session.validateEnroll(0)); + assertDoesNotThrow(() -> enrollment.enroll(0, 1L)); } @Test void 모집중이_아닐때_수강신청하면_예외() { Session session = aSession().build(); + Registrations registrations = new Registrations(); - assertThatThrownBy(() -> session.validateEnroll(0)) + assertThatThrownBy(() -> session.enrollment(registrations)) .isInstanceOf(IllegalStateException.class) .hasMessage("모집중인 강의만 수강신청이 가능합니다."); } @@ -46,10 +50,12 @@ class SessionTest { void 유료강의_수강료_불일치시_예외() { Session session = aSession() .recruiting() - .paid(10, 10000L) + .paid(10000L, 10) .build(); + Registrations registrations = new Registrations(); + Enrollment enrollment = session.enrollment(registrations); - assertThatThrownBy(() -> session.validateEnroll(5000L)) + assertThatThrownBy(() -> enrollment.enroll(5000L, 1L)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("수강료와 지불한 금액이 정확히 일치해야 합니다."); } diff --git a/src/test/java/nextstep/courses/domain/session/policy/FreeTuitionTest.java b/src/test/java/nextstep/courses/domain/session/policy/FreeTuitionTest.java new file mode 100644 index 0000000000..004eabb447 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/policy/FreeTuitionTest.java @@ -0,0 +1,22 @@ +package nextstep.courses.domain.session.policy; + +import static org.assertj.core.api.Assertions.assertThatCode; + +import nextstep.courses.domain.session.policy.tuition.FreeTuition; +import nextstep.courses.domain.session.policy.tuition.TuitionPolicy; +import org.junit.jupiter.api.Test; + +class FreeTuitionTest { + + @Test + void 금액_상관없이_수강가능() { + TuitionPolicy type = new FreeTuition(); + + assertThatCode(() -> type.validate(0)) + .doesNotThrowAnyException(); + assertThatCode(() -> type.validate(100)) + .doesNotThrowAnyException(); + assertThatCode(() -> type.validate(999999)) + .doesNotThrowAnyException(); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java b/src/test/java/nextstep/courses/domain/session/policy/PaidTuitionTest.java similarity index 60% rename from src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java rename to src/test/java/nextstep/courses/domain/session/policy/PaidTuitionTest.java index c57a819735..d6bbaa5d17 100644 --- a/src/test/java/nextstep/courses/domain/session/type/PaidTypeTest.java +++ b/src/test/java/nextstep/courses/domain/session/policy/PaidTuitionTest.java @@ -1,26 +1,27 @@ -package nextstep.courses.domain.session.type; +package nextstep.courses.domain.session.policy; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import nextstep.courses.domain.session.policy.tuition.PaidTuition; import org.junit.jupiter.api.Test; -class PaidTypeTest { +class PaidTuitionTest { @Test void 결제금액과_수강료가_동일하지_않으면_예외() { - PaidType type = new PaidType(300, 1000); + PaidTuition type = new PaidTuition(1000); - assertThatThrownBy(() -> type.validateEnroll(999)) + assertThatThrownBy(() -> type.validate(999)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("수강료와 지불한 금액이 정확히 일치해야 합니다."); } @Test void 결제금액과_수강료가_동일하면_성공() { - PaidType type = new PaidType(300, 1000); + PaidTuition type = new PaidTuition(1000); - assertThatCode(() -> type.validateEnroll(1000)) + assertThatCode(() -> type.validate(1000)) .doesNotThrowAnyException(); } } \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/policy/capacity/LimitedCapacityTest.java b/src/test/java/nextstep/courses/domain/session/policy/capacity/LimitedCapacityTest.java new file mode 100644 index 0000000000..8e02419ca5 --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/policy/capacity/LimitedCapacityTest.java @@ -0,0 +1,45 @@ +package nextstep.courses.domain.session.policy.capacity; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import nextstep.courses.domain.registration.Registration; +import nextstep.courses.domain.registration.Registrations; +import org.junit.jupiter.api.Test; + +class LimitedCapacityTest { + + @Test + void 현재인원이_최대수강인원을_초과하면_예외() { + LimitedCapacity capacity = new LimitedCapacity(2); + Registrations registrations = new Registrations() + .add(new Registration(1L, 1L)) + .add(new Registration(1L, 2L)) + .add(new Registration(1L, 3L)); + + assertThatThrownBy(() -> capacity.validate(registrations)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("최대 수강 인원을 초과할 수 없습니다."); + } + + @Test + void 현재인원이_최대수강인원과_같으면_검증_통과() { + LimitedCapacity capacity = new LimitedCapacity(2); + Registrations registrations = new Registrations() + .add(new Registration(1L, 1L)) + .add(new Registration(1L, 2L)); + + assertThatCode(() -> capacity.validate(registrations)) + .doesNotThrowAnyException(); + } + + @Test + void 현재인원이_최대수강인원_미만이면_검증_통과() { + LimitedCapacity capacity = new LimitedCapacity(3); + Registrations registrations = new Registrations() + .add(new Registration(1L, 1L)); + + assertThatCode(() -> capacity.validate(registrations)) + .doesNotThrowAnyException(); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/policy/capacity/UnlimitedCapacityTest.java b/src/test/java/nextstep/courses/domain/session/policy/capacity/UnlimitedCapacityTest.java new file mode 100644 index 0000000000..532678374b --- /dev/null +++ b/src/test/java/nextstep/courses/domain/session/policy/capacity/UnlimitedCapacityTest.java @@ -0,0 +1,25 @@ +package nextstep.courses.domain.session.policy.capacity; + +import static org.assertj.core.api.Assertions.assertThatCode; + +import nextstep.courses.domain.registration.Registration; +import nextstep.courses.domain.registration.Registrations; +import org.junit.jupiter.api.Test; + +class UnlimitedCapacityTest { + + @Test + void 무제한이면_수강인원_제한없음() { + UnlimitedCapacity capacity = new UnlimitedCapacity(); + Registrations registrations = new Registrations(); + + for (long i = 0; i < 1000; i++) { + registrations = registrations.add(new Registration(1L, i)); + capacity.validate(registrations); + } + + Registrations finalRegistrations = registrations; + assertThatCode(() -> capacity.validate(finalRegistrations)) + .doesNotThrowAnyException(); + } +} \ No newline at end of file diff --git a/src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java b/src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java deleted file mode 100644 index 3a424c0ebc..0000000000 --- a/src/test/java/nextstep/courses/domain/session/type/FreeTypeTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package nextstep.courses.domain.session.type; - -import static org.assertj.core.api.Assertions.assertThatCode; - -import org.junit.jupiter.api.Test; - -class FreeTypeTest { - - @Test - void 금액_상관없이_수강가능() { - SessionType type = new FreeType(); - - assertThatCode(() -> type.validateEnroll(0)) - .doesNotThrowAnyException(); - assertThatCode(() -> type.validateEnroll(100)) - .doesNotThrowAnyException(); - assertThatCode(() -> type.validateEnroll(999999)) - .doesNotThrowAnyException(); - } -} \ No newline at end of file