노드 심화_6

·2022년 12월 28일
0

노드 심화

목록 보기
6/9

컨트롤러 (Controller)

클라이언트의 요청을 처리 한 후 서버에서 처리된 결과를 반환해주는 역할

  • 역할
    • 클라이언트의 요청(Request)을 수신
    • 요청(Request)에 들어온 데이터 및 내용을 검증
    • 서버에서 수행된 결과를 클라이언트에게 반환(Response)

프레젠테이션 계층(Presentation Layer)

3계층 아키텍처 패턴에서는 프레젠테이션 계층(Presentation Layer)이라고 표현되며, 대표적으로는 컨트롤러(Controller)로 사용
사용자(클라이언트)가 서버요청(Request)를 하게되면 가장 먼저 만나게 되는 계층

  • 하위 계층(서비스 계층, 저장소 계층)에서 발생하는 예외(Exception)를 처리
  • 클라이언트가 전달한 데이터에 대해 유효성을 검증하는 기능을 수행
  • 클라이언트의 요청을 처리한 후 서버에서 처리된 결과를 반환

실습

  • Express에서는 컨트롤러라우터를 연결하기 위해서 express.Router에서 특정 URIHTTP Method를 요청받았을 때 컨트롤러로 요청된 내용을 전달 해줘야한다.

routes 폴더에서 posts.routes.js 라는 파일을 만들어 PostsConrtoller와 연결하도록 구성해보도록 하겠습니다.

// routes/posts.routes.js

const express = require('express');
const router = express.Router();

const PostsController = require('../controllers/posts.controller'); // 모듈을 가져옴
const postsController = new PostsController(); // 모듈의 클래스를 변수에 담아 선언

// express router와 controller의 메서드를 연결
router.get('/', postsController.getPosts);
router.post('/', postsController.createPost);

module.exports = router;

routes/posts.routes.js 파일로 컨트롤러라우터를 연결하도록 구현하였고, controllers/posts.controller.js 파일을 작성하여 Post컨트롤러를 구현해보도록 하겠습니다.

// controllers/posts.controller.js

const PostService = require('../services/posts.service'); // 서비스 모듈을 변수에 할당, 포스트 서비스를 정의할 때 사용

// Post의 컨트롤러(Controller)역할을 하는 클래스
class PostsController {
  postService = new PostService(); // Post 서비스를 클래스를 컨트롤러 클래스의 멤버 변수로 할당

