티스토리 뷰

Vue.js 로 웹 개발을 시작한지 한달 남짓. 뷰스러운 것 보다는 오히려 html, css, javascript 가 발목을 잡는다. 이래서 어느 분야던지 기초가 중요하다고 하는거다.  애초에 웹 개발쪽은 쳐다본 적도 없으니 그럴 수 밖에 없지. 아무튼 고작 한달 했다고 쓸데없이 자신감만 생겨서는 우연히 알게된 좋은 플랫폼을 개인 프로젝트로 흉내내고 싶은 욕심이 생겼다. (사내 세미나를 통해 알게 됨)

플랫폼은 비교적 간단하다. 용도는 콘퍼런스장에서 Q&A 시간에 질문을 받고 Timeline 을 보여준다. 좋은 질문이라고 생각되면 누구나 해당 질문에 upvote 를 할 수 있다. 익명으로 질문을 포스팅 하거나 닉네임을 적을 수도 있다. 그리고 타임라인을 여러가지 방식으로 정렬할 수 있다. 예를들면 시간, upvote 등.

여기서 잠깐. 이미 있는걸 그냥 쓰지 않고 왜 만드는지 의아할 수 있겠지만 어떤 플랫폼이든 사용하다보면 아쉬운 점이 몇 가지 보이고 그걸 보완하고 싶어지는게 개발자의 덕목이 아니던가. 이게 오픈소스였다면 컨트리뷰트 했겠지만 아니니 그냥 만드는수밖에.

자, 기획부터 해보자.

필요한 기술 스택

.- Vue.js (웹 페이지 개발)
-. Node.js (Client 명령어 처리)
-. Redis (메시지 저장)
-. Frontend 가 9할 정도 차지한다.

기능 명세

# 웹페이지
-. 트랙별로 화면을 처리 (서로 다른 트랙의 질문이 섞이지 않게)
-. 누구나 질문을 등록할 수 있어야 한다.
-. 등록된 질문을 누구나 볼 수 있어야 한다.
-. 질문을 정렬할 수 있어야 한다. (시간순/upvote)

# RestAPI
-. 레디스에 저장된 사용자 질문 리스트 출력 API (타임라인 출력용)
-. 레디스에 사용자 질문 저장 API

# Redis
-. 데이터(질문) 적재 용도

이정도면 대충 뭘 하려는건지 보이나? 작은 익명의 SNS 라고 생각하면 된다. 누구나 익명의 글을 올릴 수 있고 타임라인을 보면서 좋아요를 누를 수 있지만 댓글 기능은 없다. 댓글의 역할은 발표자가 응대하는 시스템이다.

이제 바로 개발에 착수하면 된다. 일단 최약점인 CSS 는 최대한 배제하고 기능 우선으로 구현을 하면 된다. 웹 페이지는 Vue-cli 를 이용해서 기본 바탕을 생성해준다.

$ vue-cli init webpack livemsg

명령어를 실행하면 프로젝트 생성에 필요한 질문이 몇 개 나오는데 적절하게 선택하면 된다. (Vue-router 는 선택해주는게 좋다) 잘 모르겠으면 그냥 엔터를 입력하고 넘기도록 하자. Default 로 선택된다. 설치가 끝나면 livemsg 디렉터리로 이동해서 뷰를 개발 모드로 실행시킨다.

$ npm run dev

이제 출력되는 메시지를 보고 웹페이지에 접속하면 되는데 기본포트가 사용중이거나 다른 변수(특별한 문제)가 없는한 localhost:8080 으로 접속하라는 메시지가 출력된다.

이제 질문 폼과 타임라인을 표현해줘야 하는데 최소한의 CSS 를 위해 bootstrap 을 이용하도록 한다. bootstrap 을 사용하려면 먼저 패키지를 install 하고 코드상에 사용을 명시해야 한다.

$ npm install bootstrap-vue

그리고 src/main.js 에 아래 내용을 추가해서 프로젝트 전반에서 bootstrap 을 사용할 수 있도록 한다.

import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue);

이제 질문 폼을 만들 차례다. 아래 페이지에서는 vuejs bootstrap 에서 사용할 수 있는 다양한 컴포넌트가 제공되니 필요한 컴포넌트가 있다면 적극적으로 활용하자.

https://bootstrap-vue.js.org/docs

질문은 textarea 로 처리하고 등록할 수 있는 button 을 생성해주면 된다. button 의 액션으로 Redis 에 질문 내용을 저장해야하는데 RestAPI 서버를 통해 저장할 수 있도록 하면 된다. 이때 노드와의 통신은 axios 를 사용한다. 이 내용을 기반으로 코드를 작성해보자.

아래는 질문을 입력 받을 수 있는 폼에 대한 코드이다.

<b-form-textarea id="textarea"
    v-model="postMsg"
    placeholder="Type your question"
    :rows="4"
    :max-rows="4">
</b-form-textarea>

그리고 아래와 같이 버튼을 등록해준다.

<b-button variant="success" @click="submit()">ASK</b-button>

submit 에 대한 동작은 아래와 같이 진행하면 된다.

submit()
{
    let timestamp = Date();
    axios.post('http://localhost:7005/writePost', {
        timestamp : timestamp,
        msg : this.postMsg
    })
    .then(response => {
        console.log(response);
        this.postMsg = '';
        this.getTimeline();
    })
    .catch(function (error) {
        console.log(error);
    });
}

이제 axios 통해 나간 메시지를 처리할 수 있도록 서버쪽을 작업할 차례다. 서두에 언급한 것 처럼 노드를 이용하도록 할텐데 express 를 사용해서 간단한 RestAPI 서버를 운영하도록 해보자. 아래는 노드의 전체 코드이다. (코드를 그대로 사용하고자 하는 경우에는 npm 으로 express 만 인스톨 해주면 된다)

