본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.
아키텍처 패턴은 소프트웨어의 구조를 구성하기 위한 가장 기본적인 토대를 제시함
각각의 시스템들과 그 역할이 정의되어어 있고, 여러 시스템 사이의 관계와 규칙 등이 포함되어 있음
복잡한 문제를 해결할 때, 아키텍처 패턴을 이용하면 모델이나 코드를 더 쉽게 변경할 수 있음
시스템을 여러 계층으로 분리하여 관리하는 아키텍처 패턴
각 계층이 자신의 바로 아래 계층에만 의존하게 만드는 것이 목표
상위 계층은 하위 계층을 사용할 수 있지만, 하위 계층은 자신의 상위 계층을 알지 못함
3계층 아키텍처의 계층
계층형 아키텍처 패턴의 장점
내 프로젝트 폴더 이름
├── package.json
├── prisma
│ └── schema.prisma
├── src
│ ├── app.js
│ ├── controllers
│ │ └── posts.controller.js
│ ├── middlewares
│ │ ├── error-handling.middleware.js
│ │ └── log.middleware.js
│ ├── repositories
│ │ └── posts.repository.js
│ ├── routes
│ │ ├── index.js
│ │ └── posts.router.js
│ ├── services
│ │ └── posts.service.js
│ └── utils
│ └── prisma
│ └── index.js
└── yarn.lock
프레젠테이션 계층(Presentation Layer)은 가장 먼저 클라이언트의 요청을 만나는 계층
컨트롤러는 클라이언트의 요청을 처리하고, 서버에서 처리된 결과를 반환함
1) 클라이언트의 요청(Request)을 수신
2) 요청(Request)에 들어온 데이터 및 내용을 검증 후 서비스로 전달
3) 서버에서 수행된 결과를 클라이언트에게 반환(Response)
// 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.get('/:postId', postsController.getPostDetail);
/** 게시글 작성 API **/
router.post('/', postsController.createPost);
/** 게시글 수정 API **/
router.patch('/:postId', postsController.updatePost);
/** 게시글 삭제 API **/
router.delete('/:postId', postsController.deletePost);
export default router;
// src/controllers/posts.controller.js
import { PostsService } from '../services/posts.service.js';
// 사용자의 입력을 받아서 Service로 넘기는 역할의 클래스
export class PostsController {
// Post 서비스 클래스를 컨트롤러 클래스의 멤버 변수로 할당
postsService = new PostsService();
// 게시물 목록 조회 메서드
getPosts = async (req, res, next) => {
try {
// 서비스 계층에 구현된 findAllPosts 로직을 실행합니다.
const posts = await this.postsService.findAllPosts();
return res.status(200).json({ data: posts });
} catch (err) {
next(err);
}
};
// 게시물 상세 조회 메서드
getPostDetail = async (req, res, next) => {
try {
const { postId } = req.params;
// 게시물 상세 조회
const post = await this.postsService.findOnePost(+postId);
return res.status(200).json({ data: post });
} 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);
}
};
// 게시물 수정 메서드
updatePost = async (req, res, next) => {
try {
const { postId } = req.params;
const { password, title, content } = req.body;
// 게시물 수정
const updatedPost = await this.postsService.updatePost(
+postId,
password,
title,
content
);
return res.status(201).json({ data: updatedPost });
} catch (err) {
next(err);
}
};
// 게시물 삭제 메서드
deletePost = async (req, res, next) => {
try {
const { postId } = req.params;
const { password } = req.body;
// 게시물 삭제
const deletedPost = await this.postsService.deletePost(+postId, password);
return res.status(201).json({ data: { deletedPost } });
} catch (err) {
next(err);
}
};
}
비즈니스 로직 계층(Business logic layer)은 아키텍처의 가장 핵심적인 비즈니스 로직을 수행하고 클라이언트가 원하는 요구사항을 구현하는 계층
즉, 사용자의 요구사항을 주로 처리하는 실세 중에 실세
프레젠테이션 계층(Presentation Layer)과 데이터 엑세스 계층(Data Access Layer) 사이에서 중간 다리 역할
컨트롤러가 데이터를 요구할 때 저장소(Repository)에게 데이터를 요청함
어플리케이션의 규모가 커질수록, 서비스 계층의 역할과 코드의 복잡성도 점점 커짐
// src/services/posts.service.js
import { PostsRepository } from '../repositories/posts.repository.js';
// 핵심적인 비즈니스 로직을 수행하는 클래스
export class PostsService {
postsRepository = new PostsRepository();
// 모든 게시물 조회 메서드
findAllPosts = async () => {
const posts = await this.postsRepository.findAllPosts();
// 게시글을 생성 날짜로 부터 내림차순 정렬
posts.sort((a, b) => {
return b.createdAt - a.createdAt;
});
// password, content를 뺀 상태로, Controller에게 Response를 전달
return posts.map((post) => {
return {
postId: post.postId,
nickname: post.nickname,
title: post.title,
createdAt: post.createdAt,
updatedAt: post.updatedAt,
};
});
};
// 특정 게시물 조회 메서드
findOnePost = async (postId) => {
const post = await this.postsRepository.findOnePost(postId);
return {
postId,
nickname: post.nickname,
title: post.title,
content: post.content,
createdAt: post.createdAt,
updatedAt: post.updatedAt,
};
};
// 게시물 생성 메서드
createPost = async (nickname, password, title, content) => {
const createdPost = await this.postsRepository.createPost(
nickname,
password,
title,
content
);
return {
postsId: createdPost.postId,
nickname: createdPost.nickname,
title: createdPost.title,
content: createdPost.content,
createdAt: createdPost.createdAt,
updatedAt: createdPost.updatedAt,
};
};
// 게시물 수정 메서드
updatePost = async (postId, password, title, content) => {
// postId에 해당하는 게시물이 있는지 체크
const post = await this.postsRepository.findOnePost(postId);
if (!post) throw new Error('존재하지 않는 게시글입니다.');
await this.postsRepository.updatePost(postId, password, title, content);
const updatedPost = await this.postsRepository.findOnePost(postId);
return {
postId,
nickname: updatedPost.nickname,
title: updatedPost.title,
content: updatedPost.content,
createdAt: updatedPost.createdAt,
updatedAt: updatedPost.updatedAt,
};
};
// 게시물 삭제 메서드
deletePost = async (postId, password) => {
// postId에 해당하는 게시물이 있는지 체크
const post = await this.postsRepository.findOnePost(postId);
if (!post) throw new Error('존재하지 않는 게시글입니다.');
await this.postsRepository.deletePost(postId, password);
return {
postId,
nickname: post.nickname,
title: post.title,
content: post.content,
createdAt: post.createdAt,
updatedAt: post.updatedAt,
};
};
}
데이터 엑세스 계층(Data Access Layer)은 주로 데이터베이스와 관련된 작업을 처리하는 계층
실제로 데이터베이스에 접근해서 데이터를 CRUD하는 계층
컨트롤러와 서비스를 통해 전달 받은 데이터를 처리 후 반환함
// src/services/posts.service.js
import { prisma } from '../utils/prisma/index.js';
// 실제로 데이터베이스에 접근해서 데이터를 CRUD하는 클래스
export class PostsRepository {
// 모든 게시물 조회 메서드
findAllPosts = async () => {
const posts = await prisma.posts.findMany();
return posts;
};
// 특정 게시물 조회 메서드
findOnePost = async (postId) => {
const post = await prisma.posts.findFirst({
where: { postId },
});
return post;
};
// 게시물 생성 메서드
createPost = async (nickname, password, title, content) => {
const createdPost = await prisma.posts.create({
data: {
nickname,
password,
title,
content,
},
});
return createdPost;
};
// 게시물 수정 메서드
updatePost = async (postId, password, title, content) => {
const updatedPost = await prisma.posts.update({
where: { postId, password },
data: { title, content },
});
return updatedPost;
};
// 게시물 삭제 메서드
deletePost = async (postId, password) => {
const deletedPost = await prisma.posts.delete({
where: { postId, password },
});
return deletedPost;
};
}
오늘 강의를 다 끝냈으니 내일부터 본격적으로 개인과제를 할 예정
우선 기존의 코드 일부분을 리팩토링 할 예정
그 다음에 Layered Architecture Pattern를 적용해서 코드 모듈화 진행
오늘은 어제 다 듣지 못한 Layered Architecture Pattern에 관한 내용을 학습함
강의 중에 있는 퀴즈 문제를 풀 때 생각보다 구현이 헷갈렸음
원래 함께 구현하던 코드들을 나눠서 구현하니 아직은 복잡하다고 느껴짐
router -> controller -> service -> repository -> database
위 순서를 잘 기억해서 데이터의 CRUD를 구현해 보자!