◆ LAB · TEAM

팀 분업 시뮬레이션

세 트랙을 한 번씩 직접 짜보기 — 실습

📍 지금 어디를 만지고 있나요?
회원과 게시판
팀 분업 (전환점)
REST API

사전 준비

이번 실습의 목표

이미 v2~v4 에서 따라하기로 만든 회원가입을, 이번에는 「프로젝트 단계」 위에서 다시 비추어 봅니다. 요구사항 → 유스케이스 → 화면 → 구현의 흐름을 직접 적어보고, 마지막에 같은 폼을 다른 디자인의 페이지에 갈아끼워봅니다 — Backend 한 줄도 안 고치고 동작하는 것을 직접 확인하세요.

1
요구사항 정의 — 어떤 데이터를 저장할까

실제 팀 프로젝트의 첫 단계입니다. 「회원가입을 만든다」 는 결정 다음에 처음 하는 일은 — 어떤 정보를 저장할지 정하는 것. 이 결정이 이후 모든 트랙(Front·Back·DB)의 약속이 됩니다.

===== 요구사항정의서 (회원가입 기능) =====
[기능명]   회원가입
[목적]     서비스 이용을 위해 사용자 정보를 등록한다
[저장 데이터]
   ┌─────────┬───────────┬──────────────────┐
   │ 의미    │ 약속 이름 │ 비고              │
   ├─────────┼───────────┼──────────────────┤
   │ 아이디  │ id        │ 50자, 중복 불가   │
   │ 비밀번호│ pwd       │ BCrypt 해시 저장  │
   │ 닉네임  │ nick      │ 30자, 화면 표시용 │
   └─────────┴───────────┴──────────────────┘
[약속의 효과]
   Front (form name) = Back (DTO 필드) = DB (컬럼명) — 한 단어로 통일
=================================================

이 표를 종이나 메모장에 적어두세요. 이후 모든 단계는 이 표를 보면서 진행합니다.

CHECKPOINT — 약속표 메모
  • 약속한 이름 3개 (id / pwd / nick) 가 한 단어로 통일됐는가?
  • 이 표가 눈에 보이는 자리 (메모장, 화이트보드, 노션 페이지 등) 에 있는가?
2
유스케이스 한 줄로 — 사용자가 어떻게 쓰는가

요구사항 다음 단계는 「사용자가 이 기능을 어떻게 사용하는가」 를 한 줄 시나리오로 적는 것입니다. 화면을 그리기 전에, 동작의 흐름을 먼저 글로 합의합니다.

===== 유스케이스 (회원가입) =====
[Actor]   서비스를 처음 방문한 사용자
[Trigger] 회원가입 페이지에 접속

[Flow]
  ① 사용자가 폼에 id, pwd, nick 3 칸을 입력한다
  ② 「가입하기」 버튼을 누른다
  ③ 시스템이 입력값을 검증한다
  ④ DB 의 mymember 테이블에 한 행을 INSERT 한다
  ⑤ 로그인 페이지로 이동한다 (redirect:/login)

[입력] → [처리]      → [결과]
 폼 3칸  → INSERT     → 로그인 페이지
=================================
CHECKPOINT — 입력→처리→결과 한 줄
  • 「폼 3칸 입력 → INSERT → 로그인 페이지로 이동」 한 줄을 자기 말로 다시 적을 수 있는가?
  • 이 한 줄에서 ① 입력 데이터의 이름이 Step 1 의 약속표와 일치하는가?
3
Frontend 트랙 — 단순 폼 작성

당신이 「화면 디자이너」 라고 가정합니다. 서버는 아직 한 줄도 없습니다. 약속한 이름으로 name 속성만 맞추고, 나머지 시간은 디자인에 씁니다.

<!-- src/main/webapp/WEB-INF/views/signup.jsp -->
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>회원가입</title>
<style>
  .signup-form { max-width: 360px; margin: 60px auto; padding: 24px;
                 border-radius: 12px; background:#fafafa;
                 box-shadow: 0 2px 8px rgba(0,0,0,.08); }
  .signup-form h2 { text-align:center; margin: 0 0 16px; }
  .signup-form input { display:block; width:100%; padding:10px;
                       margin:8px 0; border:1px solid #ddd;
                       border-radius:6px; box-sizing:border-box; }
  .signup-form button { width:100%; padding:12px;
                        background:#3b82f6; color:#fff;
                        border:0; border-radius:6px; cursor:pointer; }
</style></head><body>
  <form action="/signup" method="post" class="signup-form">
    <h2>회원가입</h2>
    <input name="id"   placeholder="아이디">
    <input name="pwd"  type="password" placeholder="비밀번호">
    <input name="nick" placeholder="닉네임">
    <button>가입하기</button>
  </form>
</body></html>

브라우저에서 이 파일을 열어 화면이 약속대로 보이는지 확인하세요. 아직 「가입하기」 를 눌러도 됩니다 — 어떻게 동작하는지 다음 단계에서 확인합니다.

