리팩터링 계기
개인 프로젝트를 만들면서 URL을 대충 /products_main, /product/new 이런 식으로 만들었는데,
김영한 선생님의 HTTP 강의를 들으면서 “아, 내 URL 설계가 엉망이구나”를 깨닳았다.
특히 RESTful 원칙을 배우면서
- URL은 자원(리소스)을 표현해야 한다
- 동사가 아닌 명사 중심으로
- HTTP 메서드(GET, POST, PUT, DELETE)로 행위를 표현
내 코드를 다시 보니… 리소스도 뒤죽박죽, 메서드도 일관성이 없었다.
REST => 자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 모든 것을 의미
REST 구성 요소
- 자원(Resource) : HTTP URL
- 자원에 대한 행위 : HTTP Method
- 자원에 대한 행위의 내용 : HTTP Message Pay Load
기존 코드의 문제점
상품 컨트롤러
@GetMapping("/productMain")
public String productMain() { ... }
@GetMapping("/product/new")
public String newProduct(...) { ... }
@PostMapping("/product/new")
public String createProduct(...) { ... }
@PutMapping("/product/{id}")
public String updateProduct(...) { ... }
@DeleteMapping("/product/{id}")
public String deleteProduct(...) { ... }
문제점:
- URL이 리소스 중심이 아닌 기능 중심으로 설계됨
- 단수형/복수형이 혼재 (product vs productMain)
- HTML Form 에서 PUT, DELETE 메서드를 지원하지 않음 (처음 알게 되었다…)
사용자 컨트롤러
@GetMapping("/users/new")
public String register(...) { ... }
@PostMapping("/users")
public String registerUser(...) { ... }
@PutMapping("/users/{id}")
public String updateUser(...) { ... }
@DeleteMapping("/users/{id}")
public String deleteUser(...) { ... }
문제점:
- 대소문자 혼용으로 일관성 부족 (UserRegisterSuccess)
/users가 목록과 등록을 모두 처리- 리소스 중심이 아닌 기능 중심 URL 구조
리팩토링 원칙
- URL을 리소스(명사) 중심으로 설계:
/users,/products - HTML Form 제약 고려: POST로 PUT/DELETE 대체
- 행위를 명확히 분리:
new(등록),edit(수정),delete(삭제)
리팩토링 결과
상품 컨트롤러
// 목록 조회
@GetMapping("/products")
public String productList(Model model) {
model.addAttribute("products", productService.findAllWithImages());
return "products/productList";
}
// 등록 폼
@GetMapping("/products/new")
public String newProduct(@AuthenticationPrincipal AuthUser authUser, Model model) {
return "products/new";
}
// 등록 처리
@PostMapping("/products")
public String createProduct(ProductRegistrationRequest request, ...) {
return "redirect:/products/success";
}
// 수정 폼
@GetMapping("/products/{id}/edit")
public String editProduct(@PathVariable Long id, Model model) {
return "products/edit";
}
// 수정 처리
@PostMapping("/products/{id}/edit")
public String updateProduct(@PathVariable Long id, ...) {
return "redirect:/products/my";
}
// 삭제 처리
@PostMapping("/products/{id}/delete")
public String deleteProduct(@PathVariable Long id, ...) {
return "redirect:/products/my";
}
사용자 컨트롤러
// 사용자 목록
@GetMapping("/users")
public String userList(Model model) {
model.addAttribute("users", userService.findAllUsers());
return "users/userList";
}
// 등록 폼
@GetMapping("/users/new")
public String register(Model model) {
return "users/new";
}
// 등록 처리
@PostMapping("/users/new")
public String registerUser(@Valid @ModelAttribute("user") UserRegistrationRequest request, ...) {
userService.registerUser(request);
return "redirect:/userSuccess";
}
// 수정 폼
@GetMapping("/users/{id}/edit")
public String userUpdate(@PathVariable Long id, Model model) {
return "users/edit";
}
// 수정 처리
@PostMapping("/users/{id}/edit")
public String userUpdateProc(@PathVariable Long id, ...) {
userService.updateUser(id, request);
return "redirect:/users";
}
// 삭제 처리
@PostMapping("/users/{id}/delete")
public String userDelete(@PathVariable Long id, ...) {
userService.deleteUser(id);
return "redirect:/";
}
주요 개선 사항
1. URL 복수형 통일
/product/...→/products/...- RESTful 설계 원칙에 따라 리소스는 복수형으로 표현
2. HTML Form 제약 대응
- HTML Form은 GET/POST만 지원
/edit,/delete경로로 구분하여 POST 요청 처리
3. 뷰 경로 구조 정리
- URL과 템플릿 폴더 구조 일치
/products/new→templates/products/new.html
4. 의미 중심 URL 설계
/products- 전체 상품 목록/products/my- 내 상품 목록/products/new- 새 상품 등록/products/{id}/edit- 상품 수정
배운 점
- HTML Form 환경에서도 RESTful 설계 가능
- 리소스 중심 사고의 중요성: 기능이 아닌 리소스를 중심으로 URL 설계
- 일관성의 가치: 모두가 이해하기 쉬운 일관된 규칙 적용 (표준 자체가 존재하지 않아 정의 필요)
- GET -> 데이터를 조회할 때 사용하고 서버 상태를 변경하지 않는 요청
- POST -> 서버에 새로운 자원을 생성하거나 상태 변경이 필요한 경우 사용
- 리팩토링의 어려움: 처음부터 잘 설계하는 것보다 기존 코드를 수정하는 것이 더 어렵다는 것을 체감..
작은 개선이지만 프로젝트의 유지보수성과 가독성이 크게 향상되었습니다. 좋은 설계 원칙을 배우고 적용해보는 것의 중요성을 다시 한번 느꼈습니다.
참고
···