본문 바로가기
임시저장

(공부하기)데이터 영구 저장 - 파일을 수정해서 로컬 저장소에 저장하기

by GREEN나무 2024. 12. 10.
728x90

(공부하기)데이터 영구 저장 - 파일을 수정해서 로컬 저장소에 저장하기

더보기

서버를 껐다 켜도 데이터를 유지하려면 파일 또는 데이터베이스를 활용하여 데이터를 영구적으로 저장해야 합니다. 주어진 요구에 따라 ../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. 테스트

  1. 서버를 실행합니다.
  2. POST, GET, PUT, DELETE 요청으로 데이터가 정상적으로 추가, 수정, 삭제되는지 확인합니다.
  3. 서버를 껐다가 다시 켜도 데이터가 유지되는지 확인합니다.

장점 및 추가 개선

  • 데이터 영구 저장: 서버를 재시작해도 JSON 파일에 저장된 데이터를 그대로 사용할 수 있습니다.
  • 성능 향상: 대규모 프로젝트에서는 파일 대신 데이터베이스(MongoDB, MySQL 등)를 사용하는 것이 좋습니다.

Q1: 이 방식을 개선하여 데이터를 파일 시스템 외에 데이터베이스로 옮기려면 어떻게 해야 할까?

Q2: JSON 파일을 사용할 때 동시 쓰기 문제가 발생하면 어떻게 해결할 수 있을까?

Q3: 데이터를 저장하기 전, 데이터의 유효성을 어떻게 검증할 수 있을까?

 

 

JSON 파일을 사용할 때 동시 쓰기 문제는 여러 프로세스나 요청이 동시에 파일에 접근하고 수정하려 할 때 발생할 수 있습니다. 이런 문제를 방지하기 위해 다음과 같은 해결 방안을 사용할 수 있습니다.


1. 파일 락(FS Locking) 활용

fs 모듈을 이용하여 파일을 잠그는 방식으로 동시 접근을 방지할 수 있습니다. 파일 쓰기 작업이 완료될 때까지 다른 프로세스가 파일에 접근하지 못하게 하는 방법입니다. 이를 위해 라이브러리를 사용할 수 있습니다.

예시: proper-lockfile 라이브러리 사용

  1. 라이브러리 설치:
  2. npm install proper-lockfile
  3. 코드 수정: ../models/players.js
  4. 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 라이브러리 사용

  1. 설치:
  2. npm install joi
  3. 구현:
  4. 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 };
  5. 사용: ../models/players.js
  6. 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 사용

  1. 설치:
  2. npm install --save-dev jest
  3. 테스트 작성: validate.test.js
  4. 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(); });
  5. 테스트 실행:
  6. 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