(공부하기)데이터 영구 저장 - 파일을 수정해서 로컬 저장소에 저장하기
서버를 껐다 켜도 데이터를 유지하려면 파일 또는 데이터베이스를 활용하여 데이터를 영구적으로 저장해야 합니다. 주어진 요구에 따라 ../models/players.js 파일을 수정하여 데이터를 JSON 파일로 저장하고 읽어오는 방식을 구현할 수 있습니다. 아래는 단계별로 구현 방법을 설명합니다.
1. JSON 파일 생성 및 데이터 초기화
먼저 데이터를 저장할 JSON 파일(예: players.json)을 프로젝트 디렉토리에 생성합니다. 초기 상태로 빈 배열을 설정하세요.
players.json
[]
2. players.js 수정
fs 모듈을 사용하여 JSON 파일에서 데이터를 읽고, 수정 후 저장하는 방식을 구현합니다.
../models/players.js
const fs = require('fs');
const path = require('path');
// JSON 파일 경로
const filePath = path.join(__dirname, '../data/players.json');
// JSON 파일에서 데이터 읽기
const readData = () => {
if (!fs.existsSync(filePath)) {
// 파일이 없으면 빈 배열 초기화
fs.writeFileSync(filePath, JSON.stringify([]));
}
const rawData = fs.readFileSync(filePath, 'utf-8');
return JSON.parse(rawData);
};
// JSON 파일에 데이터 저장
const writeData = (data) => {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); // 들여쓰기로 가독성 유지
};
// 플레이어 데이터 조작 함수들
const getPlayers = () => {
return readData();
};
const addPlayer = (player) => {
const players = readData();
players.push(player);
writeData(players);
};
const updatePlayer = (id, updatedPlayer) => {
const players = readData();
const index = players.findIndex((p) => p.id === id);
if (index !== -1) {
players[index] = { ...players[index], ...updatedPlayer };
writeData(players);
}
};
const deletePlayer = (id) => {
const players = readData();
const updatedPlayers = players.filter((p) => p.id !== id);
writeData(updatedPlayers);
};
// 모듈로 내보내기
module.exports = {
getPlayers,
addPlayer,
updatePlayer,
deletePlayer,
};
3. 서버에서 사용하기
서버에서 데이터를 조작할 때 ../models/players.js의 함수를 호출하여 데이터를 관리합니다.
예제: Express 서버
const express = require('express');
const { getPlayers, addPlayer, updatePlayer, deletePlayer } = require('../models/players');
const app = express();
app.use(express.json());
// 플레이어 조회
app.get('/players', (req, res) => {
res.json(getPlayers());
});
// 플레이어 추가
app.post('/players', (req, res) => {
const newPlayer = req.body;
addPlayer(newPlayer);
res.status(201).send('Player added');
});
// 플레이어 수정
app.put('/players/:id', (req, res) => {
const playerId = req.params.id;
const updatedPlayer = req.body;
updatePlayer(playerId, updatedPlayer);
res.send('Player updated');
});
// 플레이어 삭제
app.delete('/players/:id', (req, res) => {
const playerId = req.params.id;
deletePlayer(playerId);
res.send('Player deleted');
});
// 서버 실행
app.listen(3000, () => console.log('Server running on port 3000'));
4. 테스트
- 서버를 실행합니다.
- POST, GET, PUT, DELETE 요청으로 데이터가 정상적으로 추가, 수정, 삭제되는지 확인합니다.
- 서버를 껐다가 다시 켜도 데이터가 유지되는지 확인합니다.
장점 및 추가 개선
- 데이터 영구 저장: 서버를 재시작해도 JSON 파일에 저장된 데이터를 그대로 사용할 수 있습니다.
- 성능 향상: 대규모 프로젝트에서는 파일 대신 데이터베이스(MongoDB, MySQL 등)를 사용하는 것이 좋습니다.
Q1: 이 방식을 개선하여 데이터를 파일 시스템 외에 데이터베이스로 옮기려면 어떻게 해야 할까?
Q2: JSON 파일을 사용할 때 동시 쓰기 문제가 발생하면 어떻게 해결할 수 있을까?
Q3: 데이터를 저장하기 전, 데이터의 유효성을 어떻게 검증할 수 있을까?
JSON 파일을 사용할 때 동시 쓰기 문제는 여러 프로세스나 요청이 동시에 파일에 접근하고 수정하려 할 때 발생할 수 있습니다. 이런 문제를 방지하기 위해 다음과 같은 해결 방안을 사용할 수 있습니다.
1. 파일 락(FS Locking) 활용
fs 모듈을 이용하여 파일을 잠그는 방식으로 동시 접근을 방지할 수 있습니다. 파일 쓰기 작업이 완료될 때까지 다른 프로세스가 파일에 접근하지 못하게 하는 방법입니다. 이를 위해 라이브러리를 사용할 수 있습니다.
예시: proper-lockfile 라이브러리 사용
- 라이브러리 설치:
- npm install proper-lockfile
- 코드 수정: ../models/players.js
- const fs = require('fs'); const path = require('path'); const lockfile = require('proper-lockfile'); const filePath = path.join(__dirname, '../data/players.json'); const readData = () => { if (!fs.existsSync(filePath)) { fs.writeFileSync(filePath, JSON.stringify([])); } return JSON.parse(fs.readFileSync(filePath, 'utf-8')); }; const writeData = (data) => { try { lockfile.lockSync(filePath); // 파일 잠금 fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); } finally { lockfile.unlockSync(filePath); // 파일 잠금 해제 } }; module.exports = { getPlayers: readData, addPlayer: (player) => { const players = readData(); players.push(player); writeData(players); }, updatePlayer: (id, updatedPlayer) => { const players = readData(); const index = players.findIndex((p) => p.id === id); if (index !== -1) { players[index] = { ...players[index], ...updatedPlayer }; writeData(players); } }, deletePlayer: (id) => { const players = readData(); const updatedPlayers = players.filter((p) => p.id !== id); writeData(updatedPlayers); }, };
2. 메모리 기반 쓰기 큐(Write Queue) 사용
파일에 바로 쓰지 않고, 변경 사항을 메모리에 저장해 두었다가, 특정 시간 간격으로 파일에 반영하는 방법입니다.
예시: 쓰기 큐 구현
../models/players.js
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, '../data/players.json');
// 메모리 데이터
let players = [];
let isWriting = false;
// 데이터 로드
const loadData = () => {
if (fs.existsSync(filePath)) {
players = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
} else {
players = [];
fs.writeFileSync(filePath, JSON.stringify(players));
}
};
// 데이터 저장
const saveData = () => {
if (isWriting) return; // 이미 쓰기 작업 중인 경우 무시
isWriting = true;
setTimeout(() => {
fs.writeFileSync(filePath, JSON.stringify(players, null, 2));
isWriting = false;
}, 1000); // 1초 후에 파일 저장
};
// 플레이어 데이터 조작 함수들
const getPlayers = () => players;
const addPlayer = (player) => {
players.push(player);
saveData();
};
const updatePlayer = (id, updatedPlayer) => {
const index = players.findIndex((p) => p.id === id);
if (index !== -1) {
players[index] = { ...players[index], ...updatedPlayer };
saveData();
}
};
const deletePlayer = (id) => {
players = players.filter((p) => p.id !== id);
saveData();
};
// 모듈로 내보내기
module.exports = {
getPlayers,
addPlayer,
updatePlayer,
deletePlayer,
};
// 서버 시작 시 데이터 로드
loadData();
이 방식은 데이터가 메모리에 유지되므로 서버가 종료되기 전까지는 빠른 데이터 접근이 가능합니다. 서버 종료 시나 정해진 시간마다 데이터를 파일에 쓰기 때문에 동시 쓰기 문제를 피할 수 있습니다.
3. 프로세스 간 메시지 전달
만약 여러 프로세스가 파일을 공유한다면, 중앙 관리 시스템(예: Redis, RabbitMQ)을 통해 파일 쓰기 작업을 직렬화하는 것도 방법입니다. 그러나 이는 비교적 복잡한 설정이므로 소규모 프로젝트에서는 위 두 가지 방법을 사용하는 것이 적합합니다.
Q1: 파일 락(lock)을 사용할 때 발생할 수 있는 문제는 무엇이며, 이를 어떻게 해결할 수 있을까?
Q2: 메모리 기반 쓰기 큐 방식에서 서버 충돌로 인해 데이터가 손실되지 않게 하려면 어떻게 개선할 수 있을까?
Q3: JSON 파일 대신 데이터베이스를 사용했을 때 어떤 성능 및 안정성 이점을 얻을 수 있을까?
데이터의 유효성을 검증하는 것은 데이터가 기대하는 형식과 조건에 부합하는지 확인하는 과정입니다. 이를 통해 시스템 안정성을 유지하고 예외 상황을 최소화할 수 있습니다. 아래는 데이터를 저장하기 전 유효성을 검증하는 다양한 방법과 그 구현 예시입니다.
1. 스키마(Schema) 기반 검증
스키마를 정의하고 데이터가 이를 따르는지 확인합니다.
예시: Joi 라이브러리 사용
- 설치:
- npm install joi
- 구현:
- const Joi = require('joi'); // 데이터 스키마 정의 const playerSchema = Joi.object({ id: Joi.string().required(), // 필수 문자열 name: Joi.string().min(3).max(50).required(), // 최소 3자, 최대 50자 score: Joi.number().integer().min(0).required(), // 0 이상의 정수 }); // 데이터 검증 함수 const validatePlayer = (player) => { const { error } = playerSchema.validate(player); if (error) { throw new Error(error.details[0].message); // 첫 번째 에러 메시지 반환 } }; module.exports = { validatePlayer };
- 사용: ../models/players.js
- const { validatePlayer } = require('./validate'); const addPlayer = (player) => { validatePlayer(player); // 데이터 검증 const players = readData(); players.push(player); writeData(players); };
2. 유효성 검증 로직 직접 작성
라이브러리를 사용하지 않고 데이터 형식과 조건을 코드로 명시적으로 작성합니다.
예시:
const validatePlayer = (player) => {
if (!player.id || typeof player.id !== 'string') {
throw new Error('Player ID is required and must be a string.');
}
if (!player.name || typeof player.name !== 'string' || player.name.length < 3 || player.name.length > 50) {
throw new Error('Player name must be a string between 3 and 50 characters.');
}
if (typeof player.score !== 'number' || !Number.isInteger(player.score) || player.score < 0) {
throw new Error('Player score must be a non-negative integer.');
}
};
이 방법은 간단한 검증에 적합하지만, 스키마를 관리하거나 복잡한 조건을 추가할 때 비효율적일 수 있습니다.
3. 미들웨어로 유효성 검증
Express 서버를 사용할 경우, 미들웨어를 활용해 유효성 검증을 중앙화할 수 있습니다.
예시:
const validateRequest = (schema) => {
return (req, res, next) => {
const { error } = schema.validate(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
next();
};
};
// 적용
const express = require('express');
const Joi = require('joi');
const app = express();
app.use(express.json());
const playerSchema = Joi.object({
id: Joi.string().required(),
name: Joi.string().min(3).max(50).required(),
score: Joi.number().integer().min(0).required(),
});
app.post('/players', validateRequest(playerSchema), (req, res) => {
addPlayer(req.body);
res.status(201).send('Player added');
});
4. 유효성 검증을 위한 테스트 추가
자동화된 테스트를 작성하여 데이터 검증 로직이 올바르게 작동하는지 확인합니다.
예시: Jest 사용
- 설치:
- npm install --save-dev jest
- 테스트 작성: validate.test.js
- const { validatePlayer } = require('./validate'); test('유효한 플레이어 데이터를 검증', () => { const validPlayer = { id: '123', name: 'Player1', score: 100 }; expect(() => validatePlayer(validPlayer)).not.toThrow(); }); test('유효하지 않은 플레이어 데이터를 검증', () => { const invalidPlayer = { id: '123', name: 'P', score: -10 }; expect(() => validatePlayer(invalidPlayer)).toThrow(); });
- 테스트 실행:
- npx jest
5. 유효성 검증의 추가 고려 사항
- 유니크 값 검증: ID나 이메일 등이 중복되지 않도록 확인 (예: 데이터베이스 쿼리 사용).
- 정규식 활용: 특정 형식(예: 이메일, 전화번호)을 검증할 때 사용.
- 에러 처리: 유효성 검증 실패 시 적절한 에러 메시지와 상태 코드를 반환.
Q1: 데이터 검증과 관련된 성능 문제를 최소화하려면 어떤 방법을 사용할 수 있을까?
Q2: 클라이언트와 서버 간에 유효성 검증 로직이 중복되지 않도록 어떻게 설계할 수 있을까?
Q3: 복잡한 데이터 구조(중첩된 객체 배열 등)를 검증하려면 어떤 추가 도구나 방법이 필요할까?
'임시저장' 카테고리의 다른 글
목차 만들기 (0) | 2024.12.10 |
---|---|
변수 일괄 수정 (0) | 2024.12.09 |
스텐다드 IOS 7계층 (0) | 2024.12.08 |
github 터미널 명령어 모음 (0) | 2024.12.06 |
아ㅏㅏ (0) | 2024.12.06 |