[초보자들 EP.07] 미션 : 메신저 만들기(Summer편)

 

안녕하세요! 오랜만에 돌아온 초보자들2 Summer입니다.
시간이 쏜살같이 지나 2017년 새해가 밝았습니다. (모두 새해복 많이 받으세요!)

올해는 과연 개발왕이 될 수 있을런지… @_@
앞서 Dimanche가 지난번 프로젝트였던 메신저 만들기 글을 올렸습니다.

>> 재미와 감동이 넘치는 Dimanche의 메신저 만들기 글읽기

그럼 이번 Summer의 메신저 정복기를 함께 보실까요?

메신저를 만들자!

이번 프로젝트는 바로 메신저였습니다. 메신저 하면 바로 떠오르는 노란색의 메신저부터 시작해서 초록색, 파란색 등 여러가지 서비스가 생각나는데요ㅡ 저희는 아주 간단한 수준의 메신저를 만들기로 했습니다.

요구사항은  Dimanche의 글에서 언급된 바와 같은데요, 간단하게 정리하자면

  • 주고 받는 것은 텍스트의 글이고,
  • 대화 상대는 1명-무조건 1:1의 대화만 가능하고,
  • 주소록 추가와 별명 수정은 가능하나 친구 삭제는 없는,

그런 아주 간단한 수준의 메신저였습니다.

위에서 말한 노란색 메신저를 사용자로 쓸 때는 참 쉬워보이고(?) 때로는 별로라고 불만을 토로하기도 했는데 막상 제가 메신저를 만들려고 하니 제 요구사항을 수용하기도 버겁다는 생각이 들면서… 평소 쓰던 메신저들의 개발자들에게 죄송한 마음과 존경심을 갖게 되었다는(먼산) (역시 사람은 자기가 해봐야 합니다)

어쨌든, 기본적으로 메신저라 함은 메세지를 주고 받을 수 있는 프로그램을 말합니다.
결국 (어떤 메시지를) (어떻게 주고받을것인가)가 핵심임을 알 수 있습니다.

저 둘을 해결하면 메신저는 끝나는 것!!! 하나하나 정복해 봅시다.

차암~ 쉽죠^^?(과연…) 

무엇을 기억할까? 데이터 골라내기

프로그램에서 제일 중요한 것은 ‘데이터’입니다. 데이터라 함은 중요한 것, 기억해야 할 것을 의미하는데 메신저 같은 경우에는 주고받는 메시지가 대표적인 데이터가 되겠죠.

무엇을 기억할지는 기획을 하면서 판단해볼 수 있습니다.
예를 들어 주소록을 보자면… 주소록의 요구사항은 다음과 같았습니다.

  • 채팅 페이지에는 연락처 목록과 채팅화면이 있다.
  • 사용자는 연락처를 추가, 삭제, 별명 변경을 할 수 있다.
  • 연락처에 새 메시지(읽지 않은) 개수가 표시된다.

이를 충족하기 위해 다음과 같이 화면구성을 했고

다음과 같이 용어집을 작성했습니다.

화면에 보면 많은 숫자와 글자들이 있는데요, 저기서 있는 모든 수치들이 기억될 필요는 없습니다.

예를 들면 친구목록 옆에 10명의 숫자는 따로 기억하지 않아도 됩니다. 왜냐하면 주소록으로 등록된 목록의 숫자를 세면 알 수 있으니까요. 또 화면에는 보이지 않지만, 필요에 따라 주소록을 언제 등록했는지 기억해야 할 수도 있습니다.
이렇게 기획한 문서를 바탕으로 무엇을 기억해야할 지 뽑아낸 것을 “데이터 엔티티”라고 합니다.

어떻게 기억할까? DB 설계하기

데이터 엔티티를 뽑아냈다면, 이제 실제로 그 데이터를 어떻게 기억해야할 지 정해야 합니다. 데이터를 기억하는 방법은 여러가지가 있습니다. 변수에 임시로 저장해둘수도 있고, DB처럼 미리 정해진 형식에 데이터를 저장하고 관리할 수 있습니다. DB도 여러가지 종류가 있어서 데이터의 성격과 개발환경에 따라 가장 적절한 것을 선택할 수 있는데, 저희는 클라이언트단에서 Session storage와 Indexed DB를, 서버에서 mySQL을 사용했습니다.

처음엔 DB에 그냥 제가 정한 데이터 엔티티를 데이터 타입에만 맞게 잘 넣으면 안되나!?!? 라고 생각했습니다.
그래서 다음과 같이 DB를 설계했습니다.

주소록엔 본인 전화번호, 상대방 전화번호, 상대방 별명을,
메시지엔 보낸 사람, 받는사람, 내용, 작성날짜를 필요한대로 넣었습니다.
참 솔직하고 정직한 DB죠..^^;;
물론 저렇게 해도 프로그램은 잘 돌아갑니다. (좀 비효율적일지라도)

