TIL
TIL 240610 - 계층형 아키텍처 패턴
lemonpie611
2024. 6. 10. 20:49
강의를 여기까지 들으니까 개인과제에서 뭘 해야 하는지 대충 감이 잡힌다. 만들었던 코드 분리해라..고 하는거 같음
아직 객체가 쪼금 낯설기 때문에.. 코드 여러번 써보면서 천천히 친해져보도록 하겠음
1. 계층형 아키텍처 패턴
1) 계층형 아키텍처 패턴
- 단순하고 대중적이면서 비용도 적게 든다..!! 그러니 어떤 아키텍처를 쓸지 고민되면 이거 쓰자
- 각 계층을 명확하게 분리해서 유지하고, 각 계층이 자신의 바로 아래 계층에만 의존하게 만드는 것이 목표
- 각 계층이 높은 응집도를 가지면서 다른 계층과는 결합도를 최소화 함
- 하위 계층은 자신이 어떤 상위 계층에 속하는지 알 필요 없이 독립적으로 동작할 수 있어야 함
- 규모가 작은 어플리케이션은 3개의 계층, 크기가 크고 복잡하면 그 이상의 계층으로 구성됨
2) 계층형 아키텍처 패턴의 장점
- 관심사 분리 > 현재 구현할 코드 명확히 인지 가능
- 서로 독립적이고 의존성이 낮은 코드로, 모듈을 교체해도 코드 수정이 용이
- 계층별로 단위 테스트 작성 가능 > 테스트 코드를 조금 더 용이하게 구성 가능
3) 3계층 아키텍처
- 컨트롤러(Controller) : 어플리케이션의 가장 바깥 부분, 요청/응답 처리, Req 수신 후 Res 반환
- 서비스(Service) : 어플리케이션의 중간 부분, API의 핵심적인 동작이 많이 일어남, 비즈니스 로직 수행
- 저장소(Repository) : 어플리케이션의 가장 안쪽 부분, 데이터베이스와 통신
4) 로직 수행 플로우
- 클라이언트가 어플리케이션에 요청(Request)을 보냄
- 요청을 url에 알맞은 컨트롤러가 수신받음
- 컨트롤러는 요청을 처리하기 위해 서비스 호출
- 서비스는 필요한 데이터를 가져오기 위해 저장소(Repository)에게 데이터 요청
- 서비스는 저장소에서 가져온 데이터를 가공하여 컨트롤러로 전달
- 컨트롤러는 서비스의 결과물(Response)를 클라이언트에게 전달
2. 계층형 아키텍처 패턴 예시
// posts.router.js
import express from 'express';
import { PostsController } from '../controllers/posts.controller.js';
const router = express.Router();
const postsConstroller = new PostsController(); //PostsController를 인스턴스화 시킴
// 게시글 생성 API
router.post('/', postsConstroller.createPost);
// 게시글 조회 API
router.get('/', postsConstroller.getPosts);
export default router;
1) Controller
- 프레젠테이션 계층
- 3계층 아키텍처 패턴에서 가장 먼저 클라이언트 요청을 만나는 계층, 컨트롤러가 여기에 속함.
- 하위 계층에서 발생하는 예외를 처리, 클라이언트가 전달한 데이터의 유효성 검증 등의 역할
- 컨트롤러 : 클라이언트의 요청을 처리, 서버에서 처리된 결과를 반환
// posts.controller.js
import { PostsService } from "../services/posts.service.js";
export class PostsController {
postsService = new PostsService();
// 게시글 조회 API
getPosts = async(req, res, next) => {
try{
const posts = await this.postsService.findAllPosts();
return res.status(200).json({data: posts});
} catch(err) {
next(err)
}
}
// 게시글 작성 API
createPost = async(req, res, next) => {
try{
const { nickname, password, title, content } = req.body;
const createdPost = await this.postsService.createPost(
nickname, password, title, content
);
return res.status(201).json({data: createdPost});
} catch(err) {
next(err)
}
}
}
2) Service
- 비즈니스 로직을 수행, 클라이언트가 원하는 요구사항을 구현
- 프레젠테이션 계층과 데이터 엑세스 계층 사이의 중간 다리 > 두 계층이 직접 통신하지 않도록 함
- 데이터가 필요할 때 Repository에서 데이터 요청
// 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;
});
//pw, content 빼고 controller로 전달
return posts.map((post) => {
return {
postId: post.postId,
nickname: post.nickname,
title: post.title,
createdAt: post.createdAt,
updatedAt: post.updatedAt
}
});
}
// 게시글 작성
createPost = async (nickname, password, title, content) => {
const createdPost = await this.postsRepository.createPost(
nickname, password, title, content
);
return {
postId: createdPost.postId,
nickname: createdPost.nickname,
title: createdPost.title,
content: createdPost.content,
createdAt: createdPost.createdAt,
updatedAt: createdPost.updatedAt
}
}
}
3) Repository
- 데이터 엑세스 계층이라고도 불림. 데이터베이스와 관련된 작업 처리
- 데이터 접근과 관련된 세부사항은 숨김. 메모리상에 데이터가 존재하는 것처럼 가정하여 코드 구현
- 데이터 저장 방법을 쉽게 변경 가능, 테스트 코드 작성 시 Mock Repository 제공이 쉬움
- 저장소 계층의 변경 사항이 다른 계층에는 영향을 주지 않음 > 객체지향의 개념 중 추상화와 관련
//posts.repository.js
import { prisma } from '../utils/prisma/index.js';
export class PostsRepository {
findAllPosts = async () => {
const posts = await prisma.posts.findMany();
return posts;
}
createPost = async (nickname, password, title, content) => {
const createdPost = await prisma.posts.create({
data: {
nickname, password, title, content,
}
});
return createdPost;
}
}