세 트랙을 한 번씩 직접 짜보기 — 실습
이미 v2~v4 에서 따라하기로 만든 회원가입을, 이번에는 「프로젝트 단계」 위에서 다시 비추어 봅니다. 요구사항 → 유스케이스 → 화면 → 구현의 흐름을 직접 적어보고, 마지막에 같은 폼을 다른 디자인의 페이지에 갈아끼워봅니다 — Backend 한 줄도 안 고치고 동작하는 것을 직접 확인하세요.
실제 팀 프로젝트의 첫 단계입니다. 「회원가입을 만든다」 는 결정 다음에 처음 하는 일은 — 어떤 정보를 저장할지 정하는 것. 이 결정이 이후 모든 트랙(Front·Back·DB)의 약속이 됩니다.
===== 요구사항정의서 (회원가입 기능) =====
[기능명] 회원가입
[목적] 서비스 이용을 위해 사용자 정보를 등록한다
[저장 데이터]
┌─────────┬───────────┬──────────────────┐
│ 의미 │ 약속 이름 │ 비고 │
├─────────┼───────────┼──────────────────┤
│ 아이디 │ id │ 50자, 중복 불가 │
│ 비밀번호│ pwd │ BCrypt 해시 저장 │
│ 닉네임 │ nick │ 30자, 화면 표시용 │
└─────────┴───────────┴──────────────────┘
[약속의 효과]
Front (form name) = Back (DTO 필드) = DB (컬럼명) — 한 단어로 통일
=================================================
이 표를 종이나 메모장에 적어두세요. 이후 모든 단계는 이 표를 보면서 진행합니다.
요구사항 다음 단계는 「사용자가 이 기능을 어떻게 사용하는가」 를 한 줄 시나리오로 적는 것입니다. 화면을 그리기 전에, 동작의 흐름을 먼저 글로 합의합니다.
===== 유스케이스 (회원가입) =====
[Actor] 서비스를 처음 방문한 사용자
[Trigger] 회원가입 페이지에 접속
[Flow]
① 사용자가 폼에 id, pwd, nick 3 칸을 입력한다
② 「가입하기」 버튼을 누른다
③ 시스템이 입력값을 검증한다
④ DB 의 mymember 테이블에 한 행을 INSERT 한다
⑤ 로그인 페이지로 이동한다 (redirect:/login)
[입력] → [처리] → [결과]
폼 3칸 → INSERT → 로그인 페이지
=================================
당신이 「화면 디자이너」 라고 가정합니다. 서버는 아직 한 줄도 없습니다. 약속한 이름으로 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>
브라우저에서 이 파일을 열어 화면이 약속대로 보이는지 확인하세요. 아직 「가입하기」 를 눌러도 됩니다 — 어떻게 동작하는지 다음 단계에서 확인합니다.
<input name="id">, name="pwd", name="nick" 세 개가 정확히 약속표대로인가?name="ID" 대문자, name="password") 지금 잡으세요. 합치는 순간 사고 납니다.이번에는 「백엔드 개발자」 입니다. 화면이 없어도 빈 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 까지는 데이터가 들어옵니다.
[Backend] 받은 데이터: Member(id=hong, pwd=1234, nick=홍길동) 가 찍히는가?name ↔ DTO 필드명 사이에 오타가 있다는 신호.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;
SELECT * FROM mymember; 결과 행이 정확히 2 행 (DELETE 후) 인가?id = 'hong123' 의 nick 이 「홍길순」 으로 바뀌었는가?DESC mymember; 의 컬럼명이 약속표의 「DB 컬럼명」 과 정확히 일치하는가?이번 차시의 가장 중요한 활동입니다. v2~v4 의 단순 폼을 — 완전히 다른 디자인의 페이지 로 통째로 갈아끼웁니다. name="id", name="pwd", name="nick" 약속만 지키면, Backend 와 DB 는 한 줄도 안 바꿔도 동작합니다.
<!-- 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>
<!-- 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>
signup.jsp 를 덮어쓰세요./signup 페이지로 접속.name 속성 3개가 정확히 id / pwd / nick 인가? (F12 Elements 로 확인)SELECT * FROM mymember 시 새로 가입한 행이 보이는가?마지막으로 「약속이 깨지면 어떻게 망가지는가」 를 직접 봅니다. 다음 두 가지를 차례로 시도하세요.
<!-- signup.jsp 의 첫 input 만 수정 -->
<input name="userId" placeholder="아이디">
<!-- 나머지 (pwd, nick) 는 그대로 -->
다시 가입을 시도하세요. 콘솔에 어떤 값이 찍히는지 확인합니다.
<!-- MemberMapper.xml — INSERT 의 컬럼명만 수정 -->
INSERT INTO mymember (id, password, nick) <!-- pwd → password -->
VALUES (#{id}, #{pwd}, #{nick})
다시 가입을 시도하세요. 어떤 예외가 나오는지 확인하고, 실험 후 반드시 원래대로 되돌립니다.
Member(id=null, pwd=*, nick=*) — id 만 null. 화면도 서버도 안 죽는데 데이터만 비어있는 가장 헷갈리는 사고.SQLException: Unknown column 'password' in 'field list' — DB 가 즉시 거부. 차라리 빨리 잡힘.