하지만 메시지 DB만 봤을 때, 주소록에 등록되지 않은 사람들이 메시지를 받고 보낼 수 있게 되어있습니다. 물론 누군가 날 먼저 주소록에 등록해서 메시지를 보낼 수 있지만, 그렇다하더라도 그 누군가는 주소록에 등록한 번호에 대해서만 메시지를 보낼 수 있어야 합니다. 즉, 정확하지 않은 데이터가 저장될 수 있다는 것 입니다.

이를 방지하게 위해 관계형 DB를 이용할 수 있습니다. 관계형 DB는 데이터를 테이블 형식으로 데이터를 저장하는 DB를 말하는데요, 테이블간의 관계 및 제약 등을 통해 좀 더 확실하고 정확한 데이터를 저장하고 그를 효율적으로 활용할 수 있도록 합니다.

주소록과 메시지 테이블간의 관계를 이용해 다음과 같이 DB구조를 바꿨습니다. 메시지에서 발신자-수신자로 저장하는 것이 아니라, 주소록id 번호를 저장하게 했습니다. 이를통해 주소록에 등록된 사람들이 메시지를 보낼 수 있도록 보장했고, 더불어 발신자-수신자 번호를 중복 저장하는 것도 막아내는 효과를 누릴 수 있었습니다.

프로그램에서 데이터가 정말 중요한 만큼 어떻게 하면 중복없이! 확실하게 보장된! 데이터만 저장할 수 있을 지도 열심히 공부해야겠다고 생각했습니다.

어떻게 주고 받을까? API 문서 작성하기

자 이제 어떤 데이터가 필요한지도 알았고 DB구조도 어떻게 되는지 정해졌습니다. 그렇다면 이제 데이터를 보내기만 하면 되는데… 어떻게 보내야할까요?
답은 인터넷.. HTTP 통신이겠으나…(헉)
구체적으로 어떻게 서버와 클라이언트가 상호간에 어떤 데이터를 어떤 형식으로 주고 받을 것인지 정할 필요가 있습니다.

이를 위해 통신 API를 위한 JSON을 작성합니다. 이번 프로젝트에서는 클라이언트-서버 통신이 일어나는 부분에만 API JSON을 작성하면 되었습니다. 그것 부분은 로그인, 비밀번호 발급(회원가입), 비밀 번호 재발급, 메시지 받기, 메시지 전송하기 5부분이었습니다.

한 예로 회원 가입 부분은 다음과 같이 작성했습니다.

{
    "etc":{
        "realurl":"api/?member/getPw",
        "request":{
            "phone":"핸드폰 번호. 정규식은 /^01([0|1|6|7|8|9]?)([0-9]{3,4})([0-9]{4})$/g"
        },
        "memo":[
            "비밀번호 얻기(회원가입)",
            "회원 가입 실패 시 응답 메시지에 [error] 데이터가 전송됨",
            "code:isMember:이미 가입을 한 사람인 경우 에러값"
        ],
        "error":{
            "code":"isMember"
        }
    },
    "auth": {
        "status":"logout"
    },
    "data":{
        "result":"success"
    }
}

etc 부분은 데이터 구조와 데이터 내용을 설명하는 부분으로, 따로 API 문서를 작성해서 관리하는 것보다 여기서 하나로 관리하는 것이 효율적이라 여기에 적어 관리합니다.
realurl은 실제 요청을 보내는 url이며, request는 서버에 요청하는 값으로 밸리데이션이 필요한 경우 위에처럼 조건을 덧붙입니다.
실제 response로 받게 되는 부분은 ETC를 제외한 부분인데요, response의 구조는 얼마든지 달라질 수 있습니다.
저같은 경우 auth라는 키로 로그인 상태를 파악하고, data로 실제 요청에 대한 응답값을 받게 되어있습니다.

이렇게 JSON으로 작성을 해두면 다음과 같은 장점이 있습니다.

  1. 데이터형식이 통일되어 있으니 서버가 ASP든, PHP든 NODE든, 혹은 제가 만든 클라이언트의 메신저가 아니어도 데이터를 주고받을 수 있음
  2. 서버가 구축되지 않아도 JSON에 써놓은 데이터를 가지고 와서 그걸 바탕으로 클라이언트를?프로그래밍을 진행할 수 있음
  3. 따로 API 설명을 위한 문서작성을 할필요 없음(=덜귀찮음)

이런 이유로 이를 실무에서 적극적으로 활용하고 있습니다.

어떻게 효과적으로 주고 받을 수 있을까? 롱폴링!

지금은 노란 메신저로 이모티콘에 파일도 보내고 속도도 엄청 빠른게 당연하지만, 초기에는 통신 속도가 느려서 답답한 적이 있었다는 걸 기억하시나요..? (그때 번개도 나오고 막그랬는데말이죠..)

