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

4단계 - 수강신청(요구사항 변경) #402

Open
wants to merge 63 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
a932353
요구사항 정리
sunggyupaik Nov 25, 2023
e69ca97
[refactor] 질문 삭제를 Question 도메인으로 이동하라
sunggyupaik Nov 28, 2023
0796c54
[refactor] Question 답변 삭제 유효성을 Answer로 옮겨라
sunggyupaik Nov 28, 2023
452ffbf
[refactor] 질문 삭제 서비스 메서드를 리팩토링 하라
sunggyupaik Nov 28, 2023
d80f72f
[refactor] 삭제이력 생성을 개선하라
sunggyupaik Nov 29, 2023
83f217b
[refactor] 질문, 답변, 삭제 이력 각자의 책임을 부여하라
sunggyupaik Nov 29, 2023
7f07967
[refactor] 테스트에 사용히 필요없는 상수를 제거하라
sunggyupaik Nov 29, 2023
53416b6
[refactor] 생성시간, 수정시간을 클래스로 분리하라
sunggyupaik Nov 29, 2023
86fff7c
[refactor] 질문의 답변을 일급 컬렉션으로 개선하라
sunggyupaik Nov 29, 2023
3dfd1cb
[refactor] 답변을 질문 도메인에서 관리하라
sunggyupaik Dec 4, 2023
55a8469
[refactor] 삭제 이력의 의존을 변경하라
sunggyupaik Dec 4, 2023
e83f398
[refactor] 질문 도메인에서 삭제 이력을 관리하라
sunggyupaik Dec 4, 2023
6a649b1
[feat] 삭제이력 일급 컬렉션을 추가하라
sunggyupaik Dec 4, 2023
48a14e3
Merge pull request #262 from sunggyupaik/step1
ohtaeg Dec 5, 2023
b7e8fc7
기능 요구사항 정리
sunggyupaik Dec 5, 2023
32b07f3
[feat] 강의와 이미지 도메인을 생성하라
sunggyupaik Dec 5, 2023
861343b
[feat] 이미지정보 유효성을 추가하라
sunggyupaik Dec 5, 2023
e0f9865
[feat] 강의에 유효성 검증을 추가하라
sunggyupaik Dec 5, 2023
3ddfe50
[test] 강의와 이미지 테스트를 추가하라
sunggyupaik Dec 5, 2023
335ebea
[refactor] 강의의 필드를 개선하라
sunggyupaik Dec 6, 2023
f681082
[feat] 강의 신청 서비스 레이어를 생성하라
sunggyupaik Dec 7, 2023
95dcaa4
[refactor] 도메인 유효성 검증을 메서드로 분리하라
sunggyupaik Dec 7, 2023
e75bbd3
[feat] 강의 신청자를 일급 컬렉션으로 감싸라
sunggyupaik Dec 7, 2023
c30d294
[test] 이미지 타입 조회 테스트를 추가하라
sunggyupaik Dec 7, 2023
fd6e104
[feat] 강의 시작과 종료날짜를 클래스로 분리하라
sunggyupaik Dec 7, 2023
ac45f24
[style] 강의의 enum 위치를 조정하라
sunggyupaik Dec 7, 2023
740212c
[feat] 과정에 강의 추가를 추가하라
sunggyupaik Dec 8, 2023
967ae5b
[feat] 중복 신청 유효성 검사를 추가하라
sunggyupaik Dec 8, 2023
51396ae
[feat] 강의 금액과 지불내역이 다른 유효성을 추가하라
sunggyupaik Dec 8, 2023
7bc23b2
[feat] 강의 도메인 수강상태 변경 유효성을 추가하라
sunggyupaik Dec 8, 2023
82773e1
[feat] 수강생 클래스에 정원 수를 추가하라
sunggyupaik Dec 9, 2023
33d3d6b
[feat] 강의 상태 변경을 서비스 레이어에 추가하라
sunggyupaik Dec 9, 2023
1a558de
[refactor] 수강생과 강의의 의존성을 제거하라
sunggyupaik Dec 9, 2023
328ed9f
[refactor] 과정의 공통 속성을 상속으로 개선하라
sunggyupaik Dec 9, 2023
c1527cd
[refactor] 강의의 공통 속성을 별도 클래스로 분리하라
sunggyupaik Dec 9, 2023
3a1be5a
[fix] 강의 도메인의 강의 상태를 분리하라
sunggyupaik Dec 9, 2023
15f8086
[feat] 강의 상태에 따른 유효성 검사를 추가하라
sunggyupaik Dec 9, 2023
f37bcb7
[fix] 이미지 도메인에 공통 속성 상속을 추가하라
sunggyupaik Dec 9, 2023
8104100
[fix] 도메인 생성자에 일시 매개변수를 추가하라
sunggyupaik Dec 9, 2023
db78578
[feat] 테이블 스키마를 추가하라
sunggyupaik Dec 10, 2023
6289f40
[fix] 과정 저장소 저장 테스트를 수정하라
sunggyupaik Dec 10, 2023
4e4dae1
[feat] 이미지 jdbc 조회 및 저장을 추가하라
sunggyupaik Dec 10, 2023
ef4e541
[feat] 수강생 저장소와 조회를 추가하라
sunggyupaik Dec 11, 2023
d11dc41
[feat] 강의 저장소의 조회와 저장을 추가하라
sunggyupaik Dec 11, 2023
140fc40
[feat] 강의 신청 저장을 추가하라
sunggyupaik Dec 11, 2023
55be725
[feat] 강의 저장소에 수정을 추가하라
sunggyupaik Dec 11, 2023
794f346
[feat] 과정과 강의의 서비스에 저장소를 추가하라
sunggyupaik Dec 11, 2023
055544e
[refactor] 과정 조회에 강의가 포함되도록 개선하라
sunggyupaik Dec 11, 2023
9e538ee
[feat] 과정에 강의 추가를 저장소에서 구현하라
sunggyupaik Dec 11, 2023
a9e7b4f
[feat] 수강 지원에 apply를 사용하라
sunggyupaik Dec 11, 2023
fa78818
[fix] 강의 수정 테스트를 개선하라
sunggyupaik Dec 12, 2023
f8fdd94
[style] 쿼리를 적절한 길이에서 줄바꿈 하라
sunggyupaik Dec 12, 2023
4ff1895
[feat] 강의 서비스 테스트를 추가하라
sunggyupaik Dec 12, 2023
e5984d3
[fix] apply 생성자를 객체 중심으로 개선하라
sunggyupaik Dec 12, 2023
7520aeb
[fix] 생성시간이 not null이라면 null 검사를 제외하라
sunggyupaik Dec 12, 2023
3fcf81e
기능 요구사항 정리
sunggyupaik Dec 12, 2023
abaf303
[fix] 강의에 1개 이상의 이미지를 가지도록 개선하라
sunggyupaik Dec 13, 2023
4a469cf
[feat] 모집중/비모집중을 추가하고 Session 구조를 개선하라
sunggyupaik Dec 13, 2023
427adf8
[feat] 수강신청 유효성을 개선하라
sunggyupaik Dec 13, 2023
f85a1b0
[feat] 강사의 수강신청 승인 및 취소를 추가하라
sunggyupaik Dec 13, 2023
b02d9fe
[refactor] 강의를 리팩토링 하라
sunggyupaik Dec 22, 2023
f8386a4
[feat] 엔티티를 최대한 살리고 비지니스는 클래스로 분리하라
sunggyupaik Dec 26, 2023
0545826
[refactor] 비지니스 로직과 엔티티를 리팩토링 하라
sunggyupaik Dec 27, 2023
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
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
# 학습 관리 시스템(Learning Management System)
## 진행 방법
* 학습 관리 시스템의 수강신청 요구사항을 파악한다.
* 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다.
* 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다.
* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.

