셋이서 만들기 — 회원가입 하나로 배우는 협업
이 페이지는 딱 회원가입 기능 하나만 가지고 세 사람이 어떻게 동시에 일하는지를 보여줍니다. 전체 데이터 흐름을 먼저 보고, 그 다음 각자의 역할 페이지로 들어가서 자세히 배우는 구조입니다. 이 한 가지를 제대로 이해하면 로그인·게시판 같은 나머지 기능은 같은 패턴의 반복이에요.
전제 — 시작 시점의 상태
- member 테이블은 이미 만들어져 있다 — Day 0에 DB 담당이 만들어둠
- 컬럼:
user_id,password,nickname,email,phone,hobbies,favorite_color,job,reg_date - 이제 회원가입 기능을 세 사람이 동시에 개발하기 시작하는 순간이 Day 1
한땀한땀 따라가는 데이터 흐름 — 회원가입 성공 케이스
먼저 완성된 기능이 어떻게 동작하는지 전체 흐름을 처음부터 끝까지 봅시다. Step 1부터 Step 10까지 천천히 따라가면서 "사용자가 가입 버튼을 누른 순간부터, DB에 저장되어 성공 화면이 뜨기까지" 의 여정을 한 번에 이해하는 게 목적이에요. 이 10단계만 손에 잡히면 나머지는 응용입니다.
사용자가 회원가입 페이지에 도착
사용자가 상단 메뉴에서 "회원가입" 링크를 누르면 브라우저가 /join.jsp 를 요청합니다.
이 시점엔 서버 쪽 로직이 거의 없어요 — Tomcat이 JSP 파일을 읽고 HTML로 변환해서 브라우저에게 돌려주기만 합니다.
사용자가 정보를 입력하고 "가입하기" 클릭
사용자가 아이디·비밀번호·닉네임·이메일·취미·색깔·직업을 채우는 동안 서버로는 아무것도 안 갑니다. 브라우저 안에서만 일어나는 일이에요. 버튼을 눌러야 그때 서버 요청이 시작됩니다.
브라우저가 POST /register 요청 전송
"가입하기" 버튼이 눌리는 순간 브라우저는 모든 <input> 의 name과 value를 쌍으로 묶어 HTTP 요청 본문에 담습니다:
POST /test/register HTTP/1.1 Host: localhost:8081 Content-Type: application/x-www-form-urlencoded userId=test_user&password=1234&passwordConfirm=1234 &nickname=테스트유저&email=test@example.com&phone=010-1234-5678 &hobbies=독서&hobbies=운동&hobbies=음악 &favoriteColor=파랑&job=학생
주목할 점: hobbies 가 세 번 반복 됩니다. 사용자가 체크박스 3개를 선택했으니까요. 이게 나중에 getParameterValues 가 배열을 돌려주는 이유입니다.
Tomcat이 URL → Servlet 매핑 확인
@WebServlet("/register") 어노테이션 표를 보고 RegisterServlet 호출Tomcat은 내부 매핑 표를 보고 "/register 요청 → RegisterServlet.service() 호출" 을 결정합니다. 여기서 개발자 코드에 if-else가 없다는 점이 중요해요 — Tomcat이 알아서 합니다. URL 라우팅 페이지 참고.
Servlet이 form 값을 변수로 추출
Controller의 첫 번째 일. req.getParameter("userId") 같은 호출들이 차례로 실행되면서
HTTP 문자열 파라미터들이 Java 변수에 담깁니다. 특히 hobbies 는 getParameterValues 로 String 배열을 받아옵니다.
System.out.println 으로 찍힌 값들 — 모든 필드가 올바르게 추출됨Controller가 배열을 CSV로, 변수들을 DTO에 담기
여기가 Controller가 "진짜 일" 하는 순간입니다. hobbies 배열은 String.join(",", hobbiesArr) 로
CSV 문자열("독서,운동,음악")이 되고, 흩어져있던 변수들은 한 덩어리 Member DTO 객체로 재조립됩니다.
이제 DB가 삼킬 수 있는 형태가 됐어요. (DTO가 무엇인지는 Backend 가이드 에서 자세히 다룹니다.)
Member newMember = new Member(); newMember.setUserId("test_user"); newMember.setHobbies("독서,운동,음악"); // ← CSV로 번역됨 newMember.setFavoriteColor("파랑"); newMember.setJob("학생"); // ... 나머지 필드도 동일
Controller가 DAO.insert() 호출
memberDao.insert(newMember) 한 줄이 Controller의 마지막 책임입니다.
이 호출이 MyBatis에게 넘어가고, MyBatis는 MemberMapper.xml 의 <insert id="insert"> 쿼리를 찾아서
#{userId}, #{hobbies} 같은 자리에 Member DTO의 필드 값을 채워넣습니다.
DB가 member 테이블에 INSERT 실행, 1 row affected
MyBatis가 만든 최종 SQL이 MariaDB에 도달하고, 실제로 member 테이블에 새 행이 추가됩니다.
DB는 "1 row affected" 를 MyBatis에게 돌려주고, MyBatis는 그 결과를 int 로 DAO에게, DAO는 Servlet에게 리턴합니다.
Servlet이 응답 결정 — sendRedirect
DAO가 "1 row affected" 를 돌려줬으니 가입 성공. Servlet은 resp.sendRedirect("join_success.jsp") 로
브라우저에게 "이제 이 URL로 다시 가" 라고 알려줍니다. 이게 Servlet의 마지막 한 줄이에요.
브라우저가 성공 페이지 수신 · 사용자에게 완료 화면
브라우저는 302 응답을 받고 주소창 URL을 /test/join_success.jsp 로 바꾼 뒤 새 요청을 보냅니다.
이 두 번째 요청은 다시 Tomcat → JSP → HTML 로 흘러가서 사용자에게 "가입 완료!" 화면을 보여줍니다. 사용자 여정 끝.
sendRedirect 의 특징이에요 — 두 번 요청, URL 바뀜, request 데이터 유지 안 됨.
각자의 역할 — 나의 영역으로 들어가기
위 10단계 흐름을 봤다면, 이제 자기 역할의 자세한 가이드를 보러 갈 차례입니다. 각 페이지는 독립적으로 완결되어 있어서, 자기 담당 페이지 하나만 봐도 충분히 작업을 시작할 수 있습니다.
Frontend 담당 →
화면을 만드는 사람을 위한 가이드.
form 필드 명세, 디자인 시안 A/B, 같은 데이터로 4가지 UI 만들기, checkbox 복수 선택까지.
주제: JSP · HTML · CSS · form name 약속
Backend 담당 →
서버 로직을 만드는 사람을 위한 가이드.
DTO(데이터 그릇) 개념부터, Servlet에서 form 값 받기, getParameterValues, Member 객체 조립까지.
주제: Servlet · DTO · 데이터 번역 · Simple Page
Database 담당 →
쿼리를 만드는 사람을 위한 가이드.
가짜 데이터로 INSERT 실험, 에러 케이스 일부러 일으키기, MyBatis Mapper XML 작성까지.
주제: SQL · DBeaver · MyBatis Mapper
사용자 입장에서 본 회원가입 흐름
개발자 관점(form name, SQL 쿼리)으로만 협업을 보면 중요한 걸 놓치기 쉽습니다. 사용자가 어떤 경험을 하는지부터 먼저 그려봐야, "무엇을 주고받을지" 와 "UI를 어떻게 만들지" 가 자연스럽게 드러납니다. 세 사람이 같이 이 여정을 그리는 게 Day 1 회의의 진짜 출발점이에요.
사용자의 여정 — 7단계
- Step 1 — 메인 페이지 도착:
localhost:8081/test/접속, 상단 메뉴에 "회원가입" 링크 - Step 2 — "회원가입" 링크 클릭:
GET /join.jsp→ 빈 폼 페이지 로드 - Step 3 — 폼 화면 진입 · 정보 입력: 아이디·비번·닉네임·이메일·취미·색·직업 입력 (서버 트래픽 없음)
- Step 4 — "가입하기" 버튼 클릭:
POST /register로 form 전송 - Step 5 — 서버 처리: RegisterServlet → DTO 조립 → DAO → INSERT
- Step 6 — 결과 피드백: 성공 시
sendRedirect("join_success.jsp"), 실패 시?error=... - Step 7 — 다음 행동 유도: "로그인하러 가기" 버튼 또는 자동 메인 이동
· Step 3: 한 페이지 vs 여러 스텝으로 나누기
· Step 3: 아이디 중복 확인을 실시간 AJAX로 vs 제출 후 일괄로
· Step 6: 에러를 alert vs 폼 위 배너 vs 인라인 메시지로
· Step 7: 자동 로그인 vs 수동 로그인 페이지 이동
이런 선택들이 Frontend 디자인뿐 아니라 Backend 로직과 DB 쿼리에도 영향을 줍니다. Day 1에 같이 결정해야 하는 이유예요.
협업의 핵심 — 무엇을 주고받는가
회원가입 기능을 만드는 동안, 세 사람 사이에는 딱 3가지 주고받음이 있습니다. 이것만 선명하면 나머지는 각자 자기 자리에서 만듭니다.
주고받음 ① · Frontend → Backend
전달 내용: form이 서버로 보낼 필드 명세
"name="userId", name="password" ... 이런 값들을 <input> 으로 보낼 거예요" 라는 약속.
Backend는 이 명세만 있으면 자기 쪽 코드를 시작할 수 있습니다.
주고받음 ② · Backend → Database
전달 내용: "INSERT 할 때 쓸 DTO 필드 목록"
"Member DTO의 8개 필드를 member 테이블에 넣어야 해요" 라는 요청.
DB는 이걸 받아서 실제 INSERT 쿼리 문장을 다듬습니다.
주고받음 ③ · Database → Backend
전달 내용: 완성된 INSERT 쿼리 (MyBatis XML 형태)
"가짜 데이터로 수십 번 넣어보고 다듬은 최종본이에요. 그대로 써도 돼요" 라는 결과물. Backend는 자기 Servlet에서 이 쿼리를 호출하기만 하면 됩니다.
잠깐 — "Mockup" 이라는 단어부터
이 페이지(와 역할별 가이드)에서 앞으로 "mockup", "mock", "가짜 데이터" 라는 표현이 자주 나옵니다. 처음 듣는 단어일 수 있으니 먼저 짚고 갈게요.
Mockup이란?
자동차 회사가 신차 출시 전에 점토로 모형을 만드는 걸 본 적 있죠? 굴러가진 않지만 모양은 진짜 같아서 디자인을 미리 보고 결정할 수 있게 해줍니다. 이게 mockup이에요. "진짜는 아닌데 진짜처럼 보이는 가짜".
소프트웨어에서도 똑같습니다. 이 수업에서 만나게 될 mockup은 3가지 종류가 있어요:
① 디자인 Mockup
(화면 시제품)
Frontend가 만드는 "이렇게 생기면 어떨까" 의 화면 미리보기. 기능은 동작 안 하지만 모양만 진짜 같음. 우리 페이지의 "시안 A / 시안 B" 가 이 종류예요.
② Mock 데이터
(가짜 값)
DB나 서버가 아직 준비 안 됐을 때 화면이나 코드에 임시로 박아넣는 값. 예를 들어 게시판 화면에 "공지사항입니다" 같은 글 6건을 일단 하드코딩해두는 거.
③ Mock 구현체
(가짜 객체)
진짜 DAO 대신 흉내만 내는 Java 클래스. MemberDaoMock 같은 이름. DB 없이도
메모리 안에서 가짜로 동작합니다.
Frontend가 Backend 완성을 기다리지 않고 화면 디자인을 끝낼 수 있고, Backend가 DB 완성을 기다리지 않고 Servlet 로직을 검증할 수 있어요.
나중에 진짜가 준비되면 가짜를 빼고 그 자리에 진짜를 끼워넣습니다. 같은 모양(같은 인터페이스)이라서 가능한 일이에요. 이게 협업의 핵심 트릭입니다.
Day 1 회의 — 3가지만 합의하면 된다
회원가입 하나에 대해 세 사람이 첫 회의에서 정해야 할 건 단 세 가지입니다.
- form 필드 명세 — 어떤
name들이 서버로 전달되는가. 대소문자·오타까지 정확히. Frontend가 초안을 내고 Backend가 확인. - URL과 method —
/register (POST)로 고정. Form action과 Servlet@WebServlet이 이 URL을 정확히 써야 함. - DTO(Member 클래스) 필드 —
userId,password,nickname,email,phone,hobbies,favoriteColor,job. DB 컬럼명과의 매핑 방식(언더스코어 ↔ 카멜케이스)도 여기서 결정.
이 세 가지가 종이 한 장에 적히면 Day 1은 끝. 나머지는 각자 자기 영역의 가이드 페이지로 가서 작업 시작입니다.
에러 상황에서의 팀 조율 — "이미 존재하는 회원" 시나리오
정상 흐름만 다루면 실제 개발의 절반도 안 본 거예요. 회원가입에서 가장 흔한 에러 케이스 — 이미 가입된 아이디로 다시 시도 — 를 예로, 세 사람이 어떻게 조율해야 하는지 따라가봅시다.
사용자가 이미 존재하는 아이디로 가입 시도
사용자는 자기 아이디가 이미 있는지 모릅니다. 여기까지는 성공 케이스와 완전히 동일합니다. Frontend는 에러를 "예상" 하지 않고 정상적으로 폼을 제출합니다.
Servlet이 평소처럼 DAO.insert() 호출
Controller는 파라미터를 받고 Member DTO를 조립하고 memberDao.insert(newMember) 를 호출합니다.
실패하면 그때 대응해요.
DB가 Primary Key 위반 에러를 던진다
SQLIntegrityConstraintViolationException 발생
user_id 가 PK이므로 동일한 user_id로 INSERT 하면 DB가 거부합니다. 에러 코드는 MariaDB의 1062번,
"Duplicate entry 'test_user' for key 'member.PRIMARY'". 이 예외가 MyBatis로 올라가고, MyBatis는 이걸
PersistenceException 으로 감싸서 Backend에게 던집니다.
Servlet의 try-catch가 예외를 감지
Backend가 insert() 호출을 try 블록 안에 넣어뒀다면, 위 예외가 catch 블록으로 흘러듭니다.
여기서 Backend가 해야 할 일: 사용자 친화적 에러 코드로 변환해서 리다이렉트.
try { memberDao.insert(newMember); resp.sendRedirect("join_success.jsp"); } catch (PersistenceException e) { // PK 위반을 감지 if (e.getMessage().contains("Duplicate entry")) { resp.sendRedirect("join.jsp?error=dup_id"); // ← 약속된 error 코드 return; } throw e; }
Caused by: Duplicate entry 'test_user' 가 진짜 원인Servlet이 에러 코드 담아서 join.jsp로 리다이렉트
?error=dup_id
Backend는 join.jsp?error=dup_id 로 보낸다고 약속했고,
Frontend는 error=dup_id 가 오면 "이미 가입된 아이디" 메시지를 보여준다고 약속했습니다.
이 약속이 Day 1에 문서화되어 있어야 합니다.
Frontend가 에러 파라미터 읽어서 사용자에게 표시
Frontend는 join.jsp 에서 ${param.error} 값을 확인하고, 거기에 맞는 메시지를 출력합니다.
빨간 배너 + 문제 필드 하이라이트 + 친절한 안내문까지 붙이면 사용자 입장에서 무엇이 잘못됐는지 한눈에 알 수 있어요.
<%-- join.jsp 상단에 에러 배너 --%> <c:if test="${param.error == 'dup_id'}"> <div class="error-banner"> 이미 가입된 아이디입니다. 다른 아이디를 사용해주세요. </div> </c:if>
이 시나리오가 동작하려면 — Day 1에 합의해야 할 "에러 처리 프로토콜"
위 6단계가 매끄럽게 돌아가려면 세 사람이 Day 1 회의에서 에러 처리 규약을 명시적으로 정해야 합니다:
- DB DB 담당이 공유할 것: "중복 PK 에러는 MariaDB 1062 코드,
Duplicate entry문자열이 메시지에 포함됩니다." - BACK Backend 담당이 결정할 것: "어떤 예외를 어떤
error코드로 변환할지 매핑 표 문서화. 예: 중복 PK →dup_id, 비번 불일치 →pw_mismatch." - FRONT Frontend 담당이 결정할 것: "각
error코드에 대응되는 사용자 메시지와 UI (배너·필드 하이라이트·모달 등)."
다른 에러 케이스도 같은 원리 — 빠른 참조표
| 에러 상황 | DB 반응 / 담당 | Backend 처리 / 담당 | Frontend 표시 / 담당 |
|---|---|---|---|
| 중복 아이디 | DB PK 위반Duplicate entry |
BACK catch → ?error=dup_id |
FRONT "이미 가입된 아이디" |
| 비밀번호 불일치 | — (DB까지 가지도 않음) | BACK !password.equals(confirm) 체크 |
FRONT 실시간 JS + 서버 메시지 |
| 필수값 누락 | DB NOT NULL 위반 | BACK null/빈값 체크 | FRONT HTML required 속성 |
| 이메일 형식 오류 | — (DB는 형식 검증 안 함) | BACK 정규식 검증 | FRONT type="email" |
| DB 연결 실패 | DB SQLException |
BACK 500 에러 + 로그 | FRONT 500 전용 페이지 |
5일간 타임라인 시뮬레이터
각자의 작업이 5일간 어떻게 맞물리는지 따라가봅시다.