Web Presentation Patterns — 실전 예시와 템플릿
예시: Academic Life System의 MVC 구현
Page Controller 방식 (Drogon)
각 Controller가 특정 URL 그룹을 담당하고, 해당 도메인 객체와 대화한다:
// Page Controller 예시 (Drogon 스타일)
class RecommendationController : public HttpController<RecommendationController>:
// GET /recommendations/{studentId}
getRecommendations(req, resp):
studentId = req.getParameter("studentId")
semester = req.getParameter("semester")
// Domain에 위임
student = Student.find(studentId)
recommendations = student.getRecommendations(semester)
// View에 데이터 전달
data = {
"student": student,
"recommendations": recommendations,
"semester": semester
}
resp.render("recommendation_list.csp", data)
MVC 전체 흐름
Front Controller가 필요해지는 순간
Page Controller로 시작했는데 이런 상황이 생기면:
// 모든 Controller에 반복되는 코드
class StudentController:
getProfile(req, resp):
if not authenticate(req): return 401 // 반복!
if not authorize(req, "student"): return 403 // 반복!
log(req) // 반복!
...
class RecommendationController:
getRecommendations(req, resp):
if not authenticate(req): return 401 // 같은 코드!
if not authorize(req, "student"): return 403
log(req)
...
→ Front Controller로 공통 처리를 분리:
Drogon에서는 이 역할을 Filter/Middleware가 담당한다.
Application Controller 예시
복잡한 화면 흐름이 있을 때 — 예: "수강 신청 마법사" (3단계 프로세스):
class EnrollmentWizardController:
// 화면 흐름 관리
getNextPage(currentStep, input) -> PageInfo:
switch currentStep:
case "select_courses":
if input.courses.isEmpty():
return PageInfo("select_courses", error="과목을 선택하세요")
return PageInfo("confirm", data=input)
case "confirm":
if input.confirmed:
return PageInfo("result", data=processEnrollment(input))
return PageInfo("select_courses", data=input)
case "result":
return PageInfo("done")
템플릿
Page Controller 템플릿
// ============================================
// [기능영역]Controller (Page Controller)
// URL 패턴: /[base-path]/*
// ============================================
class [기능영역]Controller:
// GET /[base-path]/
index(req, resp):
[목록데이터] = [도메인객체].findAll() // 또는 findBy조건()
resp.render("[목록뷰].csp", {"items": [목록데이터]})
// GET /[base-path]/{id}
show(req, resp):
[id] = req.getParameter("id")
[객체] = [도메인객체].find([id])
if [객체] == null:
resp.renderError(404)
return
resp.render("[상세뷰].csp", {"item": [객체]})
// POST /[base-path]/
create(req, resp):
// 1. 입력 파싱
[데이터] = parseRequest(req)
// 2. 검증
errors = validate([데이터])
if errors:
resp.render("[입력폼].csp", {"errors": errors, "data": [데이터]})
return
// 3. 도메인 로직 실행
[결과] = [도메인서비스].[동작]([데이터])
// 4. 리다이렉트 또는 결과 표시
resp.redirect("/[base-path]/" + [결과].id)
// PUT /[base-path]/{id}
update(req, resp):
[id] = req.getParameter("id")
[객체] = [도메인객체].find([id])
[변경사항] = parseRequest(req)
[객체].update([변경사항])
[객체].save()
resp.redirect("/[base-path]/" + [id])
// DELETE /[base-path]/{id}
destroy(req, resp):
[id] = req.getParameter("id")
[객체] = [도메인객체].find([id])
[객체].delete()
resp.redirect("/[base-path]/")
MVC 시퀀스 다이어그램 템플릿
URL → Controller 매핑 테이블
## [프로젝트명] URL 라우팅
| HTTP Method | URL Pattern | Controller.Method | 설명 |
|-------------|------------|-------------------|------|
| GET | /[리소스]/ | [리소스]Ctrl.index | 목록 |
| GET | /[리소스]/{id} | [리소스]Ctrl.show | 상세 |
| GET | /[리소스]/new | [리소스]Ctrl.newForm | 생성 폼 |
| POST | /[리소스]/ | [리소스]Ctrl.create | 생성 처리 |
| GET | /[리소스]/{id}/edit | [리소스]Ctrl.editForm | 수정 폼 |
| PUT | /[리소스]/{id} | [리소스]Ctrl.update | 수정 처리 |
| DELETE | /[리소스]/{id} | [리소스]Ctrl.destroy | 삭제 |
View 선택 가이드
## [프로젝트명] View 전략
### Template View 사용 시 (대부분의 경우)
- 프레임워크: __________ (CSP, Jinja, Razor 등)
- 공통 레이아웃: __________ (base template)
- 뷰 파일 위치: __________
### 로직 침투 방지 규칙
- [ ] View에 3줄 이상의 연속된 로직이 없는가?
- [ ] View에서 DB를 직접 호출하지 않는가?
- [ ] 조건부 표시는 도메인 객체의 boolean 메서드를 호출하는가?
- ✅ `if student.isEligible()`
- ❌ `if student.gpa >= 3.0 and student.credits >= 130`
Page Controller vs Front Controller 비교
| 기준 | Page Controller | Front Controller | |------|----------------|-----------------| | 복잡도 | 단순한 사이트 | 복잡한 네비게이션 | | 공통 처리 | 각 Controller에서 반복 또는 상속 | 한 곳에서 일괄 처리 | | URL 구조 | /recommendations, /enroll | /app?action=recommend | | 프레임워크 | 대부분 기본 제공 | 직접 구현 또는 프레임워크 지원 | | 추천 | 대부분의 경우 이걸로 시작 | 인증/인가/로깅이 복잡할 때 |
Two Step View — 다국어/멀티 테마 시
Application Controller — 화면 흐름 State Diagram
템플릿: View에서 필요한 데이터 명세
## [프로젝트명] View 데이터 명세
| View | 필요한 데이터 | 타입 | 출처 |
|------|-------------|------|------|
| RecommendationView | student | Student | StudentMapper |
| RecommendationView | courses | List<Course> | RecommendationService |
| TranscriptView | enrollments | List<Enrollment> | Student.getEnrollments() |
### 공통 처리 (Cross-cutting)
- [ ] 인증: (방식 기술)
- [ ] 로깅: (방식 기술)
- [ ] 에러 처리: (공통 에러 페이지 또는 JSON 에러 응답)
- [ ] CORS: (필요 여부)