프로그램은 쪼갤수록 단순해진다!
이 개념을 아시나요?
지난 시간에 다루었던 클래스도 예외가 아닌데요.
이번 시간에는 클래스를 모듈로 분리하는 방법과 쪽지 추가 기능에 대해
점진적인 개발 과정 설명을 드리도록 하겠습니다.
스크롤 압박이 좀 있을 수 있습니다 :)
소스와 결과물은 아래 링크를,
→ https://itadventure.tistory.com/443?category=715914
처음부터 정주행하시려면 아래 링크를 참조하세요.
→ https://itadventure.tistory.com/431
클래스를 분리하자!
일반적으로 하나의 클래스는 하나의 파일에 기능을 넣고
파일명도 동일한 이름을 짓는 것이 좋습니다.
그 후에 메인 기능에서 갖다 쓰는게 관리 측면에서 좋은데요.
철칙은 아니지만, 이런 규칙성같은게 없으면 공동으로 협업하여 개발하는 데서 민폐가 될 수 있습니다 :)
지난 시간, 쪽지 관리 클래스를 추가하는 부분에 대해 설명드렸지요.
이 클래스를 독자 파일로 분리하는 과정은 아래와 같습니다.
memoclass.js 라는 파일을 아래와 같이 작성합니다.
// 쪽지 클래스
class memoClass {
constructor(){
this.list=Array();
}
async loading(db){
try {
const data = await db.query(
'SELECT x, y, r, memo FROM memolist'
);
let result=data[0];
for(var i=0;i<result.length;++i)
this.list.push({
x: result[i].x,
y: result[i].y,
r: result[i].r,
memo: result[i].memo
});
console.log(this.list);
console.log(
"[ " + result.length
+ " ] 개의 쪽지가 로딩되었습니다."
);
} catch (err) {
console.log(err.message);
}
}
}
module.exports = memoClass;
바뀐 것은 단지 아래의 한줄이 추가된 것뿐인데요.
module.exports = memoClass;
이 명령은 선언한 memoClass 를 그대로 export(익스포트 : 출력)하겠다는 겁니다.
이런 걸 클래스 모듈(Class Module)이라고 부릅니다
그러면 다른 소스에서는 이 파일을 이렇게 갖다 쓸 수 있는데요.
const memoClass = require("./memoclass");
const memo = new memoClass();
위 코드가 실행되면 memoClass 는 클래스,
memo 는 클래스 오브젝트가 됩니다.
그래서 메인 소스도 비교적 간단해 집니다.
단순히 목록을 조회하는 기능만 들어 있는 소스입니다.
const express=require('express');
const bodyParser = require('body-parser');
const ejs=require("ejs");
const app=express();
const db = require('./db')
const memoClass = require("./memoclass");
const memo = new memoClass();
// 템플릿 엔진과 폴더 설정
app.set('view engine', 'ejs');
app.set('views', './views');
// public 폴더하위의 파일들을 기본으로 서비스
app.use(express.static('public'));
// 쪽지를 불러온다
memo.loading(db);
// 쪽지 목록
app.get('/memo', function(req,res) {
res.render('memo', {
memo: memo.list
});
});
app.listen(8080, function() {});
퍼블릭IP/memo URL 을 호출하면 아래와 같이 표시되지요.
쪽지추가 전달 규칙 정하기
이제 여기에 쪽지를 추가하는 기능을 붙여보도록 하겠습니다.
아작스(Ajax) + 제이슨(JSON) 방식으로 기능이 추가될 텐데요.
Ajax 는 통신방식을 의미합니다. 통신 데이터를 보낼 때 사용자 액션에 대해 화면 깜박임이 일어나지 않아 매우 유용하게 활용됩니다.
그리고 JSON 은 통신할 데이터 형태를 의미하는데요. node.js 가 아주 좋아하는 방식이지요 :)
아래의 3가지가 개발되어야 하는데요.
만일 여러분이 주도적으로 개발한다면 어떤 순서를 따르시겠습니까?
A. 유저의 쪽지 추가 액션
B. 쪽지추가 내용을 서버에 전달
C. 서버에서 쪽지가 추가되는 액션
D. 쪽지추가 끝난 다음 화면 변화
사실 개발환경에서는 어떤 순서라도 상관은 없습니다 :)
간혹 라이브 서비스 상황에서 직접 개발해야 하는 상황이 있긴 한데
그 경우 어느정도 완성 후 사용자에게 기능을 보여주어야 하기 때문에 C->B->A->D 순으로 개발되어야겠지요.
여기서는 A->B->C->D 순으로 진행하도록 하겠습니다.
A. 유저의 쪽지 추가 액션!
먼저 템플릿 소스인 views/memo.ejs 에서는 사용자가 메모를 추가하는 액션 버튼이 추가되는데요.
memo.ejs 소스에서 버튼의 UI 부분은 아래와 같습니다.
그냥 설명드리기 쉽게히 스타일 태그를 바로 넣었습니다.
<div class='memolayer'
style="position:absolute;top:0px; left:740px; width:60px; height:60px;">
<input type=button value="+"
style="width:60px;height:60px;font-size:30pt;"
onclick="input_memo()">
</div>
그리고 버튼을 클릭하면 input_memo() 라는 자바스크립트 함수가 호출되는데요.
간단히 문구를 입력받기 위해 해당 함수를 먼저 아래와 같이 작성합니다.
<script>
function input_memo(){
var msg=prompt("명언을 입력해 보세요", "");
}
</script>
이 부분은 버튼을 클릭시 아래와 같은 팝업창을 띄워줍니다.
여기서 사용자는 3가지 액션을 할 수 있는데요.
1) 아무 내용도 입력하지 않고 확인 버튼을 누르거나
2) 문구를 입력하고 확인 버튼을 누르거나
3) 취소 버튼을 누르는 것입니다.
쪽지를 추가해야 할 경우는 2)번 뿐입니다.
1)의 경우 msg 에는 공백값이 입력되고, 3)의 경우 msg에는 undefined 라는 '정의안됨'라는 상태값이 입력되기 때문에,
if 문을 이용, 2가지 경우를 제외하고 쪽지를 입력해주면 되는데요.
var msg=prompt("명언을 입력해 보세요", "");
if(msg!='' && msg!=undefined){
// 여기에 쪽지 추가 액션을 넣음
}
B. 쪽지 내용을 서버에 전달
서버에 쪽지를 추가할 떄 AJAX + JSON 방식으로 통신하기로 했었지요?
서버에 AJAX + JSON 방식으로 통신하는 소스는 보통 아래와 같습니다.
$.ajax({
url: 'URL주소',
type: 'post',
dataType: 'json',
data: { 전달이름1: 전달데이터1, 전달이름2: 전달데이터2, ... },
success: function(data){
성공시 처리할 부분
},
error: function(response){
실패시 처리할 부분
}
});
여기서는 URL주소를 memoadd 로 정하고, 전달할 이름은 msg 로 정해서
서버에 전달하기로 한다면 소스는 아래와 같습니다.
쪽지 등록 성공 후 처리는 저 아랫부분에서 다루도록 하겠습니다.
if(msg!='' && msg!=undefined){
$.ajax({
url: '/memoadd',
type: 'post',
dataType: 'json',
data: { msg: msg },
success: function(data){
},
error: function(response){
}
});
}
C. 서버에서 쪽지 추가
이제 노드 서버에서 전달받은 쪽지를 추가할 차례입니다.
사전 약속대로 AJAX + JSON 통신 방식을 사용하려면 서버소스에 아래 부분이 미리 선언되어야 합니다.
이 2줄이 없으면 JSON 통신 데이터를 해석하지 못하거든요.
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended : true}));
그리고 쪽지를 전달받는 주소가 새로 추가되었습니다.
바로 퍼블릭IP/memoadd 인데요.
아래 소스가 그 부분을 담당합니다.
// 쪽지 추가
app.post('/memoadd', function(req,res) {
});
msg 라는 이름으로 쪽지를 받았으면 여기서는 req.body.msg 라는 이름을 사용할 수 있는데요.
이 쪽지를 바로 데이터 베이스에 넣는 방법도 있지만 여기서는 memoClass 로 전달하여 처리하도록 하겠습니다.
그 이유는 코드를 재활용할 수 있도록 하여 추후 수정이 필요할 떄 되도록 적은 수정을 거치기 위함이지요.
간단히 보자면 memoClass 에 쪽지 추가함수를 넣고 아래와 같이 호출하면 되지만,
// 쪽지 추가
app.post('/memoadd', function(req,res) {
memo.add(db, req.body.msg);
});
문제는 이렇게 해서는 쪽지 추가가 성공했는지 실패했는지 알 수가 없습니다.
Mysql 데이터베이스에 데이터를 입력하는 액션이 프로미스로 실행되기 때문인데요.
프로미스에서의 순차 실행을 위해서는 async/await 세트 기능이 사용되어야 하는데요.
먼저 async 로 실행영역을 정해 줍니다.
// 쪽지 추가
app.post('/memoadd', function(req,res) {
// async 내에서는 순차 실행
(async () => {
})();
});
그리고 덩달아 오류가 발생할 때 예외를 처리하는 try(트라이) / catch(캐치) 도 추가합니다.
각 액션마다 이 부분을 넣어주면 서버가 죽을 일이 매우 줄어듭니다.
(async () => {
try {
}
catch (err) {
}
})();
이제 try 영역 내에 순차적으로 실행할 기능을 하나씩 넣어주면 됩니다.
쪽지 추가 -> 성공시 ok, 실패시 fail 이라고 사용자의 웹브라우저에 전달해 주는 부분입니다.
이때 프로미스로 실행되어야 하는 부분은 await를 명시해 주어야
결과를 수신하기까지 대기하게 되어 있습니다.
try {
// 클래스의 메모추가 함수를 호출,
// 결과를 result에 받아옵니다.
// 성공한 경우 true,
// 실패면 false 을 받아오는 규칙을 미리 정합니다.
let result = await memo.add(db, req.body.msg);
// 성공한 경우 사용자에게
// result 라는 전달명칭으로 ok 값을 보냅니다.
if(result)res.json({result: 'ok'});
// 실패한 경우 사용자에게
// fail 값을 보냅니다.
else res.json({result: 'fail'});
}
혹여라도 여기서 오류가 발생하면 아래 부분이 실행됩니다.
발생한 오류 내용 err을 그대로 사용자 웹브라우저에 전달하는 것으로 끝내는 부분이지요.
catch (err) {
res.json(err);
}
이제 memoClass 에서 쪽지를 추가하는 add()라는 함수를 추가할 차례입니다.
쪽지클래스에 add 함수를 추가하는데 async 를 앞에 붙여야 내부에서 프로미스 함수를 순차적으로 사용할 수 있습니다.
// 쪽지 클래스
class memoClass {
:
async add(db, msg){
}
}
쪽지를 추가할 때 랜덤한 위치를 계산하기 위한
보조함수 getRandom() 을 하나 추가하도록 하는데요.
클래스 내에서는 이 함수를 this.getRandom()이란 이름으로 사용할 수 있습니다.
// 쪽지 클래스
class memoClass {
:
getRandom(min,max){
return Math.floor(Math.random()*(max-min+1)+min);
}
:
}
add 함수에서 먼저 쪽지가 추가될 가로, 세로, 각도값를 랜덤하게 정해준 다음,
async add(db, msg){
let x=this.getRandom(0,700);
let y=this.getRandom(0,500);
let r=this.getRandom(-20,20);
데이터베이스에 쪽지를 추가합니다.
이 때 오류 발생을 감지하기 위해 try / catch 로 묶어 줍니다.
let r=this.getRandom(-20,20);
// MYSQL 쿼리 실행
try {
const result = await db.query(
"INSERT INTO memolist SET x = ?, y = ?, r = ?, memo = ?",
[ x, y, r, msg ]
);
:
} catch (err) {
:
}
데이터베이스 쪽지 추가 이후에, 서버 메모리에도 쪽지를 추가해줍니다.
const result = await db.query(
"INSERT INTO memolist SET x = ?, y = ?, r = ?, memo = ?",
[ x, y, r, msg ]
);
this.list.push({
x: x,
y: y,
r: r,
memo: msg
});
앞의 과정이 모두 성공했다면, 서버콘솔창에 간단한 문구 하나를 띄워주고
성공했다는 결과값 true 를 반환해주는데요.
async 함수에서는 반드시 await 를 명시해주어야 합니다.
console.log("쪽지가 추가되었습니다.");
return await true;
하지만 오류가 발생한 경우, 쪽지 추가가 실패했다고 알려주어야 겠지요?
그러니 catch 코드 영역에 아래 내용이 실행되어 false 라는 결과값을 반환해 줍니다.
} catch (err) {
console.log(err.message);
return await false;
}
그러면 이 클래스의 add 함수를 호출했던 부분에서 아래 코드가 작동하여 사용자의 브라우저에 ok 또는 fail 이란 결과를 최종적으로 전달해주고 서버는 사명을 완수하게 되지요 :)
let result = await memo.add(db, req.body.msg);
if(result)res.json({result: 'ok'});
else res.json({result: 'fail'});
D. 사용자 웹브라우저 화면 변화
다시 memo.ejs 로 돌아와서 쪽지 추가가 성공하든 실패하든 사용자의 웹브라우저는 그 결과를 받아들이게 되는데요.
쪽지 성공/실패와 관계없이 정상적으로 처리가 끝난 경우는 무조건 success 의 영역이 실행이 됩니다.
success: function(data){
},
하지만 통신이 실패하거나 서버에서 특별한 오류가 발생했었다면,
화면에는 아무 변화가 없게 됩니다. 그냥 콘솔이라는 창에 오류 내용을 보여주는 것이 전부입니다.
error: function(response){
console.log(response);
}
쪽지 추가의 성공여부는 data.result 라는 변수에 들어 있는데요.
result 라는 이름은 서버측에서 해당 이름을 전달해주었기 때문입니다.
쪽지 추가가 성공한 경우 아주 간단히 문서를 새로고침합니다.
그러면 자연스럽게 추가된 쪽지를 불러와 화면에 보여주기 때문에 쪽지가 보여지겠지요.
이 때 화면 깜박임이 발생할 수 있습니다.
if(data.result=='ok')location.reload();
하지만 쪽지 추가가 실패한 경우라면 화면에 실패했다는 안내문구를 보여주는데요.
화면 새로고침은 굳이 할 필요는 없어 하지 않고 끝냅니다.
else if(data.result=='fail')alert("쪽지 추가가 실패했습니다.");
여기까지가 클래스를 별도 모듈 분리, 쪽지를 추가 기능을 넣는 설명이었습니다.
내용이 다소 난해하게 느껴졌을 수도 있을듯 한데요.
전문분야이기 때문에 이 분야에 관심 있는 분에게만 눈에 들어오실 수 있으니
양해 바랍니다 :)
필요하신 분에게 도움이 되셨을지 모르겠네요.
도움이 되셨다면 공감 한방, 댓글은 굿잡! 감사합니다.
Node.js 를 기본기부터 제대로 익히고 싶으신가요?
관련 책자의 일부 본문을 공개합니다!
https://itadventure.tistory.com/468
'코딩과 알고리즘' 카테고리의 다른 글
백준 문제풀이 - 큰 수 A+B (22) | 2021.12.20 |
---|---|
AWS 안 보이는 요금 해제하기 (40) | 2021.12.05 |
node.js express | 클래스? (Class) (14) | 2021.10.04 |
node.js express | 템플릿쪽지함 #8. 기다려! await! (4) | 2021.09.26 |
node.js express | 템플릿쪽지함 #7. mysql2 프로미스 (4) | 2021.09.14 |