저장을 습관화
MySQL sequelize 연습 기록 3 본문
메모
작업 환경 Windows 10, VSC, sequelize, MySQL, AWS
계층형 아키텍처 패턴 적용하기
- 계층형 아키텍처 패턴
계층을 분리해서 관리하는 아키텍처 패턴이고,
현재 가장 흔하게 사용되고 있는 아키텍처 패턴 중 하나이다(사실상 표준)
관련 자료가 많이 나와있기 때문에
어떤 아키텍쳐 패턴을 도입해야할지 확인이 없을때 좋은 선택지가 될 수 있다
어떤 경우든 계층을 분리해서 유지하고,
각 계층이 자신의 바로 아래 계층에만 의존하게 만드는 것이 목표이다
- 컨트롤러
클라이언트의 요청을 처리한 후 서버에서 처리된 결과를 반환해주는 역할
1) 클라이언트의 요청(request)을 수신
2) 요청(request)에 들어온 데이터 및 내용을 검증
3) 서버에서 수행된 결과를 클라이언트에게 반환(response)
3계층 아키텍쳐 패턴에서 프레젠테이션 계층(Presentation Layer)이라고 표현되며,
사용자(클라이언트)가 서버에 요청(request)를 하게 되면 가장 먼저 만나게 되는 계층이다
컨트롤러에서는 비즈니스 로직을 수행하지 않고
, 클라이언트의 요청(request)을 바로 서비스 계층으로 전달하도록 구현한다
서비스 계층에서 어떠한 내부 구조를 통해 비즈니스 로직을 수행하는 것인지
상위 계층인 컨트롤러에게는 중요하지 않다
- 서비스
비즈니스 로직 계층(Business login layer)이라고도 불린다.
아키텍처의 가장 핵심적인 비즈니스 로직을 수행하고
실제 사용자(클라이언트)가 원하는 요구사항을 구현하는 계층이다.
프레젠테이션 계층(컨트롤러)와 데이터 엑세스 계층(저장소) 사이에서
중간 다리 역할을 하며 서로 다른 두 계층이 직접 통신하지 않게 만든다.
그 이유의 예로 클라이언트가 DB의 사용자 정보를 요청했을때, 서비스 계층의 가공이 없다면
사용자의 비밀번호와 같은 노출되어선 안되는 정보까지 제공하게 되어 보안성이 떨어지게 된다.
데이터가 필요할 때 저장소(Repository)에게 데이터를 요청한다.
어플리케이션의 규모가 커질수록 서비스의 역할 및 코드 또한 커지게 된다
- 저장소
데이터 엑세스 계층(Data Access Layer)이라고도 불린다.
대표적으로 DB와 관련된 작업을 수행하는 계층이다.
모든 데이터가 메모리 상에 존재하는 것처럼 가장해 데이터 접근과 관련된 세부 사항을 감춘다.
대표적인 저장소 계층의 메소드로 1. 새 원소를 저장소에 추가하는 .add() 와
2. 이전에 추가한 원소를 저장소에거 가져오는 .get() 이 있다.
저장소 계층을 구현했을 때 데이터를 저장하는 방법을 더 쉽게 변경할 수 있고,
테스트 코드 작성 시 가짜 저장소(Mock Repository)를 제공하기 더 쉬워진다
어플리케이션의 다른 계층에서는
저장소의 세부 사항이 어떤 방식으로 구현되어 있더라도 영향을 받지 않는다
데이터 저장소를 간단히 추상화한 것으로 이 패턴을 사용하면
모델 계층과 데이터 계층을 분리할 수 있다
0. 별도로 추가 설치해야할 패키지는 없으며
controllers, services, repositories 폴더를 생성한다.
각각 컨트롤러, 서비스, 저장소의 내용이 들어갈 폴더이며
API의 흐름도 라우터 -> 컨트롤러 -> 서비스 -> 저장소 순으로 진행된다.
※ 아래 예시에서 쓰인 프로젝트는 API 관련 로직은 전부 생략되었음
1. 파일 수정
- app.js
const express = require("express");
const app = express();
const port = 4000;
const router = require("./routes");
app.use(express.json());
app.use("/api", router);
app.listen(port, () => {
console.log(port, "포트로 서버가 열렸어요!");
});
- /routes/posts.routes.js
const express = require("express");
const router = express.Router();
// 게시글 관련 API를 모두 게시글 컨트롤러로 보낸다
const PostsController = require("../controllers/posts.controller");
const postsController = new PostsController();
// 게시글 조회 API
router.get("/", postsController.getPosts);
// 게시글 작성 API
router.post("/", postsController.createPost);
module.exports = router;
- /controllers/posts.controller.js
const PostService = require("../services/posts.service");
// Post의 컨트롤러(Controller)역할을 하는 클래스
// 컨트롤러에서는 비즈니스 로직을 수행하지 않고,
// 클라이언트의 요청(request)을 바로 서비스 게층으로 전달한다.
// 서비스 계층에서 어떤한 내구 구조를 통해 비즈니스 로직을 수행하는 것인지는
// 상위 계층인 컨트롤러에게는 중요하지 않다.
class PostsController {
postService = new PostService(); // Post 서비스를 클래스를 컨트롤러 클래스의 멤버 변수로 할당합니다.
getPosts = async (req, res, next) => {
// 서비스 계층에 구현된 findAllPost 로직을 실행합니다.
const posts = await this.postService.findAllPost();
res.status(200).json({ data: posts });
};
createPost = async (req, res, next) => {
const { nickname, password, title, content } = req.body;
// 서비스 계층에 구현된 createPost 로직을 실행합니다.
const createPostData = await this.postService.createPost(
nickname,
password,
title,
content
);
res.status(201).json({ data: createPostData });
};
}
module.exports = PostsController;
- services/posts.service.js
const PostRepository = require("../repositories/posts.repository");
// 서비스가 비즈니르 소맂ㄱ을 수행하기 위해 필요한 데이터를
// 저장소 계층(Repository Layer)에게 요청하여 가져온다.
class PostService {
postRepository = new PostRepository();
findAllPost = async () => {
// 저장소(Repository)에게 데이터를 요청합니다.
const allPost = await this.postRepository.findAllPost();
// 호출한 Post들을 가장 최신 게시글 부터 정렬합니다.
allPost.sort((a, b) => {
return b.createdAt - a.createdAt;
});
// 비즈니스 로직을 수행한 후 사용자에게 보여줄 데이터를 가공합니다.
// 이 과정 없이 데이터를 repository의 데이터를 그대로 클라이언트에게 전달할 경우
// 사용자의 Password와 같이 알아서는 안될 정보까지 전달하게 되어
// 보안성이 떨어지는 결과를 낳게 된다.
return allPost.map((post) => {
return {
postId: post.postId,
nickname: post.nickname,
title: post.title,
createdAt: post.createdAt,
updatedAt: post.updatedAt,
};
});
};
createPost = async (nickname, password, title, content) => {
// 저장소(Repository)에게 데이터를 요청합니다.
const createPostData = await this.postRepository.createPost(
nickname,
password,
title,
content
);
// 비즈니스 로직을 수행한 후 사용자에게 보여줄 데이터를 가공합니다.
return {
postId: createPostData.null,
nickname: createPostData.nickname,
title: createPostData.title,
content: createPostData.content,
createdAt: createPostData.createdAt,
updatedAt: createPostData.updatedAt,
};
};
}
module.exports = PostService;
- repositories/posts.repository.js
const { Posts } = require("../models");
// ORM sequelize의 메소드를 사용하여 데이터를 다루는 핵심 내용
// 지금은 예시이기에 구성이 보기에 간단하지만
// 어플리케이션의 규모가 커지거나 사용해야할 DB의 구성이 복잡해질 경우
// 이 곳 또한 복잡해질 것이다.
class PostRepository {
findAllPost = async () => {
// ORM인 Sequelize에서 Posts 모델의 findAll 메소드를 사용해 데이터를 요청합니다.
const posts = await Posts.findAll();
return posts;
};
createPost = async (nickname, password, title, content) => {
// ORM인 Sequelize에서 Posts 모델의 create 메소드를 사용해 데이터를 요청합니다.
const createPostData = await Posts.create({
nickname,
password,
title,
content,
});
return createPostData;
};
}
module.exports = PostRepository;
- 마무리
프로젝트에서 특정 아키텍처 패턴이 필요하다라고 했을 때
각 아키텍처 패턴이 어떤 장단점을 가지고 있고
만약 실제로 도입했을때 프로젝트에 어떤 이점이 있는지에 대해 명확하게 생각해야한다.
아키텍처 패턴은 무조건적으로 도입해야하는 것이 아니다.
하지만 이러한 패턴들이 있다라는 것을 인지하고 있어야 할 것이다.
'공부 > node.js' 카테고리의 다른 글
S3 버킷 사용하여 이미지 저장하기 + access key 받아오기(IAM) (0) | 2023.07.26 |
---|---|
에러 기록 - SequelizeForeignKeyConstraintError (0) | 2023.07.14 |
에러 기록 - TypeError: Router.use() requires a middleware function but got a Object (0) | 2023.07.09 |
MySQL sequelize 연습 기록 2 (0) | 2023.07.08 |
MySQL sequelize 연습 기록 (0) | 2023.07.07 |