Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

수강신청 Step2 - 수강신청(도메인 모델) #393

Open
wants to merge 13 commits into
base: parkje0927
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion src/main/java/nextstep/courses/domain/Course.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package nextstep.courses.domain;

import nextstep.sessions.domain.Session;
import nextstep.sessions.domain.Sessions;

import java.time.LocalDateTime;
import java.util.List;

public class Course {
private Long id;
Expand All @@ -9,6 +13,10 @@ public class Course {

private Long creatorId;

private int cardinalNumber;

private Sessions sessions;

private LocalDateTime createdAt;

private LocalDateTime updatedAt;
Expand All @@ -17,7 +25,18 @@ public Course() {
}

public Course(String title, Long creatorId) {
this(0L, title, creatorId, LocalDateTime.now(), null);
this(0L, title, creatorId, 1, null, LocalDateTime.now(), null);
}

public Course(Long id, String title, Long creatorId, int cardinalNumber, Sessions sessions,
Copy link
Contributor

Choose a reason for hiding this comment

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

주생성자를 생성자에 마지막에 구현하는 것이 관례

LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.title = title;
this.creatorId = creatorId;
this.cardinalNumber = cardinalNumber;
this.sessions = sessions;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}

public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) {
Expand All @@ -40,12 +59,18 @@ public LocalDateTime getCreatedAt() {
return createdAt;
}

public List<Session> getPossibleSessionList() {
return this.sessions.getPossibleSessionList();
}

Copy link
Contributor

Choose a reason for hiding this comment

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

새로운 Session을 추가하는 addSession(Session session)과 같은 메서드도 추가하는 것은 어떨까?

@Override
public String toString() {
return "Course{" +
"id=" + id +
", title='" + title + '\'' +
", creatorId=" + creatorId +
", cardinalNumber=" + cardinalNumber +
", sessions=" + sessions +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/nextstep/courses/domain/CourseRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ public interface CourseRepository {
int save(Course course);

Course findById(Long id);

Course findByCardinalNumber(int order);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import nextstep.courses.domain.Course;
import nextstep.courses.domain.CourseRepository;
import nextstep.sessions.domain.Sessions;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
Expand Down Expand Up @@ -35,6 +36,20 @@ public Course findById(Long id) {
return jdbcTemplate.queryForObject(sql, rowMapper, id);
}

@Override
public Course findByCardinalNumber(int cardinalNumber) {
String sql = "select id, title, creator_id, cardinal_number, sessions, created_at, updated_at from course where cardinal_number = ?";
RowMapper<Course> rowMapper = (rs, rowNum) -> new Course(
rs.getLong(1),
rs.getString(2),
rs.getLong(3),
rs.getInt(4),
(Sessions) rs.getObject(5),
toLocalDateTime(rs.getTimestamp(6)),
toLocalDateTime(rs.getTimestamp(7)));
return jdbcTemplate.queryForObject(sql, rowMapper, cardinalNumber);
}

private LocalDateTime toLocalDateTime(Timestamp timestamp) {
if (timestamp == null) {
return null;
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/nextstep/courses/service/CourseService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package nextstep.courses.service;

import nextstep.courses.domain.Course;
import nextstep.courses.infrastructure.JdbcCourseRepository;

public class CourseService {

private final JdbcCourseRepository jdbcCourseRepository;

public CourseService(JdbcCourseRepository jdbcCourseRepository) {
this.jdbcCourseRepository = jdbcCourseRepository;
}

public Course findByCardinalNumber(int cardinalNumber) {
return jdbcCourseRepository.findByCardinalNumber(cardinalNumber);
}
}
17 changes: 17 additions & 0 deletions src/main/java/nextstep/docs/step2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### step2 수강 신청(도메인 모델)

- [x] 과정(Course)은 기수 단위로 운영하고, 여러 개의 강의(Session)를 가진다.
- [X] 강의는 시작일과 종료일을 가진다.
- [X] 강의는 강의 커버 이미지 정보를 가진다.
- [X] 이미지 크기는 1MB 이하
- [X] 이미지 타입은 gif, jpg, jpeg, png, svg 만 허용
- [X] 이미지의 width 는 300 픽셀, height 은 200 픽셀 이상이어야 하고 비율은 3:2
- [X] 강의는 무료 강의와 유료 강의로 나뉜다.
- [X] 무료 강의는 최대 수강 인원 제한 X
- [X] 유료 강의는 강의 최대 수강 인원 초과 X
- [X] 유료 강의는 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청 가능
- [X] 강의 상태는 준비중, 모집중, 종료 3가지 상태
- [X] 강의 수강 신청은 강의 상태가 모집중일 때만 가능
- [X] 유료 강의의 경우 결제는 이미 완료한 것으로 가정, 이후 과정을 구현
- [X] 결제를 완료한 결제 정보는 payments 모듈을 통해 관리되며, 결제 정보는 Payment 객체에 담겨 반환
- [X] 도메인 모델 TDD 로 구현
32 changes: 32 additions & 0 deletions src/main/java/nextstep/enrolment/EnrolmentMain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package nextstep.enrolment;

import nextstep.courses.infrastructure.JdbcCourseRepository;
import nextstep.courses.service.CourseService;
import nextstep.enrolment.controller.EnrolmentController;
import nextstep.enrolment.facade.EnrolmentFacade;
import nextstep.payments.service.PaymentService;
import nextstep.sessions.dto.SessionResponse;
import nextstep.sessions.dto.SessionsResponse;
import nextstep.sessions.infrastructure.JdbcSessionRepository;
import nextstep.sessions.service.SessionService;
import org.springframework.jdbc.core.JdbcTemplate;

public class EnrolmentMain {

public static void main(String[] args) {
EnrolmentFacade enrolmentFacade = new EnrolmentFacade(
new CourseService(new JdbcCourseRepository(new JdbcTemplate())),
new SessionService(new JdbcSessionRepository(new JdbcTemplate())),
new PaymentService()
);
EnrolmentController enrolmentController = new EnrolmentController(enrolmentFacade);

//수강 신청 전에 현재 오픈된 기수의 과정을 볼 수 있는 로직
SessionsResponse sessionsResponse = enrolmentController.findPossibleSessionList(17);
//특정 강의에 대한 세부 내용을 확인하는 로직
SessionResponse sessionInfo = enrolmentController.findSessionInfo(1L);
//수강 신청
Long nsUserId = 1L;
enrolmentController.enrollCourse(sessionInfo.id(), nsUserId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package nextstep.enrolment.controller;

import nextstep.courses.domain.Course;
import nextstep.enrolment.facade.EnrolmentFacade;
import nextstep.payments.domain.Payment;
import nextstep.sessions.domain.Session;
import nextstep.sessions.dto.SessionResponse;
import nextstep.sessions.dto.SessionsResponse;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class EnrolmentController {

private final EnrolmentFacade enrolmentFacade;

public EnrolmentController(EnrolmentFacade enrolmentFacade) {
this.enrolmentFacade = enrolmentFacade;
}

public SessionsResponse findPossibleSessionList(int cardinalNumber) {
Course course = enrolmentFacade.findCourseByCardinalNumber(cardinalNumber);
return new SessionsResponse(course.getPossibleSessionList().stream()
.map(SessionResponse::of)
.collect(Collectors.toList()));
}

public SessionResponse findSessionInfo(long sessionId) {
Session session = enrolmentFacade.findSessionById(sessionId);
return SessionResponse.of(session);
}

public void enrollCourse(Long sessionId, Long nsUserId) {
Session session = enrolmentFacade.findSessionById(sessionId);
if (!session.isRecruitingStatus()) {
throw new IllegalArgumentException("수강 신청이 불가능한 강의 입니다.");
}

List<Payment> payments = enrolmentFacade.findPaymentsBySessionId(sessionId);
Payment payment = payments.stream().filter(p -> Objects.equals(p.nsUserId(), nsUserId))
.findAny()
.orElse(null);

if (Objects.nonNull(payment) && !session.isPossibleToRegister(payment.amount(), payments.size())) {
//수강 신청 불가
throw new IllegalArgumentException("수강 신청이 불가능합니다.");
}

//수강 신청

}
}
41 changes: 41 additions & 0 deletions src/main/java/nextstep/enrolment/facade/EnrolmentFacade.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package nextstep.enrolment.facade;

import nextstep.courses.domain.Course;
import nextstep.courses.service.CourseService;
import nextstep.payments.domain.Payment;
import nextstep.payments.service.PaymentService;
import nextstep.sessions.domain.Session;
import nextstep.sessions.service.SessionService;

import java.util.List;

public class EnrolmentFacade {

private final CourseService courseService;
private final SessionService sessionService;
private final PaymentService paymentService;

public EnrolmentFacade(CourseService courseService,
SessionService sessionService,
PaymentService paymentService) {
this.courseService = courseService;
this.sessionService = sessionService;
this.paymentService = paymentService;
}

public Course findCourseByCardinalNumber(int cardinalNumber) {
return courseService.findByCardinalNumber(cardinalNumber);
}

public Session findSessionById(Long sessionId) {
return sessionService.findById(sessionId);
}

public List<Payment> findPaymentsBySessionId(Long sessionId) {
return paymentService.findBySessionId(sessionId);
}

public Payment findPaymentBySessionIdAndNsUserId(Long sessionId, Long nsUserId) {
return paymentService.findBySessionIdAndNsUserId(sessionId, nsUserId);
}
}
58 changes: 58 additions & 0 deletions src/main/java/nextstep/images/domain/Image.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package nextstep.images.domain;

import java.util.EnumSet;
import java.util.Objects;

public class Image {

private Long id;

private ImageType imageType;

private int size;

private int width;

private int height;
Comment on lines +12 to +16
Copy link
Contributor

Choose a reason for hiding this comment

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

원시값 포장, 관련 있는 필드 값을 객체로 분리하는 차원에서 ImageFileSize, ImageSize와 같은 객체를 분리하는 것은 의미가 있을까?


public Image(Long id, ImageType imageType, int size, int width, int height) {
checkImageSize(size);
checkImageType(imageType);
checkImageWidthAndHeight(width, height);
this.id = id;
this.imageType = imageType;
this.size = size;
this.width = width;
this.height = height;
}

private void checkImageSize(int size) {
if (size == 0) {
throw new IllegalArgumentException("강의는 강의 커버 이미지 정보가 필요합니다.");
}

if (size > 1024 * 1024) { // 1MB
throw new IllegalArgumentException("이미지 크기는 1MB 이하여야 합니다.");
}
}

private void checkImageType(ImageType imageType) {
if (Objects.isNull(imageType) || !EnumSet.allOf(ImageType.class).contains(imageType)) {
throw new IllegalArgumentException("이미지 타입은 gif, jpg, jpeg, png, svg 만 가능합니다.");
}
}

private void checkImageWidthAndHeight(int width, int height) {
if (width == 0 || height == 0) {
throw new IllegalArgumentException("강의는 강의 커버 이미지 정보가 필요합니다.");
}

if (width < 300 || height < 200) {
throw new IllegalArgumentException("이미지 가로는 300 픽셀, 세로는 200 픽셀 이상이어야 합니다.");
}

if (2 * width != 3 * height) {
throw new IllegalArgumentException("가로와 세로의 비율은 3:2여야 합니다");
}
}
}
14 changes: 14 additions & 0 deletions src/main/java/nextstep/images/domain/ImageType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nextstep.images.domain;

public enum ImageType {

GIF,
JPG,
JPEG,
PNG,
SVG;

ImageType() {

}
Copy link
Contributor

Choose a reason for hiding this comment

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

이미지 파일이 유효한 파일인지에 대한 체크 로직을 이 객체에 구현하는 것은 어떨까?

}
8 changes: 8 additions & 0 deletions src/main/java/nextstep/payments/domain/Payment.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) {
this.amount = amount;
this.createdAt = LocalDateTime.now();
}

public Long amount() {
return amount;
}

public Long nsUserId() {
return nsUserId;
}
}
12 changes: 12 additions & 0 deletions src/main/java/nextstep/payments/service/PaymentService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@

import nextstep.payments.domain.Payment;

import java.util.List;

public class PaymentService {
public Payment payment(String id) {
// PG사 API를 통해 id에 해당하는 결제 정보를 반환
return new Payment();
}

//sessionId 에 맞는 결제 정보를 가져왔다고 가정
public List<Payment> findBySessionId(Long sessionId) {
return List.of(new Payment(), new Payment());
}

public Payment findBySessionIdAndNsUserId(Long sessionId, Long nsUserId) {
//sessionId 와 nsUserId 에 맞는 결제 정보를 가져왔다고 가정
return new Payment();
}
}
17 changes: 17 additions & 0 deletions src/main/java/nextstep/sessions/domain/FreeSessionType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package nextstep.sessions.domain;

public class FreeSessionType implements SessionType {

private Long amount;
private int maxCapacity;
Copy link
Contributor

Choose a reason for hiding this comment

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

amount, maxCapacity 인스턴스 변수로 초기화하고 아무 곳에서도 사용하지 않고 있다.
굳이 인스턴스로 구현할 필요가 있을까?


public FreeSessionType() {
this.amount = 0L;
this.maxCapacity = Integer.MAX_VALUE;
}

@Override
public boolean isPossibleToRegister(Long paidAmount, int enrolledStudents) {
return true;
}
}
Loading