## 온라인 코드 리뷰 과정
* [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)
## 4단계 - 수강신청(요구사항 변경)

### 변경된 기능 요구사항
- 강의 수강신청은 강의 상태가 모집중일 때만 가능하다.
- 강의가 진행 중인 상태에서도 수강신청이 가능해야 한다.
- 강의 진행 상태(준비중, 진행중, 종료)와 모집 상태(비모집중, 모집중)로 상태 값을 분리해야 한다.
- 강의는 강의 커버 이미지 정보를 가진다.
- 강의는 하나 이상의 커버 이미지를 가질 수 있다.
- 강사가 승인하지 않아도 수강 신청하는 모든 사람이 수강 가능하다.
- 우아한테크코스(무료), 우아한테크캠프 Pro(유료)와 같이 선발된 인원만 수강 가능해야 한다.
- 강사는 수강신청한 사람 중 선발된 인원에 대해서만 수강 승인이 가능해야 한다.
- 강사는 수강신청한 사람 중 선발되지 않은 사람은 수강을 취소할 수 있어야 한다.

### 프로그래밍 요구사항
- 리팩터링할 때 컴파일 에러와 기존의 단위 테스트의 실패를 최소화하면서 점진적인 리팩터링이 가능하도록 한다.
- DB 테이블에 데이터가 존재한다는 가정하에 리팩터링해야 한다.
- 즉, 기존에 쌓인 데이터를 제거하지 않은 상태로 리팩터링 해야 한다.
29 changes: 29 additions & 0 deletions src/main/java/nextstep/courses/domain/BaseEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package nextstep.courses.domain;

