레이어 | 웹 사이트 (Thymeleaf + Spring MVC) | 모바일/SPA 백엔드 (React Native, Flutter 등 클라이언트를 위한 REST) |
---|---|---|
controller | @Controller + Model → return "view.html" 뷰 이름을 리턴해서 HTML 렌더링 |
@RestController → return ResponseEntity<DTO> JSON / XML 반환 (뷰 X) |
dto | 화면에 넣을 값만 담은 ViewDto (ex. 날짜 포맷 변환 완료) |
모바일/프론트가 필요로 하는 JSON 스키마용 DTO (ex. ✅·❌ 플래그, 페이징 커서) |
converter | 엔티티→ViewDto 변환 시 날짜→문자열 포맷 맞추기 등 | 엔티티→ApiDto 변환 + 링크, 상태코드 포함 (HATEOAS, 커스텀 에러) |
service / repository / domain | 같다! 비즈니스 로직·DB 계층은 재사용 ↔ 다만 서비스단에서 트랜잭션 범위나 캐싱 전략을 API 성격에 맞춰 미세 조정할 수 있음 |
Controller‑DTO‑Converter 부분만 “응답 형태”에 맞춰 바뀌고, Service‑Repository‑Domain은 그대로 재사용한다.
계층 | 🌐 웹 (Server‑Side Rendering) | 📱 앱/SPA (REST API) |
---|---|---|
Controller | java @Controller @RequestMapping("/boards") public class BoardWebController { @GetMapping("/{id}") public String detail(@PathVariable Long id, Model model) { BoardViewDto dto = boardSvc.getView(id); model.addAttribute("board", dto); return "board/detail"; // templates/board/detail.html } } |
java @RestController @RequestMapping("/api/boards") public class BoardApiController { @GetMapping("/{id}") public ResponseEntity<BoardApiDto> detail(@PathVariable Long id){ BoardApiDto dto = boardSvc.getApi(id); return ResponseEntity.ok(dto); } } |
DTO | java // 화면 맞춤 DTO – 문자열 포맷, 댓글 수 추가 public record BoardViewDto( Long id, String title, String writer, String createdDate, int replyCount ){ } |
java // API DTO – ISO 날짜, 하이퍼링크 포함 public record BoardApiDto( Long id, String title, String writer, LocalDateTime createdAt, URI self ){ } |
Validation | java @PostMapping("/write") public String write(@Valid @ModelAttribute BoardForm form, BindingResult errors){ if(errors.hasErrors()) return "board/write"; boardSvc.save(form); return "redirect:/boards"; } |
java @PostMapping public ResponseEntity<?> write(@Valid @RequestBody BoardWriteReq req){ boardSvc.save(req); return ResponseEntity.status(201).build(); } |
예외 처리 | java @ControllerAdvice public class WebErrorAdvice { @ExceptionHandler(NotFoundEx.class) public String notFound(){ return "error/404"; } } |
java @RestControllerAdvice public class ApiErrorAdvice { @ExceptionHandler(NotFoundEx.class) public ResponseEntity<ApiError> notFound(NotFoundEx e){ return ResponseEntity.status(404).body(new ApiError(e)); } } |
Security | 세션/쿠키 기반 로그인 → formLogin() |
토큰(JWT) → http.addFilter(new JwtAuthFilter(...)) |
템플릿/응답 | Thymeleaf, <div th:text="${board.title}"> |
JSON: { "id":1,"title":"..." } |
페이지네이션 | Page<Board> → Model에 넣고 Thymeleaf로 <a href="?page=${p}"> |
GET /api/boards?page=3 → { "content":[...], "page":3, "total":10 } |
파일 업로드 | MultipartFile 받아서 ⇒ redirect |
모바일은 S3 Presigned URL 받아 직접 업로드 → URL만 서버에 저장 |
@Controller
+ Model
→ view 이름 반환@RestController
→ DTO/컬렉션 반환즉, Service/Repository/Entity는 그대로 재사용하고,
“외부에 노출되는 계층” (Controller–DTO–Error Handling–Security Filter)만
응답 포맷에 맞춰 갈라진다—이게 두 구현의 실질적인 코드 차이입니다.
일단 REST API를 사용해 만들어 보았다
mysql설치 후 연동까지 해보았다
깃에 모든 코드는 올려놓았다