  getPosts = async (req, res, next) => {
    // 서비스 계층에 구현된 findAllPost 로직을 실행
    // PostsController 클래스에서 멤버 변수로 정의한 postService에서 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;
  • 컨트롤러에서는 하위 계층이 어떠한 내부 구조를 가지고 있는지 신경쓰지 않고, 외부에 드러나 있는 메소드호출
    => 추상화(Absctraction)의 특성

  • PostController에서는 클라이언트의 요청(Request)을 서비스 계층으로 데이터를 전달

서비스 (Service)

비즈니스 로직 계층(Business logic layer)이라고도 불림
아키텍처의 가장 핵심적인 비즈니스 로직을 수행하고 실제 사용자(클라이언트)가 원하는 요구사항을 구현하는 계층

  • 프레젠테이션 계층(Presentation Layer)데이터 엑세스 계층(Data Access Layer) 사이에서 중간 다리 역할을 하며 서로 다른 두 계층이 직접 통신하지 않게 만들어 줌

  • 서비스(Service)데이터가 필요할 때 저장소(Repository)에게 데이터를 요청

  • 어플리케이션의 규모가 커지면 커질수록 서비스의 역할코드또한 점점 더 커지게 됨

  • 어플리케이션의 핵심적인 비즈니스 로직을 수행하여 클라이언트들의 요구사항을 반영하여 원하는 결과를 컨트롤러에게 반환해주는 계층

서비스 계층의 장단점

장점

  • 각각의 유스 케이스(Use Case)워크플로우(Workflow)를 명확히 정의할 때 도움이 된다.
    - 저장소(Repository)에게 얻을 필요가 있는 데이터가 무엇인지 이해할 수 있다.
    - 어떤 사전 검사현재 상태 검증을 필수적으로 해야하는 것인지 이해할 수 있다.
    - 어떤 내용을 저장해야 하는지 이해할 수 있다.
    유스 케이스(Use Case)에 대해 자세히 알고 싶다면 여기를 클릭하세요!

  • 비즈니스 로직을 API 뒤에 감췄기 때문에 서비스 계층의 코드를 자유롭게 리팩터링할 수 있다.

  • 저장소 패턴(Repository Pattern)가짜 저장소(Fake Repository)와 조합하면 높은 수준테스트작성할 수 있다.

단점

  • 서비스 계층에 너무 많은 기능을 넣으면 빈약한 도메인 모델(Anemic Domain Model)과 같은 안티 패턴이 생길 수 있다.

실습

// services/posts.service.js

// 하위 계층의 모듈을 호출
const PostRepository = require('../repositories/posts.repository');

class PostService {
  postRepository = new PostRepository();

  findAllPost = async () => {
    // 저장소(Repository)에게 데이터를 요청해서 allPost라는 변수에 할당
    const allPost = await this.postRepository.findAllPost();

    // 호출한 Post들을 가장 최신 게시글 부터 정렬
    allPost.sort((a, b) => {
      return b.createdAt - a.createdAt;
    })

    // 정렬된 데이터를 map을 통해 원하는 형식(객체 타입)으로 가공하여 컨트롤러에게 반환
    // map을 통해 실행했을 경우, 결과값이 배열 형태로 출력
    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;
  • PostServicePostRepositoryfindAllPost, createPost 메소드를 호출한 것을 확인
  • 서비스비즈니스 로직을 수행하기 위해 필요한 데이터저장소 계층(Repository Layer)에게 요청
  • return allPost.map(post => {});는 만약 Repository에서 데이터를 가져와 가공하지 않고 클라이언트에게 전달할 경우 사용자의 Password같이 알아서는 안될 정보까지 전달되게 되어 보안성이 떨어지는 결과를 낳게된다.

저장소 (Repository)

데이터 엑세스 계층(Data Access Layer)이라고도 불리는데요 대표적으로 Database와 관련된 작업을 수행하는 계층

  • 모든 데이터Memory상에 존재하는 것처럼 가정해 데이터 접근과 관련된 세부 사항감춥니다

  • 대표적인 저장소 계층의 메소드

    • add() : 새 원소를 저장소에 추가
    • get() : 이전에 추가한 원소를 저장소에서 가져옴
  • 저장소 계층을 구현했을 때 데이터를 저장하는 방법을 더 쉽게 변경할 수 있고, 테스트 코드 작성시 가짜 저장소(Mock Repository)를 제공하기가 더 쉬워집니다.

  • 어플리케이션의 다른 계층에서는 저장소의 세부 사항이 어떤 방식으로 구현되어 있더라도 영향을 받지 않는다.
    객체 지향의 개념 중에서 추상화(Abstraction)와 관계가 있습니다.

  • 저장소 계층은 데이터 저장소를 간단히 추상화한 것으로 이 패턴을 사용하면 모델 계층데이터 계층분리할 수 있습니다.

저장소 계층의 장단점

장점

  • 모델인프라에 대한 사항을 완전히 분리했기 때문에 단위 테스트(Unit test)를 위한 가짜 저장소(Fake Repository)를 쉽게 만들 수 있다.

  • 도메인 모델을 미리 작성하면 처리해야 할 비즈니스 문제에 더 잘 집중할 수 있다.

  • 접근 방식을 바꾸고 싶을 때 외래키마이그레이션 등을 염려하지 않고 모델에 반영할 수 있다.

  • 객체테이블매핑하는 과정을 원하는 대로 제어할 수 있어서 DB 스키마단순화할 수 있다.

  • 저장소 계층에 ORM을 사용하면 필요할 때 단순히 설정값만 바꾸더라도 MySQLPostgres와 같이 DB를 서로 바꾸기 쉬워진다.

단점

  • 저장소 계층이 없더라도 ORM이 어느 정도 (모델저장소의) 결합을 완화시켜주기 때문에 무조건적으로 저장소 계층을 쓸 필요가 없다.

  • ORM 매핑수동으로 하려면 개발 코스트가 더욱 소모된다.
    → 여기서 설명하는 ORM은 이전에 사용한 Sequelize와 같은 라이브러리를 뜻함

실습

PostServices에서 PostRepository를 호출하여 데이터를 요청하는 것을 확인 할 수 있었는데,
저장소 계층(Repository Layer)에서는 데이터베이스의 데이터를 어떠한 방식으로 가져와 상위 계층에게 반환하는지 확인

// repositories/posts.repository.js

const { Posts } = require('../models'); // Posts테이블에 접근하기 위해 모듈을 가져옴

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;

PostRepository 클래스에서 Sequelize의 메소드를 사용해 데이터조회하거나 생성하는 것이 가장 핵심적인 내용

  • 이번 예제는 Sequelize에서 1개의 테이블을 사용하여 구현하였기 때문에 특별하게 코드가 복잡하진 않지만,
    이후에 어플리케이션규모가 커지거나 사용해야 할 데이터베이스구성복잡해지게 될 경우 저장소또한 복잡하게 구현될 것
profile
개발자가 되는 과정

0개의 댓글