sungyup's.

PostgreSQL / Managing Database Design with Schema Migrations / 6.4 Data Access Pattern-Repositories

6.4Data Access Pattern-Repositories

Repository 패턴으로 데이터에 접근하기

저번 포스팅에서 API를 만들어봤으니, Repository 패턴을 통해 데이터에 접근해보자.

Repository 패턴이란, 데이터 접근 로직을 캡슐화하고, 애플리케이션의 다른 부분(예: 라우터, 서비스 등)이 데이터베이스의 구체적인 동작 방식을 알 필요 없이 데이터에 접근할 수 있도록 해주는 디자인 패턴이다.

즉, 데이터베이스에 직접 쿼리를 쓰는게 아니라, UserRepo.find()처럼 추상화된 메소드를 호출하는 디자인 패턴이다. 이렇게 접근하면 복잡한 쿼리 로직을 클래스에서 편하게 관리가 가능하고, 코드 일관성과 재사용성이 증가하는 이점이 있다.

우리의 Repository는 users 테이블에 접근해 아래와 같은 작업을 수행하는 함수들을 포함한다.

Function설명
find유저들의 정보를 가지고 있는 객체들의 배열을 반환
findByIdid를 통해 유저 찾기
insert유저 정보 추가
updateid를 통해 유저 찾고 업데이트하기
deleteid를 통해 유저 정보 지우기

Repository 만들기

src/repos/user-repo.js 파일을 작성한다. 아래는 우선 find 메소드만 구현한 코드다:

javascript
const pool = require("../pool"); class UserRepo { static async find() { const { rows } = await pool.query("SELECT * FROM users"); return rows; } static async findById() {} static async insert() {} static async update() {} static async delete() {} } module.exports = UserRepo;

우선 find만 테스트를 해보자.

users 라우터에 연결

아래는 기존에 작성한 users.js로, UserRepo.find()GET / users 라우터에 연결한다.

javascript
const express = require("express"); const UserRepo = require("../repos/user-repo"); const router = express.Router(); router.get("/users", async (req, res) => { const users = await UserRepo.find(); res.send(users); }); router.get("/users/:id", async (req, res) => { }); router.post("/users", async (req, res) => {}); router.put("/users/:id", async (req, res) => {}); router.delete("/users/:id", async (req, res) => {}); module.exports = router;

API 테스트해보기: REST client

npm run start로 로컬 서버를 시작한다. 이후, 요청을 보내기 위해 postman 또는 VSCode의 REST client 익스텐션을 이용한다. 이번 포스팅에선 후자를 사용한다.

  1. 우선 익스텐션을 설치하고 프로젝트 루트 폴더에 request.http라는 파일을 작성한다.
  2. 여기에 요청을 작성한다:
text
http://localhost:3005/users
  1. 해당 줄을 우클릭해 send request를 선택하면 요청이 보내진다.

추가적으로 요청을 보내려면, ###을 입력하고 아래에 또 다른 경로를 적는다. 우선 이렇게 요청을 보내보면, 현재는 데이터를 넣은게 없으므로 http 200 응답을 받지만 빈 배열([])을 반환한다.

PGAdmin4에서 간단한 데이터를 users에 넣자.

sql
INSERT INTO users (bio, username) VALUES ('This is my bio', 'Alyson14'), ('This is me', 'Gia67')

이후 요청을 보내보면 아래의 이미지와 같이 우리가 추가한 결과를 반환한다.

api access with http request

여기서, 자바스크립트 코드와 postgresql 간 Casing 전통에 차이가 있을 수 있다. 자바스크립트는 camelCase를 쓰는 반면, postgres에서는 snake_case를 쓰기 때문에 자바스크립트에선 users[0].createdAt처럼 접근하고 싶겠지만 실제 데이터는 users[0].created_at으로 접근해야 한다.

데이터베이스에서 데이터를 받아서 자바스크립트 코드로 변환을 하는 입장에서 이 문제는 고치고 가야 이후에 있을 큰 혼란을 방지할 수 있다.

Casing 고치기

쿼리로 데이터를 받고 나서 snake_case를 camelCase로 파싱하는 코드를 작성하면 된다.

javascript
static async find() { const { rows } = await pool.query("SELECT * FROM users"); const parsedRows = rows.map((row) => { const replaced = {}; for (const key in row) { const camelCase = key.replace(/([-_][a-z])/gi, ($1) => $1.toUpperCase().replace("_", "") ); replaced[camelCase] = row[key]; } return replaced; }); return parsedRows; }

물론, 이 파서는 find 메소드에서만 쓸 것이 아니므로 utils 같은 폴더에 따로 이 어댑터를 함수로 저장해서 재사용하는 것이 좋다.

  • to-camel-case.js
javascript
module.exports = (rows) => { return rows.map((row) => { const replaced = {}; for (let key in row) { const camelCase = key.replace(/([-_][a-z])/gi, ($1) => $1.toUpperCase().replace("_", "") ); replaced[camelCase] = row[key]; } return replaced; }); };
  • user-repo.js
javascript
const pool = require("../pool"); const toCamelCase = require("./utils/to-camel-case"); class UserRepo { static async find() { const { rows } = await pool.query("SELECT * FROM users"); return toCamelCase(rows); } // 다른 메소드... } module.exports = UserRepo;

이후 요청을 보내면 createdAt, updatedAt 식으로 데이터가 들어온다.

id로 유저 찾기

우선 users.js를 수정한다.

javascript
router.get("/users/:id", async (req, res) => { const { id } = req.params; const user = await UserRepo.findById(id); if (user) { res.send(user); } else { res.sendStatus(404); } });

다음으로, user-repo.js도 수정한다.

javascript
static async findById(id) { // WARNING: 큰 보안 문제 있음!! 다음 포스팅에서 수정 예정 const { rows } = await pool.query(` SELECT * FROM users WHERE id = ${id}; `); return toCamelCase(rows)[0]; }

다만, 코드에도 적혀있지만 이 방식은 안전하지 않은 방식이므로 수정이 필요하다.

PostgreSQL의 보안 관련해서는 다음 포스팅에서 보다 자세히 알아보자.