diff --git a/README.md b/README.md
index 4ba6c8e721..3fa8e65b34 100644
--- a/README.md
+++ b/README.md
@@ -8,9 +8,24 @@
 ## 온라인 코드 리뷰 과정
 * [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)
 
-## 요구사항
+## 기능 요구사항
+### 1단계 - 레거시 코드 리팩토링
 - deleteQuestion 에서 비즈니스 로직을 도메인 모델로 리팩토링
   - [X] 로그인 사용자와 질문한 사람이 같은 경우 삭제 가능
   - [X] 질문자와 답변글의 모든 답변자가 같은 경우 삭제 가능
   - [X] 삭제 이력 남기기
 
+### 2단계 - 수강신청(도메인 모델)
+- [X] 과정(Course)은 기수 단위로 운영, 여러개의 강의(Session)존재
+- [X] 강의는 시작일과 종료일이 존재
+- [X] 강의는 강의 커버 이미지가 존재
+  - [X] 이미지 크기는 1MB 이하
+  - [X] 이미지 확장자는 gif, jpg, jpeg, png, svg 만 가능
+  - [X] width는 300픽셀, height는 200픽셀 이상이, width와 height의 비율은 3:2
+- [X] 강의는 무료강의와 유료강의 분류
+  - [X] 무료강의 - 최대 수강 인원 제한 X
+  - [X] 유료강의 - 최대 수강 인원 제한 O
+  - [X] 유료강의 - 수강생이 결제한 금액과 수강료가 일치할 때 수강 신청이 가능
+- [X] 강의 상태 : 준비중, 모집중, 종료
+- [X] 강의 상태가 모집중일 때만 수강신청 가능
+- [X] 결제 정보는 payments 모듈을 통해 관리, 결제 정보는 Payment 객체
diff --git a/src/main/java/nextstep/courses/CannotEnrollSessionException.java b/src/main/java/nextstep/courses/CannotEnrollSessionException.java
new file mode 100644
index 0000000000..819368ef9f
--- /dev/null
+++ b/src/main/java/nextstep/courses/CannotEnrollSessionException.java
@@ -0,0 +1,7 @@
+package nextstep.courses;
+
+public class CannotEnrollSessionException extends RuntimeException {
+    public CannotEnrollSessionException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/nextstep/courses/InvalidCoverImageException.java b/src/main/java/nextstep/courses/InvalidCoverImageException.java
new file mode 100644
index 0000000000..cb8d060e7f
--- /dev/null
+++ b/src/main/java/nextstep/courses/InvalidCoverImageException.java
@@ -0,0 +1,8 @@
+package nextstep.courses;
+
+public class InvalidCoverImageException extends RuntimeException {
+
+    public InvalidCoverImageException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/nextstep/courses/domain/Course.java b/src/main/java/nextstep/courses/domain/Course.java
index 0f69716043..ad47f3d286 100644
--- a/src/main/java/nextstep/courses/domain/Course.java
+++ b/src/main/java/nextstep/courses/domain/Course.java
@@ -4,6 +4,7 @@
 
 public class Course {
     private Long id;
+    private Long generation;
 
     private String title;
 
@@ -13,17 +14,25 @@ public class Course {
 
     private LocalDateTime updatedAt;
 
+    private Sessions sessions;
+
     public Course() {
     }
 
     public Course(String title, Long creatorId) {
-        this(0L, title, creatorId, LocalDateTime.now(), null);
+        this(0L, 0L, title, creatorId, new Sessions(), LocalDateTime.now(), null);
     }
 
     public Course(Long id, String title, Long creatorId, LocalDateTime createdAt, LocalDateTime updatedAt) {
+        this(id, 0L, title, creatorId, new Sessions(), createdAt, updatedAt);
+    }
+
+    public Course(Long id, Long generation, String title, Long creatorId, Sessions sessions, LocalDateTime createdAt, LocalDateTime updatedAt) {
         this.id = id;
+        this.generation = generation;
         this.title = title;
         this.creatorId = creatorId;
+        this.sessions = sessions;
         this.createdAt = createdAt;
         this.updatedAt = updatedAt;
     }
@@ -50,4 +59,5 @@ public String toString() {
                 ", updatedAt=" + updatedAt +
                 '}';
     }
+
 }
diff --git a/src/main/java/nextstep/courses/domain/CoverImage.java b/src/main/java/nextstep/courses/domain/CoverImage.java
new file mode 100644
index 0000000000..03251a011a
--- /dev/null
+++ b/src/main/java/nextstep/courses/domain/CoverImage.java
@@ -0,0 +1,38 @@
+package nextstep.courses.domain;
+
+import nextstep.courses.InvalidCoverImageException;
+
+public class CoverImage {
+
+    private final int width;
+    private final int height;
+    private final int  sizeInBytes;
+    private final ImageExtension extension;
+
+    public CoverImage(int width, int height, int sizeInBytes, ImageExtension extension) {
+        this.width = width;
+        this.height = height;
+        this.sizeInBytes = sizeInBytes;
+        this.extension = extension;
+        validate();
+    }
+
+    private void validate() {
+        if (sizeInBytes > 1_000_000) {
+            throw new InvalidCoverImageException("1MB를 초과하는 이미지입니다.");
+        }
+        if (width < 300) {
+            throw new InvalidCoverImageException("너비는 300px 이상이어야 합니다.");
+        }
+        if (height < 200) {
+            throw new InvalidCoverImageException("높이는 200px 이상이어야 합니다.");
+        }
+        if (width * 2 != height * 3) {
+            throw new InvalidCoverImageException("비율은 3:2여야 합니다.");
+        }
+        if (extension == null) {
+            throw new InvalidCoverImageException("지원하지 않는 확장자입니다.");
+        }
+    }
+
+}
diff --git a/src/main/java/nextstep/courses/domain/ImageExtension.java b/src/main/java/nextstep/courses/domain/ImageExtension.java
new file mode 100644
index 0000000000..98c4817f70
--- /dev/null
+++ b/src/main/java/nextstep/courses/domain/ImageExtension.java
@@ -0,0 +1,16 @@
+package nextstep.courses.domain;
+
+public enum ImageExtension {
+    GIF("gif"),
+    JPG("jpg"),
+    JPEG("jpeg"),
+    PNG("png"),
+    SVG("svg");
+
+    private final String value;
+
+    ImageExtension(String value) {
+        this.value = value;
+    }
+
+}
diff --git a/src/main/java/nextstep/courses/domain/Session.java b/src/main/java/nextstep/courses/domain/Session.java
new file mode 100644
index 0000000000..c23638653e
--- /dev/null
+++ b/src/main/java/nextstep/courses/domain/Session.java
@@ -0,0 +1,43 @@
+package nextstep.courses.domain;
+
+import nextstep.courses.CannotEnrollSessionException;
+import nextstep.payments.domain.EnrollmentPolicy;
+import nextstep.payments.domain.Payment;
+
+import java.time.LocalDate;
+
+public class Session {
+
+    private final LocalDate startDate;
+    private final LocalDate endDate;
+    private final CoverImage coverImage;
+    private final SessionStatus status;
+    private final EnrollmentPolicy enrollmentPolicy;
+
+    private int currentEnrolledCount = 0;
+
+    public Session(LocalDate startDate,
+                   LocalDate endDate,
+                   CoverImage coverImage,
+                   SessionStatus status,
+                   EnrollmentPolicy enrollmentPolicy) {
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.coverImage = coverImage;
+        this.status = status;
+        this.enrollmentPolicy = enrollmentPolicy;
+    }
+
+    public void enroll(Payment payment) {
+        if (!this.status.canEnroll()) {
+            throw new CannotEnrollSessionException("모집 중이 아닙니다.");
+        }
+
+        if (!enrollmentPolicy.canEnroll(currentEnrolledCount, payment)) {
+            throw new CannotEnrollSessionException("수강 조건이 맞지 않습니다.");
+        }
+
+        currentEnrolledCount++;
+    }
+
+}
diff --git a/src/main/java/nextstep/courses/domain/SessionStatus.java b/src/main/java/nextstep/courses/domain/SessionStatus.java
new file mode 100644
index 0000000000..19b3a74b64
--- /dev/null
+++ b/src/main/java/nextstep/courses/domain/SessionStatus.java
@@ -0,0 +1,11 @@
+package nextstep.courses.domain;
+
+public enum SessionStatus {
+    READY,
+    ENROLLING,
+    COMPLETED;
+
+    public boolean canEnroll() {
+        return this == ENROLLING;
+    }
+}
diff --git a/src/main/java/nextstep/courses/domain/Sessions.java b/src/main/java/nextstep/courses/domain/Sessions.java
new file mode 100644
index 0000000000..4dbfc0bb1e
--- /dev/null
+++ b/src/main/java/nextstep/courses/domain/Sessions.java
@@ -0,0 +1,16 @@
+package nextstep.courses.domain;
+
+import java.util.List;
+
+public class Sessions {
+
+    private List<Session> sessions;
+
+    public Sessions() {
+    }
+
+    public Sessions(List<Session> sessions) {
+        this.sessions = sessions;
+    }
+
+}
diff --git a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java
index f9122cbe33..10970910af 100644
--- a/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java
+++ b/src/main/java/nextstep/courses/infrastructure/JdbcCourseRepository.java
@@ -2,6 +2,7 @@
 
 import nextstep.courses.domain.Course;
 import nextstep.courses.domain.CourseRepository;
+import nextstep.courses.domain.Sessions;
 import org.springframework.jdbc.core.JdbcOperations;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.stereotype.Repository;
diff --git a/src/main/java/nextstep/payments/domain/EnrollmentPolicy.java b/src/main/java/nextstep/payments/domain/EnrollmentPolicy.java
new file mode 100644
index 0000000000..e1845f7ae3
--- /dev/null
+++ b/src/main/java/nextstep/payments/domain/EnrollmentPolicy.java
@@ -0,0 +1,5 @@
+package nextstep.payments.domain;
+
+public interface EnrollmentPolicy {
+    boolean canEnroll(int currentEnrolledCount, Payment payment);
+}
diff --git a/src/main/java/nextstep/payments/domain/FreeEnrollmentPolicy.java b/src/main/java/nextstep/payments/domain/FreeEnrollmentPolicy.java
new file mode 100644
index 0000000000..92145f74d5
--- /dev/null
+++ b/src/main/java/nextstep/payments/domain/FreeEnrollmentPolicy.java
@@ -0,0 +1,9 @@
+package nextstep.payments.domain;
+
+public class FreeEnrollmentPolicy implements EnrollmentPolicy {
+
+    @Override
+    public boolean canEnroll(int currentEnrolledCount, Payment payment) {
+        return payment.isSameAmount(0L);
+    }
+}
diff --git a/src/main/java/nextstep/payments/domain/PaidEnrollmentPolicy.java b/src/main/java/nextstep/payments/domain/PaidEnrollmentPolicy.java
new file mode 100644
index 0000000000..796bf39a66
--- /dev/null
+++ b/src/main/java/nextstep/payments/domain/PaidEnrollmentPolicy.java
@@ -0,0 +1,18 @@
+package nextstep.payments.domain;
+
+public class PaidEnrollmentPolicy implements EnrollmentPolicy {
+
+    private final int maxEnrolledCount;
+    private final int price;
+
+
+    public PaidEnrollmentPolicy(int maxEnrolledCount, int price) {
+        this.maxEnrolledCount = maxEnrolledCount;
+        this.price = price;
+    }
+
+    @Override
+    public boolean canEnroll(int currentEnrolledCount, Payment payment) {
+        return currentEnrolledCount < maxEnrolledCount && payment.isSameAmount((long) price);
+    }
+}
diff --git a/src/main/java/nextstep/payments/domain/Payment.java b/src/main/java/nextstep/payments/domain/Payment.java
index 57d833f851..ef093c387a 100644
--- a/src/main/java/nextstep/payments/domain/Payment.java
+++ b/src/main/java/nextstep/payments/domain/Payment.java
@@ -26,4 +26,8 @@ public Payment(String id, Long sessionId, Long nsUserId, Long amount) {
         this.amount = amount;
         this.createdAt = LocalDateTime.now();
     }
+
+    public boolean isSameAmount(Long amount) {
+        return this.amount.equals(amount);
+    }
 }
diff --git a/src/main/java/nextstep/qna/domain/Answers.java b/src/main/java/nextstep/qna/domain/Answers.java
index 544cdb1cb1..add1489d24 100644
--- a/src/main/java/nextstep/qna/domain/Answers.java
+++ b/src/main/java/nextstep/qna/domain/Answers.java
@@ -5,6 +5,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class Answers {
     private final List<Answer> answers;
@@ -22,16 +23,15 @@ public List<Answer> getAnswers() {
     }
 
     public List<DeleteHistory> deleteAll(NsUser loginUser) {
-        for (Answer answer : answers) {
-            if (!answer.isOwner(loginUser)) {
-                throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다.");
-            }
+        if(!canAllDelete(loginUser)) {
+            throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다.");
         }
+        return answers.stream().map(Answer::delete).collect(Collectors.toList());
+    }
+
+    private boolean canAllDelete(NsUser loginUser) {
+        return answers.stream().filter(answer -> answer.isOwner(loginUser)).count()
+                == answers.size();
 
-        List<DeleteHistory> deleteHistories = new ArrayList<>();
-        for (Answer answer : answers) {
-            deleteHistories.add(answer.delete());
-        }
-        return deleteHistories;
     }
 }
diff --git a/src/test/java/nextstep/courses/domain/CoverImageTest.java b/src/test/java/nextstep/courses/domain/CoverImageTest.java
new file mode 100644
index 0000000000..9d1f9f2edd
--- /dev/null
+++ b/src/test/java/nextstep/courses/domain/CoverImageTest.java
@@ -0,0 +1,52 @@
+package nextstep.courses.domain;
+
+import nextstep.courses.InvalidCoverImageException;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+public class CoverImageTest {
+
+    @Test
+    void 유효한_커버이미지는_예외없이_생성되고_검증_통과() {
+        assertThatCode(() ->
+                new CoverImage(600, 400, 500_000, ImageExtension.JPG)
+        ).doesNotThrowAnyException();
+    }
+
+    @Test
+    void 크기가_1MB_초과하면_예외발생() {
+        assertThatThrownBy(() ->
+                new CoverImage(600, 400, 2_000_000, ImageExtension.PNG)
+        ).isInstanceOf(InvalidCoverImageException.class);
+    }
+
+    @Test
+    void 너비가_300미만이면_예외발생() {
+        assertThatThrownBy(() ->
+                new CoverImage(299, 400, 500_000, ImageExtension.PNG)
+        ).isInstanceOf(InvalidCoverImageException.class);
+    }
+
+    @Test
+    void 높이가_200미만이면_예외발생() {
+        assertThatThrownBy(() ->
+                new CoverImage(600, 199, 500_000, ImageExtension.PNG)
+        ).isInstanceOf(InvalidCoverImageException.class);
+    }
+
+    @Test
+    void 비율이_3대2_아니면_예외발생() {
+        assertThatThrownBy(() ->
+                new CoverImage(600, 500, 500_000, ImageExtension.PNG)
+        ).isInstanceOf(InvalidCoverImageException.class);
+    }
+
+    @Test
+    void 지원하지_않는_확장자면_예외발생() {
+        assertThatThrownBy(() ->
+                new CoverImage(600, 400, 500_000, null)
+        ).isInstanceOf(InvalidCoverImageException.class);
+    }
+}
diff --git a/src/test/java/nextstep/courses/domain/SessionTest.java b/src/test/java/nextstep/courses/domain/SessionTest.java
new file mode 100644
index 0000000000..954cc4d4a5
--- /dev/null
+++ b/src/test/java/nextstep/courses/domain/SessionTest.java
@@ -0,0 +1,42 @@
+package nextstep.courses.domain;
+
+import nextstep.courses.CannotEnrollSessionException;
+import nextstep.payments.domain.PaidEnrollmentPolicy;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDate;
+
+import static nextstep.payments.PaymentTest.PAYMENT_1000;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+public class SessionTest {
+
+    public static final Session ReadySession = new Session(
+            LocalDate.of(2025, 1, 1),
+            LocalDate.of(2025, 12, 31),
+            new CoverImage(600, 400, 500_000, ImageExtension.JPG),
+            SessionStatus.READY,
+            new PaidEnrollmentPolicy(100, 1000));
+
+    public static final Session EnrollingSession = new Session(
+            LocalDate.of(2025, 1, 1),
+            LocalDate.of(2025, 12, 31),
+            new CoverImage(600, 400, 500_000, ImageExtension.JPG),
+            SessionStatus.ENROLLING,
+            new PaidEnrollmentPolicy(100, 1000));
+
+    @Test
+    void 모집중이_아닐_때_수강신청_불가능() {
+        assertThatThrownBy(() ->
+                ReadySession.enroll(PAYMENT_1000)
+        ).isInstanceOf(CannotEnrollSessionException.class);
+    }
+
+    @Test
+    void 모집중일_때_수강신청_가능() {
+        assertThatCode(() -> EnrollingSession.enroll(PAYMENT_1000))
+                .doesNotThrowAnyException();
+
+    }
+}
diff --git a/src/test/java/nextstep/payments/EnrollmentPolicyTest.java b/src/test/java/nextstep/payments/EnrollmentPolicyTest.java
new file mode 100644
index 0000000000..62648f0545
--- /dev/null
+++ b/src/test/java/nextstep/payments/EnrollmentPolicyTest.java
@@ -0,0 +1,40 @@
+package nextstep.payments;
+
+import nextstep.payments.domain.EnrollmentPolicy;
+import nextstep.payments.domain.FreeEnrollmentPolicy;
+import nextstep.payments.domain.PaidEnrollmentPolicy;
+import org.junit.jupiter.api.Test;
+
+import static nextstep.payments.PaymentTest.PAYMENT_1000;
+import static nextstep.payments.PaymentTest.PAYMENT_FREE;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class EnrollmentPolicyTest {
+
+    @Test
+    void 무료_강의_최대_수강_인원_제한이_없다() {
+        EnrollmentPolicy enrollmentPolicy = new FreeEnrollmentPolicy();
+        assertTrue(enrollmentPolicy.canEnroll(9999, PAYMENT_FREE));
+    }
+
+    @Test
+    void 유료_강의_최대_수강_인원_제한이_있다() {
+        EnrollmentPolicy enrollmentPolicy = new PaidEnrollmentPolicy(10, 1000);
+        assertTrue(enrollmentPolicy.canEnroll(3, PAYMENT_1000));
+        assertFalse(enrollmentPolicy.canEnroll(10, PAYMENT_1000));
+    }
+
+    @Test
+    void 유료_강의_수강료가_같으면_수강할_수_있다() {
+        EnrollmentPolicy enrollmentPolicy = new PaidEnrollmentPolicy(10, 1000);
+        assertTrue(enrollmentPolicy.canEnroll(3, PAYMENT_1000));
+    }
+
+    @Test
+    void 유료_강의_수강료가_다르면_수강할_수_없다() {
+        EnrollmentPolicy enrollmentPolicy = new PaidEnrollmentPolicy(10, 5000);
+        assertFalse(enrollmentPolicy.canEnroll(3, PAYMENT_1000));
+    }
+
+}
diff --git a/src/test/java/nextstep/payments/PaymentTest.java b/src/test/java/nextstep/payments/PaymentTest.java
new file mode 100644
index 0000000000..b326aaea51
--- /dev/null
+++ b/src/test/java/nextstep/payments/PaymentTest.java
@@ -0,0 +1,28 @@
+package nextstep.payments;
+
+import nextstep.payments.domain.Payment;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class PaymentTest {
+
+    public static final Payment PAYMENT_FREE = new Payment(
+            "12345", 1L, 100L, 0L
+    );
+
+    public static final Payment PAYMENT_1000 = new Payment(
+            "12345", 1L, 100L, 1000L
+    );
+
+    @Test
+    void 결제금액이_같으면_true를_반환한다() {
+        assertTrue(PAYMENT_1000.isSameAmount(1000L));
+    }
+
+    @Test
+    void 결제금액이_다르면_false를_반환한다() {
+        assertFalse(PAYMENT_1000.isSameAmount(2000L));
+    }
+}