Layered Architecture Pattern - Controller
01. 컨트롤러 (Controller)
- 1) 프레젠테이션 계층(Presentation Layer) 이란?
💡 **프레젠테이션 계층(Presentation Layer)**은 **3계층 아키텍처 패턴**에서 **가장 먼저 클라이언트의 요청(Request)**을 만나게 되는 계층이며, 대표적으로 **컨트롤러(Controller)**가 이 역할을 담당합니다.
- **하위 계층(서비스 계층, 저장소 계층)**에서 발생하는 **예외(Exception)**를 처리 합니다.
- 클라이언트가 전달한 데이터에 대해 **유효성을 검증**하는 기능을 수행합니다.
→ 이전에 **Joi** 라이브러리를 이용한 유효성 검사가 기억나시죠? 😉
- 클라이언트의 **요청**을 처리한 후 서버에서 처리된 결과를 **반환(Response)**합니다.
- 2) 컨트롤러(Controller) 란?
💡 **컨트롤러(Controller)**란 클라이언트의 **요청(Request)**을 처리하고, 서버에서 처리된 **결과**를 **반환(Response)**하는 **역할**을 담당합니다.
**컨트롤러(Controller)**는 추가적으로 아래의 역할을 담당합니다.
- **클라이언트의 요청(Request)을 수신합니다.**
- **요청(Request)에 들어온 데이터 및 내용을 검증합니다.**
- **서버에서 수행된 결과를 클라이언트에게 반환(Response)합니다.**
- 3) Express로 구현하는 컨트롤러 !https://s3-us-west-2.amazonaws.com/secure.notion-static.com/9ab055dc-a05c-475b-9ca1-f7b986983024/Untitled.png
📌 저희는 이전 챕터에서 배운 내용을 바탕으로 **컨트롤러(Controller)**를 구현할 것입니다. 프로젝트를 시작하기에 앞서 **3계층 아키텍처** 프로젝트에서 어떤 **API**를 만들지 확인해보겠습니다.
- **📚 [3계층 아키텍처] 게시글 API 명세서**
[제목 없는 데이터베이스](https://www.notion.so/7c4fd196c43d4296aa17e44d7a4a0f93?pvs=21)
**Express**에서는 **컨트롤러**와 **라우터(Router)**를 연결하기 위해서는 `express.Router`를 사용하여, 라우터가 클라이언트의 **요청(Request)**에서 특정 **URI**와 **HTTP Method**를 전달받았을 때 **컨트롤러의 특정 메서드**로 요청된 내용을 전달하도록 구현해야합니다.
그렇기 때문에 `routes` 폴더에서 `posts.router.js` 라는 파일을 만들어 `PostsConrtoller`와 연결하도록 구성해보겠습니다! 🙂
- **[코드 스니펫] 3계층 아키텍처 - `routes/posts.router.js`**
```jsx
// src/routes/posts.router.js
import express from 'express';
import { PostsController } from '../controllers/posts.controller.js';
const router = express.Router();
// PostsController의 인스턴스를 생성합니다.
const postsController = new PostsController();
/** 게시글 조회 API **/
router.get('/', postsController.getPosts);
/** 게시글 작성 API **/
router.post('/', postsController.createPost);
export default router;
```
- **✅ `router.get('/', postsController.getPosts);` 코드 추가 설명**
✅ `router.get`은 저희가 **Express**를 이용하여 **API**를 만들때 매번 사용했던 코드에요!
이 메서드는 `/` URI에 대한 GET 메서드 요청이 들어올 경우, `postsController.getPosts`를 호출하라는 의미입니다.
즉, 클라이언트의 요청이 들어오면, `PostsController` 클래스에서 정의된 `getPosts` 메서드를 실행하도록 라우터를 설정한 것입니다.
**그 결과, 저희는 Post에 해당하는 컨트롤러와 라우터를 연결하게 되었습니다! 😆**
저희는 `posts.router.js` 파일을 통해 **컨트롤러**와 **라우터**를 **연결**하였으니, 다음 단계로 `posts.controller.js` 파일을 작성하여 **Post**의 **컨트롤러**를 구현해보도록 하겠습니다!
- **[코드 스니펫] 3계층 아키텍처 - `controllers/posts.controller.js`**
```jsx
// src/controllers/posts.controller.js
import { PostsService } from '../services/posts.service.js';
// Post의 컨트롤러(Controller)역할을 하는 클래스
export class PostsController {
postsService = new PostsService(); // Post 서비스를 클래스를 컨트롤러 클래스의 멤버 변수로 할당합니다.
getPosts = async (req, res, next) => {
try {
// 서비스 계층에 구현된 findAllPosts 로직을 실행합니다.
const posts = await this.postsService.findAllPosts();
return res.status(200).json({ data: posts });
} catch (err) {
next(err);
}
};
createPost = async (req, res, next) => {
try {
const { nickname, password, title, content } = req.body;
// 서비스 계층에 구현된 createPost 로직을 실행합니다.
const createdPost = await this.postsService.createPost(
nickname,
password,
title,
content,
);
return res.status(201).json({ data: createdPost });
} catch (err) {
next(err);
}
};
}
```
- **✅ `await this.postsService.findAllPosts();` 코드 추가 설명**
`await this.postsService.findAllPosts();` 는 `PostsController` 클래스의 `postService` 인스턴스에서 `findAllPost` 메서드를 호출합니다.
컨트롤러는 **하위 계층**의 **내부 구조**에 대해신경쓰지 않습니다. 대신, **외부**에 공개된 **메서드**를 **호출**하기만 합니다. 이것이 가능한 이유는 **추상화(Absctraction)**의 특성 덕분입니다.
`PostsController` 클래스는 전달된 **요청(Request)**을 처리하기 위해 `PostsService`를 호출하도록 구현하였습니다. 여기서 **컨트롤러**가 **비즈니스 로직**을 직접 수행하지 않고, **클라이언트**의 **요청**을 **서비스 계층**으로 바로 전달 하도록 구현한 것을 확인 할 수 있습니다. 😊
결국, `PostController` 클래스는 **클라이언트**의 **요청(Request)**을 **서비스 계층**으로 **전달**하는 역할을 수행하며, **서비스 계층**이 어떠한 내부 구조를 통해 **비즈니스 로직**을 수행하는지는 **상위 계층**인 **컨트롤러**에게는 중요하지 않습니다.
💪 이렇게 각 계층이 **자신의 역할에만 집중**할 수 있게 되며, **다른 계층의 세부사항**에 대해서는 알 필요가 없어지게 되는 것이죠.😉