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;
    }
}