import java.time.LocalDateTime;

public class BaseEntity {
private final Long creatorId;

private final LocalDateTime createdAt;

private final LocalDateTime updatedAt;

public BaseEntity(Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.creatorId = creatorId;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}

public Long creatorId() {
return creatorId;
}

public LocalDateTime createdAt() {
return createdAt;
}

public LocalDateTime updatedAt() {
return updatedAt;
}
}
53 changes: 0 additions & 53 deletions src/main/java/nextstep/courses/domain/Course.java

This file was deleted.

7 changes: 0 additions & 7 deletions src/main/java/nextstep/courses/domain/CourseRepository.java

This file was deleted.

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

import nextstep.courses.domain.BaseEntity;
import nextstep.courses.domain.course.session.Session;
import nextstep.courses.domain.course.session.Sessions;

import java.time.LocalDateTime;

public class Course extends BaseEntity {
private final Long id;

private final String title;

private final int ordering;

private final Sessions sessions;

public Course(String title, int ordering, Long creatorId, LocalDateTime date) {
this(0L, title, ordering, new Sessions(), creatorId, date, null);
}

public Course(Long id, String title, int ordering, Sessions sessions,
Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) {
super(creatorId, createdAt, updatedAt);
this.id = id;
this.title = title;
this.ordering = ordering;
this.sessions = sessions;
}

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

public Long id() {
return id;
}

public String title() {
return title;
}

public int ordering() {
return this.ordering;
}

public Sessions sessions() {
return this.sessions;
}

@Override
public String toString() {
return "Course{" +
"id=" + id +
", title='" + title + '\'' +
", ordering=" + ordering +
", sessions=" + sessions +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package nextstep.courses.domain.course;

public interface CourseRepository {
Course save(Course course);

Course findById(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package nextstep.courses.domain.course.session;

import nextstep.courses.domain.course.session.apply.Applies;
import nextstep.courses.domain.course.session.apply.Apply;
import nextstep.courses.domain.course.session.apply.ApprovalStatus;
import nextstep.payments.domain.Payment;

import java.time.LocalDateTime;

public class Enrollment {
private final Long sessionId;

private final Applies applies;

private final SessionDetail sessionDetail;

public Enrollment(Long sessionId, Applies applies, SessionDetail sessionDetail) {
this.sessionId = sessionId;
this.applies = applies;
this.sessionDetail = sessionDetail;
}

public Apply apply(Long nsUserId, Payment payment, LocalDateTime date) {
checkPaymentIsPaid(nsUserId, payment);
checkStatusOnRecruit();
checkStatusOnReadyOrOnGoing();
checkChargedAndApplySizeIsValid();
checkApplicantAlreadyExisted(nsUserId);

return new Apply(sessionId, nsUserId, ApprovalStatus.WAIT, date);
}

private void checkPaymentIsPaid(Long nsUserId, Payment payment) {
if (this.sessionDetail.charged()) {
checkPaymentIsValid(nsUserId, payment);
}
}

private void checkPaymentIsValid(Long nsUserId, Payment payment) {
if (payment == null ||
!payment.isPaid(
nsUserId,
sessionId,
this.sessionDetail.getAmount()
)
) {
throw new IllegalArgumentException("결제를 다시 확인하세요.");
}
}

private void checkStatusOnRecruit() {
if (this.sessionDetail.notRecruiting()) {
throw new IllegalArgumentException("강의 신청은 모집 중일 때만 가능 합니다.");
}
}

private void checkStatusOnReadyOrOnGoing() {
if (this.sessionDetail.notReadyOrOnGoing()) {
throw new IllegalArgumentException("강의 신청은 준비, 진행중일 때만 가능 합니다.");
}
}

private void checkChargedAndApplySizeIsValid() {
if(this.sessionDetail.chargedAndFull(applies.size())) {
throw new IllegalArgumentException("수강 신청 인원이 초과 되었습니다.");
}
}

private void checkApplicantAlreadyExisted(Long nsUserId) {
if (this.applies.containsUserId(nsUserId)) {
throw new IllegalArgumentException("이미 수강 신청 이력이 있습니다.");
}
}
}
131 changes: 131 additions & 0 deletions src/main/java/nextstep/courses/domain/course/session/Session.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package nextstep.courses.domain.course.session;

import nextstep.courses.domain.BaseEntity;
import nextstep.courses.domain.course.session.apply.Applies;
import nextstep.courses.domain.course.session.apply.ApproveCancel;
import nextstep.courses.domain.course.session.image.Images;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;

public class Session extends BaseEntity {
private final Long id;

private final Images images;

private final Applies applies;

private final SessionDetail sessionDetail;

public Session(Images images, SessionDuration sessionDuration, SessionState sessionState,
Long creatorId, LocalDateTime date) {
this(0L, images, new Applies(), sessionDuration, sessionState, SessionRecruitStatus.NOT_RECRUIT,
SessionProgressStatus.READY, creatorId, date, null);
}

public Session(Long id, Images images, Applies applies, SessionDuration sessionDuration, SessionState sessionState,
SessionRecruitStatus sessionRecruitStatus, SessionProgressStatus sessionProgressStatus,
Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) {
this(id, images, applies, new SessionDetail(sessionDuration, sessionState, sessionProgressStatus, sessionRecruitStatus),
creatorId, createdAt, updatedAt);
}

public Session(Long id, Images images, Applies applies, SessionDetail sessionDetail,
Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) {
super(creatorId, createdAt, updatedAt);
if (images == null) {
throw new IllegalArgumentException("이미지를 추가해야 합니다.");
}

if (applies == null) {
throw new IllegalArgumentException("지원자를 추가해야 합니다.");
}

if (sessionDetail == null) {
throw new IllegalArgumentException("강의 정보를 추가해야 합니다.");
}

this.id = id;
this.images = images;
this.applies = applies;
this.sessionDetail = sessionDetail;
}

public Enrollment enrollment() {
return new Enrollment(this.id, this.applies, this.sessionDetail);
}

public ApproveCancel approve() {
return new ApproveCancel(this.applies);
}

public ApproveCancel cancel() {
return new ApproveCancel(this.applies);
}

public Session changeOnReady(LocalDate date) {
SessionDetail changedSessionDetail = this.sessionDetail.changeOnReady(date);
return new Session(id, images, applies, changedSessionDetail, creatorId(), createdAt(), updatedAt());
}

public Session changeOnGoing(LocalDate date) {
SessionDetail changedSessionDetail = this.sessionDetail.changeOnGoing(date);
return new Session(id, images, applies, changedSessionDetail, creatorId(), createdAt(), updatedAt());
}

public Session changeOnEnd(LocalDate date) {
SessionDetail changedSessionDetail = this.sessionDetail.changeOnEnd(date);
return new Session(id, images, applies, changedSessionDetail, creatorId(), createdAt(), updatedAt());
}

public Long id() {
return this.id;
}

public Images images() {
return images;
}

public SessionDetail sessionDetail() {
return sessionDetail;
}

public SessionDuration sessionDuration() {
return sessionDetail.sessionDuration();
}

public SessionState sessionState() {
return sessionDetail.sessionState();
}

public SessionProgressStatus sessionProgressStatus() {
return sessionDetail.sessionProgressStatus();
}

public SessionRecruitStatus sessionRecruitStatus() {
return sessionDetail.sessionRecruitStatus();
}

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

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

@Override
public String toString() {
return "Session{" +
"id=" + id +
", images=" + images +
", sessionDetail=" + sessionDetail +
'}';
}
}
Loading