CHECKPOINT — F12 개발자 도구
  • F12 → Elements 탭에서 <input name="id">, name="pwd", name="nick" 세 개가 정확히 약속표대로인가?
  • 오타가 있으면 (예: name="ID" 대문자, name="password") 지금 잡으세요. 합치는 순간 사고 납니다.
4
Backend 트랙 — Controller + Service + DTO (v2~v4 재확인)

이번에는 「백엔드 개발자」 입니다. 화면이 없어도 빈 HTML 폼으로 데이터를 받을 수 있습니다.

// com/smhrd/domain/Member.java
package com.smhrd.domain;
import lombok.*;

@Data @AllArgsConstructor @NoArgsConstructor
public class Member {
    private String id;     // ← 약속표의 첫 칸
    private String pwd;    // ← 약속표의 둘째 칸
    private String nick;   // ← 약속표의 셋째 칸
}

// com/smhrd/controller/SignupController.java
package com.smhrd.controller;
import com.smhrd.domain.Member;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class SignupController {

    @PostMapping("/signup")
    public String signup(Member m) {
        System.out.println("[Backend] 받은 데이터: " + m);
        // service.signup(m);  // 다음 단계에서 합치기
        return "redirect:/login";
    }
}

Tomcat 을 띄우고, Step 2 의 폼에서 임의의 값으로 가입하기 를 눌러보세요. DB 는 아직 안 만들어졌어도 Controller 까지는 데이터가 들어옵니다.

CHECKPOINT — Eclipse 콘솔
  • Eclipse 콘솔에 [Backend] 받은 데이터: Member(id=hong, pwd=1234, nick=홍길동) 가 찍히는가?
  • 세 필드가 모두 채워졌는가? null 이 하나라도 있다면 폼 name ↔ DTO 필드명 사이에 오타가 있다는 신호.
5
DB 트랙 — 테이블 + 가데이터 검증

Backend 가 받은 데이터가 마지막에 들어가는 자리. 약속한 이름 그대로 컬럼을 만들고, 가데이터로 SELECT 가 잘 되는지만 짧게 확인합니다.

-- 1) 테이블 생성 (약속표대로)
CREATE TABLE mymember (
  id   VARCHAR(50)  PRIMARY KEY,
  pwd  VARCHAR(100) NOT NULL,
  nick VARCHAR(30)
);

-- 2) 가데이터 3 건
INSERT INTO mymember VALUES ('hong123', 'hashed_pw_1', '홍길동');
INSERT INTO mymember VALUES ('kim456',  'hashed_pw_2', '김영희');
INSERT INTO mymember VALUES ('lee789',  'hashed_pw_3', '이철수');

-- 3) SELECT / UPDATE / DELETE 검증
SELECT * FROM mymember;
SELECT id, nick FROM mymember WHERE id = 'hong123';

UPDATE mymember SET nick = '홍길순' WHERE id = 'hong123';
SELECT id, nick FROM mymember WHERE id = 'hong123';

DELETE FROM mymember WHERE id = 'lee789';
SELECT COUNT(*) FROM mymember;
CHECKPOINT — MySQL Workbench
  • SELECT * FROM mymember; 결과 행이 정확히 2 행 (DELETE 후) 인가?
  • id = 'hong123' 의 nick 이 「홍길순」 으로 바뀌었는가?
  • DESC mymember; 의 컬럼명이 약속표의 「DB 컬럼명」 과 정확히 일치하는가?
6
★ 폼만 갈아끼우기 — 같은 데이터, 다른 디자인

이번 차시의 가장 중요한 활동입니다. v2~v4 의 단순 폼을 — 완전히 다른 디자인의 페이지 로 통째로 갈아끼웁니다. name="id", name="pwd", name="nick" 약속만 지키면, Backend 와 DB 는 한 줄도 안 바꿔도 동작합니다.

① 기존 폼 (v2~v4 에서 만든 단순 버전)

<!-- src/main/webapp/WEB-INF/views/signup.jsp — 기존 -->
<form action="/signup" method="post">
  <input name="id">
  <input name="pwd" type="password">
  <input name="nick">
  <button>가입</button>
</form>

② 새 디자인 폼 — 통째로 복사해서 signup.jsp 를 덮어써보세요