메신저의 생명은 빠른 의사소통! 어떻게 하면 효과적으로 빠르게 통신을 할 수 있을지 고민해볼 필요가 있습니다.

저희 메신저는 HTTP 환경으로 통신을 합니다. HTTP 통신은 기본적으로 클라이언트-서버간의 통신 프로토콜로 클라이언트에서 요청을 보내면 서버에서 응답을 하는 구조로 구성되어 있습니다. 간단한 구조에 쓰기 편하다는 장점이 있지만, 반대로 단점도 있습니다. HTTP에서는 서버의 정보가 갱신되었는지 아닌지를 알기 위해서는 클라이언트가 항상 먼저 요청을 보내야합니다. 만약에 서버에 갱신된 정보가 없으면 클라이언트가 보낸 요청은 헛수고가 된거죠. 제 메신저도 서버로 누가 자기에게 메시지를 보냈는지 알 방법이 없기때문에 서버에 주기적으로 물어보게 되어있습니다. 누군가 자기에게 메시지를 보내지 않았다면 헛수고가 된것이고, 이런 사용자가 많게되면 헛수고만 가득한 통신 부하만 생기게 되는 거죠.

한국인에게 특히나 더 괴로운 이 화면…..

이런 HTTP 통신의 단점을 보완하기 위해 클라이언트의 요청 없이도 서버에서 통신을 먼저 보낼 수 있는 푸시 서버, 저희가 이용한  “롱폴링” 등을 도입할 수 있습니다.

롱폴링은 HTTP 요청이 왔을 때 서버가 일정 시간 동안 응답을 대기 시키는 것을 말합니다. 만약 대기하는 동안 서버 측 데이터에 변화가 생기면 바로 응답을 반환하구요. 실제 보내줘야할 데이터가 있을 때만 응답을 하게 되니 쓸데없는 응답이 줄어들겠죠.

이를 구현하기 위해 setInterval함수와 for문 이용했습니다.

var intervalId, cnt;
intervalId = setInterval(function(){		
    cnt++; //반복할 횟수를 카운트. 		
    if(cnt < 5){
        //데이터가 있는 경우 바로 인터벌 해제하고 데이터 전송
        if(storage[reqBody.phone] !== undefined || storage[reqBody.phone] !== []){
                resBody = {
                      auth:{status:'login'},
                      data: getMsg(reqBody.phone)
                };
	        clearInterval(intervalId);
		res.send(resBody);
	}
    }
    else {
         //최대 5회(5초)까지 롱폴링 시킴.
         clearInterval(intervalId);						
         resBody = {
                   auth:{status:'login'},
                   data: {}
         }
         res.send(resBody);
    }
}, 1000);

롱폴링도 응답을 지연시키면서 서버의 리소스를 쓰게 되므로 절대적으로 좋은 것은 아닙니다. 상황에 맞게 가장 적절한 것을 선택하는 센스!

어찌되었든 완성…

글에는 적지 못했지만 구현하면서 어려웠던 점들이 참 많았습니다. Javscript의 스코프를 제대로 몰라서 undefined의 늪에 허우적 거리던 때, 비동기 통신을 제대로 몰라서 다시한 번 빠졌던 undefined의 늪.. 그리고 콜백 함수 지옥 등.. @_@… 그래도 다행이 동료 Dimanche와 상사분들의 도움으로 어찌 저찌 1.0버전을 완성했습니다.

로그인

메신저 메인

메신저 채팅화면

그래도 프로그램에 있어 가장 중요한 데이터를 찾고 저장하는 법, 그리고 그것을 교환하는 방식에 대해 전반적으로 경험해보고 배울 수 있었던 프로젝트였습니다.

마지막으로… 이번 프로젝트를 진행하면서 한가지 더 아쉬웠던 점은 이 프로젝트가 프로그래밍 실력을 쌓는 교육과정일 뿐만이 아니라 ‘실무를 배워나가는 과정’이기도 하다는것을 잊은 것이었습니다.  이제 어느정도 회사가 일하는 방식을 보고 배웠으니 제 프로젝트에서도 똑같이 적용해보며 실무에서 더 잘 쓸 수 있도록 했어야 했지만, 예전 습관처럼 개인 프로젝트하듯 프로그램을 만들고 있었습니다. (그래서 더 삽질을 하면서 시간이 오래걸렸구요..) 교육용 프로젝트 또한 실무를 배우기 위한 디딤돌임을 잊지 말자고 다짐을 했습니다.

올해도 힘을 내어 봅니다. 개발왕이 될 그날까지 화이팅!


summer| bsidesoft 신입사원
디자인을 공부했던 섬머는 개발까지 해버리겠다는 욕심으로 개발자의 세계에 입문하게 되었습니다. 개발왕이 되어 멋진 제품을 만들어내는 꿈을 꾸고 있습니다. 코드, 디자인을 포함한 세상의 모든 아름다운 것들과 미드, 그리고 달리기를 좋아합니다.