API Architecture, Layered Pattern

Kyoungchan Cho·2022년 9월 5일
1
post-thumbnail

Intro

API Architecture

코드의 구조를 체계적으로 구조적으로 구현하는 것

  • 확장성 : 각 레이아거 서로 독립적이라 가능
  • 재사용성 : 프레임워크나 라이브러리 활용으로 중복된 코드를 줄이고 모듈로 재사용 가능
  • 유지보수 가능성 : 문제 발생 원인의 파악이 쉬워지고 독립성을 이용하여 유지보수가 쉬워짐
  • 가독성 : 레이어의 역할이 명확하고 코드가 구조화되기 때문에 가독성이 높아지고 구조를 파악하기 쉬움
  • 테스트 가능성 : 각 레이어들의 기능을 테스트하기 쉽고 에러 발생시 원인 파악 쉬움

관심사 분리 (Seperated Of Concern)

코드의 역할을 독립적으로 분리
단방향 의존성 (각 layer들을 require)

파일1 HTTP Request/Respone 처리

  • Request body에서 데이터를 꺼낸다. ( 요청받기. 요청key 확인)
  • JSON Response로 제품 정보를 전달한다.

파일2 Business Logic 처리

  • 비밀번호 길이 확인
  • 유저 존재 유무, 이메일 등 정보 중복 확인
  • 암호화

파일3 Database 통신 처리

  • 데이터 INSERT & SELECT

Layered pattern

Client

Presentation Layer

  • Controller
  • 클라이언트에 대한 data들의 입출력
  • api endpoint
  • http request 읽어들이는 로직

Business Layer

  • Service
  • request data들의 검토
  • 서비스 기획이 반영
  • 비즈니스 로직 처리

Persistence Layer

  • Model
  • 데이터베이스와 관련된 로직
  • Business Layer에서 넘어온 data들의 CRUD 구현

Database

레이어드 패턴 실제 적용

유저 회원 가입 및 로그인 부분

1.app.js

app.js:Express 서버 설치 및 미들웨어 세팅

require('dotenv').config();

const http = require("http");

const express = require("express");
const cors = require("cors");
const morgan = require("morgan");
const route = require("./routes");

app = express()
const server = http.createServer(app);
const PORT = process.env.PORT;

app.use(cors());
app.use(morgan('dev'));
app.use(express.json());
app.use(route);

const start = async () => {
    server.listen(PORT, () => console.log(`server is listening on ${PORT}`))
}

start()

2. routes 폴더

routes/index.js : express 불러오기, router 모듈 export, 각 route 대분기

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

const userRouter = require("./userRouter")
const postRouter = require("./postRouter")
const likeRouter = require("./likeRouter")

router.use('/users', userRouter);
router.use('/posts', postRouter);
router.use('/likes', likeRouter);

module.exports = router;

routes/userRouter.js : user uri 소분기, method 설정, 단방향 require 설정, router 모듈 export

const express = require('express');
const { userController } = require('../controllers');

const router = express.Router();

router.post('/signup', userController.signUp);
router.post('/signin', userController.signIn);

module.exports = router;

3. controllers 폴더

index.js: 기능별 contorller 분기

const userController = require('./userController');
const postController = require('./postController');
const likeController = require('./likeController');

module.exports = {
    userController,
    postController,
    likeController,
}

userController.js : Presentation layer, 기능별(회원가입, 로그인) 입력값 출력값 설정, controller 모듈 Export, userService Import

const { userService } = require('../services')

const signUp = async (req, res) => {
    const { name, email, profileImage, password } = req.body;
    const user = await userService.signUp(name, email, profileImage, password);

    return res.status(201).json({ result : user });
};

const signIn = async (req, res) => {
    const { email, password } = req.body;
    const accessToken = await userService.signIn(email, password)
    return res.status(200).json({ token : accessToken})
}

module.exports = {
    signUp,
    signIn,
}

4. services 폴더

index.js : 기능별 serivce 모듈 export

const userService = require('./userService');
const postService = require('./postService')
const likeService = require('./likeService')

module.exports = {
    userService,
    postService,
    likeService,
}

userService: Business Layer, 회원가입 및 로그인 bcyrpt 해시값 설정 및 jwt 발급 로직 추가

const { userDao } = require('../models/');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

const signUp = async (name, email, profileImage, password) => {
    const saltRounds = 10;
    const makeHash = async (password, saltRounds) => { 
        return await bcrypt.hash(password, saltRounds);
    };
    hashedPassword = await makeHash(password, saltRounds);
    return await userDao.postUser(name, email, profileImage, hashedPassword);
};

const signIn = async (email, password) => {
    const user= await userDao.verifyUser(email);

    const checkHash = await bcrypt.compare(password, user.password)
    if(!checkHash) {
        const err = new Error("invalid password");
        err.statusCode = 400;
        throw err;
    }
    return jwt.sign({ sub: user.id, email: user.email }, process.env.SECRETKEY);
};

module.exports = {
    signUp,
    signIn,
}

5 models 폴더

index.js: DAO 모듈 분기

const userDao = require('./userDao');
const postDao = require('./postDao')
const likeDao = require('./likeDao')

module.exports = {
    userDao,
    postDao,
    likeDao,
}

userDao.js: Persistence Layer, MySQL 쿼리문, DB 관리 Layer

const database = require('./DataSource')

//return id 필수
const postUser = async (name, email, profileImage, password) => {
    const user = await database.query(
        `INSERT INTO users(
            name,
            email,
            profile_image,
            password
         ) VALUES (?, ?, ?, ?);
        `,
        [name, email, profileImage, password]
    );
    return user;
};

const verifyUser = async (email, password) => {
    const [userEncryption] = await database.query(
        `SELECT
            id,
            email,
            password
        FROM users
        WHERE email = ?
        `,
        [email]
    );
    return userEncryption;
}

module.exports = {
    postUser,
    verifyUser,
}

DataSourc.js:typeORM 모듈 import 및 각 Dao Layer를 위한 typoORM 객체 export

const { DataSource } =require('typeorm');

const database = new DataSource({
    type: process.env.TYPEORM_CONNECTION,
    host: process.env.TYPEORM_HOST,
    port: process.env.TYPEORM_PORT,
    username: process.env.TYPEORM_USERNAME,
    password: process.env.TYPEORM_PASSWORD,
    database: process.env.TYPEORM_DATABASE
})

database.initialize()
    .then(() => {
        console.log("Data Source has been initialized!")
    })
    .catch((err) => {
        console.error("Error during Data Source initialization", err)
    })

module.exports = database;

정리

app.js 서버 세팅 -> routes 분기 -> controllers(Presentation Layer) -> services(Business Layer) -> models(Persistence Layer) -> Database 순으로 단방향성을 보여준다.

profile
https://lying-lettuce-69f.notion.site/KyoungchanCho-Blog-f9f150b9e3be4467a67cf2a21932650d (게시글 자동 비공개 현상으로 일단 노션으로 이동합니다. 소개에서 URL 링크 클릭으로 연결됩니다.)

0개의 댓글