<!-- src/main/webapp/WEB-INF/views/signup.jsp — 디자이너가 새로 그려준 버전 -->
<!DOCTYPE html>
<html lang="ko"><head><meta charset="UTF-8"><title>회원가입</title>
<style>
  body { margin: 0; min-height: 100vh; display: flex;
         align-items: center; justify-content: center;
         background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
         font-family: 'Pretendard', sans-serif; }
  .card { width: 380px; padding: 36px 32px; border-radius: 20px;
          background: #fff; box-shadow: 0 20px 50px rgba(0,0,0,.25); }
  .card h2 { margin: 0 0 6px; font-size: 26px; color: #1f2937; }
  .card .greet { margin: 0 0 24px; color: #6b7280; font-size: 14px; }
  .card label { display: block; margin: 14px 0 6px;
                font-size: 13px; color: #374151; font-weight: 600; }
  .card input { width: 100%; padding: 12px 14px;
                border: 1px solid #e5e7eb; border-radius: 10px;
                font-size: 15px; box-sizing: border-box;
                transition: border-color .2s; }
  .card input:focus { outline: none; border-color: #667eea; }
  .card button { width: 100%; margin-top: 22px; padding: 14px;
                 border: 0; border-radius: 10px; cursor: pointer;
                 font-size: 15px; font-weight: 600; color: #fff;
                 background: linear-gradient(135deg, #667eea, #764ba2); }
  .card button:hover { opacity: 0.92; }
  .card .footnote { margin-top: 18px; text-align: center;
                    font-size: 12px; color: #9ca3af; }
</style></head><body>
  <form action="/signup" method="post" class="card">
    <h2>Welcome 👋</h2>
    <p class="greet">3 칸만 채우면 시작할 수 있어요</p>

    <label>아이디</label>
    <input name="id" placeholder="영문, 숫자 50자 이내">

    <label>비밀번호</label>
    <input name="pwd" type="password" placeholder="비밀번호">

    <label>닉네임</label>
    <input name="nick" placeholder="화면에 표시될 이름">

    <button>가입하고 시작하기</button>
    <p class="footnote">이미 계정이 있나요? 로그인 페이지로</p>
  </form>
</body></html>

③ 직접 확인

  1. 위 새 디자인 폼을 통째로 복사해 signup.jsp 를 덮어쓰세요.
  2. Tomcat 을 재시작하고 /signup 페이지로 접속.
  3. 화면이 그래디언트 배경 + 카드형 디자인으로 바뀌었는지 확인.
  4. 새 디자인에서 임의의 값으로 가입을 시도하세요.
  5. Controller / Service / Mapper / DB 어느 것도 손대지 않았습니다.
CHECKPOINT — 디자인이 바뀌어도 동작은 동일
  • 새 디자인의 name 속성 3개가 정확히 id / pwd / nick 인가? (F12 Elements 로 확인)
  • 가입 시도 후 Eclipse 콘솔에 Member 객체가 채워져서 찍히는가?
  • MySQL 에서 SELECT * FROM mymember 시 새로 가입한 행이 보이는가?
  • ★ Backend 코드를 단 한 줄도 고치지 않았는가? (이게 「데이터 약속」 의 효과)
7
(선택) 깨뜨려보기 — 일부러 약속을 어긴다

마지막으로 「약속이 깨지면 어떻게 망가지는가」 를 직접 봅니다. 다음 두 가지를 차례로 시도하세요.

실험 ① 폼 name 만 바꾸기

<!-- signup.jsp 의 첫 input 만 수정 -->
<input name="userId"   placeholder="아이디">
<!-- 나머지 (pwd, nick) 는 그대로 -->

다시 가입을 시도하세요. 콘솔에 어떤 값이 찍히는지 확인합니다.

실험 ② Mapper 컬럼명 바꾸기 (실험 후 원복)

<!-- MemberMapper.xml — INSERT 의 컬럼명만 수정 -->
INSERT INTO mymember (id, password, nick)   <!-- pwd → password -->
VALUES (#{id}, #{pwd}, #{nick})

다시 가입을 시도하세요. 어떤 예외가 나오는지 확인하고, 실험 후 반드시 원래대로 되돌립니다.

CHECKPOINT — 사고의 모양
  • 실험 ① 결과 콘솔: Member(id=null, pwd=*, nick=*) — id 만 null. 화면도 서버도 안 죽는데 데이터만 비어있는 가장 헷갈리는 사고.
  • 실험 ② 결과: SQLException: Unknown column 'password' in 'field list' — DB 가 즉시 거부. 차라리 빨리 잡힘.
  • 👉 실험 ① 류 사고는 「화면이 빈 칸으로 INSERT 된 row」 만 남기고 조용히 지나갑니다. 그래서 네이밍 약속표를 눈에 보이는 자리에 두는 것이 그토록 중요합니다.
  • 실험 후 두 변경 사항을 모두 원래대로 돌렸는가?

실습 완료 체크리스트

Step 1 — 요구사항정의서 작성 (id / pwd / nick 약속표)
Step 2 — 유스케이스 한 줄 작성 (입력 → 처리 → 결과)
Step 3 — Frontend 단순 폼 작성, F12 로 name 속성 확인
Step 4 — Controller 가 Member 객체로 자동 바인딩 (v2~v4 재확인)
Step 5 — mymember 테이블 + 가데이터 SELECT 동작
★ Step 6 — 같은 폼을 카드형 디자인으로 갈아끼움. Backend 안 고치고 동작
(선택) Step 7 — null 바인딩 / NoSuchColumn 사고를 직접 본 후 원복