const express = require('express');
const app = express();
const cors = require('cors');
const JSON = require('JSON');
const bodyParser = require('body-parser');
const redis = require("redis"),
        client = redis.createClient();
const PORT = "7005";
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json())

client.select(0);

client.on("error", function (err) {
    console.log("Error " + err);
});

// CORS 설정
app.use(cors());

app.get('/timeline', (req, res) => {
  //console.log('get request');
  client.zrevrange("section1", 0, -1, 'WITHSCORES', function (err, obj) {
      if (err) throw(err);
      res.send(obj);
  });
});

app.post('/upvote', (req, res) => {
  console.log('upvote request');
  console.log(req.body);
  client.zincrby("section1", 1, req.body.key, function (err, obj) {
      if (err) throw(err);
      res.send('succ');
  });
});

app.post('/writePost', (req, res) => {
  console.log('post request');
  console.log(req.body);
  client.zadd("section1", 0, JSON.stringify(req.body), function (err, obj) {
      if (err) throw(err);
      res.send('succ');
  });
});

app.listen(PORT, () => {
  console.log('server listening on port %s!', PORT);
});

코드 내용중 Redis 이 저장할 때 sorted sets 타입을 사용했다. 이는 후에 upvote 를 사용하기 위함으로 생각하면 된다. Redis 에서 사용하는 데이터 타입에 대해 더 궁금하면 아래 링트를 참고하도록 하자.

https://redis.io/topics/data-types

또한 여기서는 뷰와 노드, 레디스가 모두 한 서버에서 동작하고 있는 상황이기 때문에 서버 분리가 필요하면 코드상에 localhost 부분을 수정하면 되겠다. 이제 우리의 노드 서버를 시작할 시간이다.

$ node serv.js

와우, 이제 화면을 통해 질문한 내용이 레디스에 적재되는 부분까지 완성되었다. 여기까지 했으면 한숨 돌려도 되지만 다음 단계는 저장된 목록을 화면에 뿌리는 기능을 구현할 차례다. 사실상 이것까지 완성되면 최소한의 기능 요구는 다 충족 되는 듯. 빠르게 다음을 진행해보자.

노드 서버를 통해 데이터(질문 리스트)를 가져오는 것도 axios 를 사용하도록 한다. 코드는 아래와 같다.

getTimeline()
{
    axios.get('http://localhost:7005/timeline')
    .then(response => {
        this.history = [];
        for (var i = 0, j = 0; i < response.data.length; i+=2, j++)
        {
            this.history[j] = [];
            this.history[j]['timestamp'] = JSON.parse(response.data[i]).timestamp;
            this.history[j]['postMsg'] = JSON.parse(response.data[i]).msg;
            this.history[j]['score'] = response.data[i+1];
        }
        this.sortFunc();
    })
    .catch(function (error) {
        console.log(error);
    });
}

이제 페이지에 접속하면 노드 서버로부터 질문 리스트를 가져오고 화면에 출력되게 된다. 여기에 실제 사용자 경험을 충족시키기 위해 살을 붙여야한다. 우리는 새로운 질문을 등록하면 타임라인이 갱신되면서 새로운 질문이 노출되길 기대할 것이다. 이건 간단하다. 질문 등록이 성공하면 리스트를 가져오는 함수를 호출해주면 된다. (눈치챘겠지만 위에 submit 함수에 이미 구현되어 있다)

잘 동작하는 것을 확인하고보니.. 우리 프로그램은 혼자 사용하는게 아니지 않던가? A 가 질문을 등록하는 순간 B 의 화면도 갱신되어야 한다. 이를위해 무식하지만 제일 확실한 방법을 임시로 사용하도록 한다. 노드로부터 리스트 정보를 주기적으로 갱신하는 것이다. 여기서는 setinterval 을 사용하도록 한다. (가장 좋은 방법은 뷰에 computed 를 사용하는 것 같은데 아직 어떻게 다뤄야 하는지 잘 모르겠습니다)

setInterval(() => {
    this.getTimeline();
}, 3000);

다음으로는 upvote 기능을 붙일 차례다. 이건 의외로 간단하다. upvote 가 눌리는 질문의 내용을 그대로 서버에 보내고 서버에서는 scoreincrese 하기만 하면 된다. 앞서 레디스 데이터 타입을 sorted sets 으로 한 이유가 이것 때문이 아니던가.

클라이언트에서는 다음과 같이 코드를 작성한다.

upvote(post)
{
    var msg = JSON.stringify({'timestamp': post.timestamp, 'msg': post.postMsg});
    axios.post('http://localhost:7005/upvote', {
        key : msg
    })
    .then(response => {
        this.getTimeline();
    })
    .catch(function (error) {
        console.log(error);
    });
}

서버에서 받아서 처리하는 부분은 이미 위에 전체 코드를 공개했기 때문에 어렵지 않게 확인할 수 있다.

이제 세 가지를 더 구현하면 이 프로젝트는 그럴싸해진다. 우선 트랙별로 타임라인을 제공해야하고, 타임라인의 정렬이 필요하다. 그리고 끝으로는 예쁜 UI. 이게 사실 제일 중요하다. :-)

여기까지 간단한 프로젝트를 개발하면서 시간을 보냈는데 여러 개발스택을 들쑤셔서 간만에 재밌게 개인 개발 시간을 보낸 것 같다. 다음 기능은 언제쯤 붙이게 될까.

전체 코드와 내용은 아래 GitHub 을 통해 확인하면 되겠다.
https://github.com/jybaek/livemsg


댓글
최근에 올라온 글
최근에 달린 댓글
글 보관함
Total
Today
Yesterday