diff --git a/src/main/java/nextstep/ladder/LadderMain.java b/src/main/java/nextstep/ladder/LadderMain.java
new file mode 100644
index 0000000000..bbbed32d4c
--- /dev/null
+++ b/src/main/java/nextstep/ladder/LadderMain.java
@@ -0,0 +1,18 @@
+package nextstep.ladder;
+
+import nextstep.ladder.domain.Ladder;
+import nextstep.ladder.domain.PersonNames;
+import nextstep.ladder.view.InputView;
+import nextstep.ladder.view.ReviewView;
+
+import java.util.List;
+
+public class LadderMain {
+    public static void main(String[] args) {
+        List<String> personNameInput = InputView.getPersonNameInput("참여할 사람 이름을 입력하세요. (이름은 쉼표(,)로 구분하세요)", ",");
+        int height = InputView.getPositiveNumberInput("최대 사다리 높이는 몇 개인가요?");
+
+        Ladder ladder = new Ladder(new PersonNames(personNameInput), height);
+        ReviewView.printLadder(ladder);
+    }
+}
diff --git a/src/main/java/nextstep/ladder/domain/Ladder.java b/src/main/java/nextstep/ladder/domain/Ladder.java
new file mode 100644
index 0000000000..9cc0deb5db
--- /dev/null
+++ b/src/main/java/nextstep/ladder/domain/Ladder.java
@@ -0,0 +1,19 @@
+package nextstep.ladder.domain;
+
+public class Ladder {
+    private final PersonNames personNames;
+    private final LadderLines ladderLines;
+
+    public Ladder(PersonNames personNames, int height) {
+        this.personNames = personNames;
+        this.ladderLines = LadderLineGenerator.generateLadderLines(height, personNames.size() - 1);
+    }
+
+    public PersonNames getPersonNames() {
+        return personNames;
+    }
+
+    public LadderLines getLadderLines() {
+        return ladderLines;
+    }
+}
diff --git a/src/main/java/nextstep/ladder/domain/LadderLine.java b/src/main/java/nextstep/ladder/domain/LadderLine.java
new file mode 100644
index 0000000000..5b728dd842
--- /dev/null
+++ b/src/main/java/nextstep/ladder/domain/LadderLine.java
@@ -0,0 +1,46 @@
+package nextstep.ladder.domain;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class LadderLine {
+    private final List<Boolean> lines;
+
+    public LadderLine(List<Boolean> lines) {
+        if (lines == null || lines.isEmpty()) {
+            throw new IllegalArgumentException("lines cannot be null or empty");
+        }
+
+        validateLines(lines);
+
+        this.lines = lines;
+    }
+
+    public int size() {
+        return lines.size();
+    }
+
+    private void validateLines(List<Boolean> lines) {
+        Iterator<Boolean> iterator = lines.iterator();
+        boolean prev = iterator.next();
+
+        while (iterator.hasNext()) {
+            boolean cur = iterator.next();
+            if (prev && cur) {
+                throw new IllegalArgumentException("Ladder lines cannot be connected continuously.");
+            }
+            prev = cur;
+        }
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        for (Boolean line : lines) {
+            sb.append("|");
+            sb.append(line ? "--------": "        ");
+        }
+        sb.append("|");
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/nextstep/ladder/domain/LadderLineGenerator.java b/src/main/java/nextstep/ladder/domain/LadderLineGenerator.java
new file mode 100644
index 0000000000..9594e9d1a0
--- /dev/null
+++ b/src/main/java/nextstep/ladder/domain/LadderLineGenerator.java
@@ -0,0 +1,31 @@
+package nextstep.ladder.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class LadderLineGenerator {
+    private static final Random random = new Random();
+
+    public static LadderLines generateLadderLines(int height, int size) {
+        List<LadderLine> ladderLines = new ArrayList<>();
+        for (int i = 0; i < height; i++) {
+            ladderLines.add(generateLadderLine(size));
+        }
+
+        return new LadderLines(ladderLines);
+    }
+
+    private static LadderLine generateLadderLine(int size) {
+        List<Boolean> lines = new ArrayList<>();
+
+        for (int i = 0; i < size; i++) {
+            boolean prev = i != 0 && lines.get(i - 1);
+
+            boolean cur = !prev && random.nextBoolean();
+            lines.add(cur);
+        }
+
+        return new LadderLine(lines);
+    }
+}
diff --git a/src/main/java/nextstep/ladder/domain/LadderLines.java b/src/main/java/nextstep/ladder/domain/LadderLines.java
new file mode 100644
index 0000000000..a5c9488487
--- /dev/null
+++ b/src/main/java/nextstep/ladder/domain/LadderLines.java
@@ -0,0 +1,28 @@
+package nextstep.ladder.domain;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class LadderLines {
+    private final List<LadderLine> ladderLines;
+
+    public LadderLines(List<LadderLine> ladderLines) {
+        this.ladderLines = ladderLines;
+    }
+
+    public int size() {
+        return ladderLines.size();
+    }
+
+    public List<LadderLine> getLadderLines() {
+        return Collections.unmodifiableList(ladderLines);
+    }
+
+    @Override
+    public String toString() {
+        return "    " + ladderLines.stream()
+                .map(LadderLine::toString)
+                .collect(Collectors.joining("\n    "));
+    }
+}
diff --git a/src/main/java/nextstep/ladder/domain/PersonName.java b/src/main/java/nextstep/ladder/domain/PersonName.java
new file mode 100644
index 0000000000..07e0349221
--- /dev/null
+++ b/src/main/java/nextstep/ladder/domain/PersonName.java
@@ -0,0 +1,48 @@
+package nextstep.ladder.domain;
+
+import java.util.Objects;
+
+public class PersonName {
+    public static final int MAX_NAME_LENGTH = 5;
+
+    private String name;
+
+    public PersonName(String name) {
+        if (name == null || name.isEmpty()) {
+            throw new IllegalArgumentException("Name cannot be null or empty");
+        }
+
+        if (name.length() > MAX_NAME_LENGTH) {
+            throw new IllegalArgumentException("Name is too long");
+        }
+
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        PersonName that = (PersonName) o;
+        return Objects.equals(name, that.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(name);
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(" ".repeat((9 - name.length()) / 2));
+        sb.append(name);
+        sb.append(" ".repeat(9 - sb.length()));
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/nextstep/ladder/domain/PersonNames.java b/src/main/java/nextstep/ladder/domain/PersonNames.java
new file mode 100644
index 0000000000..d25a8c53b5
--- /dev/null
+++ b/src/main/java/nextstep/ladder/domain/PersonNames.java
@@ -0,0 +1,30 @@
+package nextstep.ladder.domain;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class PersonNames {
+    private final List<PersonName> personNames;
+
+    public PersonNames(List<String> personNames) {
+        this.personNames = personNames.stream()
+                .map(PersonName::new)
+                .collect(Collectors.toList());
+    }
+
+    public int size() {
+        return personNames.size();
+    }
+
+    public List<PersonName> getPersonNames() {
+        return Collections.unmodifiableList(personNames);
+    }
+
+    @Override
+    public String toString() {
+        return personNames.stream()
+                .map(PersonName::toString)
+                .collect(Collectors.joining(""));
+    }
+}
diff --git a/src/main/java/nextstep/ladder/view/InputView.java b/src/main/java/nextstep/ladder/view/InputView.java
new file mode 100644
index 0000000000..49a70377ae
--- /dev/null
+++ b/src/main/java/nextstep/ladder/view/InputView.java
@@ -0,0 +1,72 @@
+package nextstep.ladder.view;
+
+import nextstep.ladder.domain.PersonName;
+
+import java.util.Arrays;
+import java.util.InputMismatchException;
+import java.util.List;
+import java.util.Scanner;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public class InputView {
+    private static final Scanner scanner = new Scanner(System.in);
+
+    private InputView() {
+    }
+
+    public static int getPositiveNumberInput(String prompt) {
+        return getNumberInput(prompt, "0 이상의 정수만 허용됩니다. 다시 입력해 주세요.",
+                input -> input >= 0);
+    }
+
+    private static int getNumberInput(String prompt, String errorMessage, Predicate<Integer> predicate) {
+        System.out.println(prompt);
+
+        while (true) {
+            try {
+                int result = scanner.nextInt();
+
+                // scanner.nextInt() 는 개행 문자를 제거하지 못해 nextInt 이후 nextLine 으로 개행 제거
+                scanner.nextLine();
+
+                if (predicate.test(result)) {
+                    return result;
+                }
+
+                System.out.println(errorMessage + " input: " + result);
+            } catch (InputMismatchException e) {
+                System.out.println(errorMessage + scanner.nextLine());
+            }
+        }
+    }
+
+    public static List<String> getPersonNameInput(String prompt, String delimiter) {
+        System.out.println(prompt);
+        while (true) {
+            String line = scanner.nextLine();
+
+            String[] split = line.split(delimiter);
+            if (split.length == 0) {
+                System.out.println("이름은 하나 이상 입력되어야 합니다. 다시 입력해 주세요.");
+                continue;
+            }
+
+            List<String> result = Arrays.stream(split)
+                    .map(String::trim)
+                    .filter(s -> !s.isEmpty())
+                    .collect(Collectors.toList());
+
+            if (split.length != result.size()) {
+                System.out.println("이름은 중복될 수 없습니다. 다시 입력해 주세요.");
+                continue;
+            }
+
+            if (result.stream().allMatch(s -> s.length() <= PersonName.MAX_NAME_LENGTH)) {
+                return result;
+            }
+
+            System.out.println("이름은 최대 5글자까지 사용 가능합니다. 다시 입력해 주세요.");
+        }
+    }
+}
diff --git a/src/main/java/nextstep/ladder/view/ReviewView.java b/src/main/java/nextstep/ladder/view/ReviewView.java
new file mode 100644
index 0000000000..3507c31e46
--- /dev/null
+++ b/src/main/java/nextstep/ladder/view/ReviewView.java
@@ -0,0 +1,13 @@
+package nextstep.ladder.view;
+
+import nextstep.ladder.domain.Ladder;
+
+public class ReviewView {
+    private ReviewView() {
+    }
+
+    public static void printLadder(Ladder ladder) {
+        System.out.println(ladder.getPersonNames());
+        System.out.println(ladder.getLadderLines());
+    }
+}
diff --git a/src/test/java/nextstep/ladder/domain/LadderLineTest.java b/src/test/java/nextstep/ladder/domain/LadderLineTest.java
new file mode 100644
index 0000000000..398a43147b
--- /dev/null
+++ b/src/test/java/nextstep/ladder/domain/LadderLineTest.java
@@ -0,0 +1,42 @@
+package nextstep.ladder.domain;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class LadderLineTest {
+    @DisplayName("사다리 라인 정상 연결 테스트")
+    @Test
+    public void connectLine() throws Exception {
+        assertThat(new LadderLine(List.of(true,false,true)))
+                .isNotNull();
+    }
+
+    @DisplayName("사다리 라인 null or empty")
+    @Test
+    public void invalidConnectLineNullOrEmpty() throws Exception {
+        assertThatThrownBy(() -> new LadderLine(List.of()))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageStartingWith("lines cannot be null or empty");
+
+        assertThatThrownBy(() -> new LadderLine(null))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageStartingWith("lines cannot be null or empty");
+    }
+
+    @DisplayName("사다리 라인 연속해서 연결되어 생성 실패")
+    @Test
+    public void invalidConnectLine() throws Exception {
+        assertThatThrownBy(() -> new LadderLine(List.of(true,true,false)))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageStartingWith("Ladder lines cannot be connected continuously.");
+
+        assertThatThrownBy(() -> new LadderLine(List.of(false,true,true)))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageStartingWith("Ladder lines cannot be connected continuously.");
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/nextstep/ladder/domain/LadderTest.java b/src/test/java/nextstep/ladder/domain/LadderTest.java
new file mode 100644
index 0000000000..07a5fb95fb
--- /dev/null
+++ b/src/test/java/nextstep/ladder/domain/LadderTest.java
@@ -0,0 +1,28 @@
+package nextstep.ladder.domain;
+
+import nextstep.ladder.view.ReviewView;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class LadderTest {
+    @DisplayName("사다리 생성 테스트")
+    @Test
+    public void createLadder() throws Exception {
+        Ladder ladder = new Ladder(new PersonNames(List.of("1", "2", "3")), 3);
+        ReviewView.printLadder(ladder);
+
+        assertThat(ladder.getPersonNames().getPersonNames())
+                .containsExactly(new PersonName("1"), new PersonName("2"), new PersonName("3"));
+
+        assertThat(ladder.getLadderLines().size())
+                .isEqualTo(3);
+
+        assertThat(ladder.getLadderLines().getLadderLines().get(0).size())
+                .isEqualTo(2);
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/nextstep/ladder/domain/PersonNameTest.java b/src/test/java/nextstep/ladder/domain/PersonNameTest.java
new file mode 100644
index 0000000000..2ddb95d3d4
--- /dev/null
+++ b/src/test/java/nextstep/ladder/domain/PersonNameTest.java
@@ -0,0 +1,36 @@
+package nextstep.ladder.domain;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class PersonNameTest {
+    @DisplayName("사용자 이름 생성")
+    @Test
+    public void personName() throws Exception {
+        assertThat(new PersonName("12345").getName())
+                .isEqualTo("12345");
+    }
+
+    @DisplayName("사용자 이름 생성 실패, null")
+    @Test
+    public void personNameNull() throws Exception {
+        assertThatThrownBy(() -> new PersonName(null))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageStartingWith("Name cannot be null or empty");
+
+        assertThatThrownBy(() -> new PersonName(""))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageStartingWith("Name cannot be null or empty");
+    }
+
+    @DisplayName("사용자 이름 생성 실패, 최대 길이 초과")
+    @Test
+    public void personNameMaxLength() throws Exception {
+        assertThatThrownBy(() -> new PersonName("123456"))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageStartingWith("Name is too long");
+    }
+}
\ No newline at end of file