'블렉스 이야기' 시리즈BLEX :: 블렉스 개발 중간(?) 회고

배진오

@baealex

소비적인 일보단 생산적인 일을 좋아합니다.

블렉스의 개발을 잠정적으로 마무리하면서 개발을 되돌아보고자 한다. 현재 블렉스의 개발자는 초반에 비해서 매우 사소한 수정 작업만 하면서도 오늘도 공부했다는 착각에 빠져있다. 이는 매우 심각한 상황이므로 하루라도 빠르게 서브 프로젝트를 진행해야 할 것 같다! 아, 물론 블렉스에 새로운 아이디어가 떠오른다면 계속해서 붙여나갈 생각이다.



BLEX — BLOG EXPRESS ME

블렉스는 왜 만들어 졌는가? 나는 블로그라는 서비스를 예전부터 좋아했다. 관심을 끌고 싶거나, 돈을 벌고 싶거나, 기록을 위해서거나 이유는 다양했지만 공통적으로 블로그라는 서비스는 이러한 변덕스러운 "나를 표현하는"데 가장 적합한 서비스라 생각했다. 그래서 다양한 서비스를 사용해 보면서 각 블로그 서비스의 장점과 단점을 파악하고 블로그라는 본연의 기능에 충실한 서비스를 만들고 싶었다.

나는 가입형 블로그 서비스에 대해서 그다지 좋지 않은 감정을 가지고 있었다. 네이버 블로그를 보라. 정말 개성없는 블로그 같다. 우리는 모두 각기 다른 형태와 생각을 지녔는데 어찌 블로그는 그리 일관적이란 말인가? 그랬던 나의 생각을 외국의 블로그 서비스인 @미디엄이 깨부셨다. 미디엄은 개성없지만(?) 사이트 자체가 특색있고 그런 블로그를 누구나 쉽게 시작할 수 있다는 점이 매력적이었다. 그래도 약간의 개성을 표현하기 위한 자유로움은 있어야 한다고 생각하며 깃허브 블로그의 마크다운이 블로그라는 특성에 정말 잘 맞는것 같아 이 세가지를 조합하는 것을 목표로 개발하였다.


삽질시작

마크다운

처음 마크다운을 적용하기 위해서 GitHub API를 사용하였다. 다만 해당 API는 횟수 제한이 존재하였고 글을 읽을 때마다 마크다운을 HTML로 변환하는 방법을 사용하고 있었기에 좀만 글을 읽어도 제한이 걸렸다. 그래서 내가 사용해봤던 Parsedown이라는 PHP 라이브러리를 사용해서 직접 서버를 열었고 그 서버와 통신을 시도했다. 결과는 아주 만족스러웠으나 동적으로 변환하면 로딩이 매우 느려져 새로운 모델을 하나 더 만드는게 훌륭한 선택으로 보였다. 이 기능은 수정할 부분 없이 완벽에 가까우나 PHP 코드를 Python으로 변환해 볼 예정이다. BLEX 만드는 것 보다도 오래 걸릴듯...

이미지 업로드

Python 그리고 Django에 대한 이해도가 낮았기 때문에 AJAX를 활용해 동적으로 이미지를 업로드하는 방법이 마땅히 떠오르지 않아 이 부분도 PHP를 사용했었다. 덕분에 크로스 도메인에 대한 개념을 얻을 수 있었다. 다만 최근에 봤던 글인 폴리글랏을 지양하라는 글을 보고 납득한 뒤 Python의 지식을 충분히 갖추고 변경하였다. 이 기능도 딱히 개선할 부분은 없다.

비동기 통신

알림은 Toast로 구성되어 있다. AJAX에 대해서 어느정도 이해했지만 JSON에 대해서 무지했던 나는 그냥 문자열만 비동기로 주고 받는 정도로 AJAX를 활용했다. 그래서 알림 역시 서비측에서 모두 렌더링을 해주고 HTML만 넘겨서 사용자가 볼 수 있도록 구성했다. 그러다 JSON에 대해서 알게되고 Python의 딕셔너리와 DjangoJSON 응답에 익숙해 지면서 고쳤다. 데이터를 딕셔너리에 담아 JSON으로 응답하면 사용자가 그려서 볼 수 있도록 말이다.

위 부분을 개선하면서 댓글에 대해서도 완전하게 변경했다. 기존에는 어처구니 없게도 댓글이 수정되거나 삭제되면 페이지를 다시 불러와서 댓글 부분만 잘라서 다시 붙이는 식이었다. 그러므로 서버에서 쓸데없이 트레픽을 낭비하고 있었던 셈인데 이를 JSON 응답으로 바꿔서 처음에는 SEO를 위해 서버에서 댓글을 그려줬지만 수정이나 삭제는 사용자 측에서 그려주는 방식으로 전환하였다.

소셜 로그인

이 시점에서 개발할 당시에는 나와 유사한 환경에서 무언가를 추가하는 사람은 없었다. 독자적인 개발환경이 되어 소셜 로그인 예제를 찾아도 쉽게 이식할 수 없었다. 몇 개의 글을 보고 종합하여 어떤 원리로 소셜 로그인이 되는지 알게 되고 넣었는데 (사실 크게 어려운 건 없었다. all-auth라는 라이브러리를 사용해서) 이게 내부적으로 어떻게 돌아가지는지 몰라서 난감하다. 토큰 주고 받은 다음 토큰을 저장하고 사용자가 사용할 아이디를 입력하도록 해야하는데... 그냥 알아서 만들어진다. 이걸 어디서 가로채야 하는지 모르겠다. 반드시 개선이 필요한 부분이다. 소셜 로그인을 직접 구현하던가 해야겠다.

메일

메일 전송도 아주 간단하게 넣을 수 있었고 전송도 나름 잘 된다. 스레드를 사용해서 비동기 전송도 할 줄 알게 되었고 다만, 메일이 즉각즉각 보내지지 않는다. 이것도 고쳐야 되는데 검색해도 잘 나오지 않는다... 이걸 그나마 개선했다고 해야할지 말지는 모르겠는데 HTML로 렌더링한 결과를 전송하면 굉장히 오래걸리는 반면, 텍스트만 전송했을떈 전송이 매우 빨랐다. 그래서 일단 인증메일은 텍스트로만 보내기로 하였다...

트레픽

GIF 이미지의 용량이 너무 과도하게 크다고 판단해서 개인 블로그에도 항상 MP4로 전환하여 업로드 하고있다. 일반 사용자 입장에서도 그냥 지나가듯이 보는 이미지일 뿐이며 품질도 차이가 거의 없었기에 필수적인 작업이다. 그래서 BLEX에도 GIFMP4로 변환시키고 있다. FFmpeg라는 소프트웨어를 활용중인데 매우 만족스럽다. 또한 이미지 썸네일을 생성하도록 하여 전체적인 퍼포먼스 향상과 트레픽 절약을 가능케 하였다!


조언구하기

처음에는 남들에게 무작정 조언을 구하는 건 무례하며 실례되는 행동이라고 생각했지만, 혼자서 개발하다보니 나의 개발 진척 상황과 앞으로의 방향등이 너무나 불안했다. 그래서 @OKKY에 부끄러움을 무릎쓰고 질문을 올렸다. 다만 지속적으로 해당 사이트에서 링크되어 있으면 트레픽을 빨아먹는 것 같아서 글은 내리도록 하였다. 여하튼 댓글을 달아주신 분들에게 너무나 감사하여 이렇게 모든 댓글을 남겨놓았다.


회원가입하자말자 500에러가....ㅠ 카카오 로그인도 작동안하는건가요? — meaway님

처음 사이트의 링크를 올리면서 Analytics도 열어놓고 있었다. 사람들이 갑자기 쏟아지듯 들어오는데 심장이 되게 두근거렸다. 재밌다고 해야될지... 여하지간 그러면서 댓글도 실시간으로 보고 있었는데 위와같이 말씀해 주셔서 정말 긴장감이 말로 표현할 수 없을 정도였다. 메일 인증을 급격하게 지우느라 약간의 로직 에러가 남아 있었다. 여하지간 빠르게 고칠 수 있어서 다행이었다. 그리고 소셜 로그인도 추가했었지만 왠지 모르게 500 에러가나서 추가중이라고 괴변하였다...


이쁘네용 — 햇개발자님
디자인 잘하시네옹 ㅠ — black_p님
너무 퀄리티 있는데요;;; ㄷㄷ 디자인 감각이 정말 부럽네요.. — 개꿀님
와우.. ㅠ ㅠ 디자인 감각 있으시네요 부럽... 사이트 잘보고갑니다. 전체적으로 매우 깔끔하네요 — 로직X님

사실 필자는 디자인 감각이 뛰어나진 않지만 더 나은 디자인을 위해 노력했기에 위 말씀은 매우 기분좋은 칭찬으로 들렸다. 또한 지금처럼 직접 디자인을 할 수 있기까지 정말 많은 시간이 걸렸다. 티스토리나 깃허브 블로그의 테마를 수정하거나 만들면서 무지한 내가 할 수 있는 거라곤 몇 날 몇 일 밤을 지새며 코드를 바꿔보는 것 뿐이었다. 그러면서 많은 실력이 쌓인 건 사실이지만 종종 허무하다고 느꼈던 적도 있었는데 이런 칭찬은 그 시간이 헛되지 않음을 말해주는 것 같아 다행이다.


회원가입버튼누르면 submit 되는 action url은 스크립틀릿으로 해주셨나요? — Autowired님

해당 페이지의 action이 공백으로 나타나 있어서 물어보신 것이라 생각된다. 같은 페이지에 post 방식으로 전달한 것이어서 action url은 안써도 됐는데 있어서 놀랬다.


글 하나 남겨봤어요ㅎㅎㅎ 좋습니덩 — 드디어회원님

많은 분들이 회원가입을 시도해 주셨는데 안타깝게(?) 글쓰기는 많이 시도해 보시지 않은 것 같다. 마크다운 글쓰기라는 아주 멋진 방법을 사용하고 있는데 말이다. 아쉽다... ㅎㅎ


장만월!!!!! 합격!!!!!! — 초보.님

아이유는 역시...?


위에 #태그 표시하는 부분 꼭 넣으셔야 하는 부분인지.... 전체적으로 깔끔, 심플한 컨셉에 안 맞는거 같아서요. 개인적인 의견입니다.... — 사회초년생입니다님

방문자가 처음 사이트를 들어오면 어떤 글들이 주로 작성되는지 보여주기 위해서 메인 화면의 상단에 태그를 랜덤으로 배치했는데(미디엄 느낌으로...) 위와같은 의견을 내주셔서 빠르게 삭제했다.


음........................................ 일단 RESTful 대한 개념과 설계방법을 학습하시고 개선해보시공..(https://meetup.toast.com/posts/92) 홈페이지를 만든 포플을 쓰고싶은거잖아요? 그러면 내가 어떤 기술을 사용하고? 내 코드는 이렇게 짯어 이 알고리즘을 이용하여 최적화했어 뭐 이런걸 어필하면 좋고 백엔드 쪽도 표현하고싶으면 ERD 설계도를 보여주면서 이렇게 설계햇어~ 라고 표현하는 것도 좋고용. 그냥 난 이 기술로 이렇게 만들었어 봐봐 홈페이지만 보여주면 음 디자인을 보여주고싶은건지 프론트 기술을 보여주고 싶은건지 알 수 없지 않을까... 끄적끄적해봤습니다~ 물론 제 말이 답이 아니지만요 ㅎㅎ 아래 글을 참고하면 취업하시는데 도움이 되실거라 생각이 들어요~ https://okky.kr/article/368504, https://github.com/jojoldu/junior-recruit-scheduler, (형식만 참고??) https://www.sl — 후닥후닥님

정말 친절하게 링크와 의견을 달아주셔서 정말 감사했다. 비동기 통신을 적극적으로 활용한 것도 이분의 의견 덕분이었다.


혹시 장고 공부 어떻게하셨나요 ..? 파이썬 실력은 어느정도 되야할까용?? — Wu님
저도 저번 학기에 Django 했었는데요 고생하셨네요 ㅠ 디자인이 진짜 너무 깔끔하고 예쁘게 잘하셨네요! — Vors님

장고는 로그인, 회원가입, CRUD 정도만 인터넷에서 익히면 나머지는 파이썬 실력인 것 같다.


도메인 구매한건가요? 어디서 구매했는지 알수있을까요? — 쭈구리님

닷홈의 무제한 웹호스팅이 너무 고마웠고 익숙한 이름이었기에 사용하였다. 안타깝게도 지금은 구글 도메인즈로 옮겨가고 있지만...


이미지 carousel 부분 어떻게 구현하셨나 봤더니 플러그인 없이 css 애니메이션만으로도 저렇게 할수가 있네요..ㄷㄷ 사이트 정말 잘 만드셨습니다~ — 새드맨님

인터넷에서 가져다 쓴 거라서... 할 말이 마땅히 없다. CSS 만으로 구현한게 신기해서 넣었던 디자인이다.


잘만드셨네요 — ISA님
신입 지망생이요?? ㅠㅠ 저는 6개월의 국비과정을 거치고 신입으로 일한지 한달이 좀 넘었는데 아직 저정도는 엄두도 못낼꺼같습니다. ㄷㄷㄷ 저도 더더 노력해야겟네요. — leean_님
"취업 시즌이 다가오니 많은 걱정이 생겼습니다" 걱정 안하셔도 될듯 ㄷㄷ 진짜 개~잘만들었네요 — 24_26님

이런 말씀을 해주시면 한편으론 안도가 되지만... 걱정은 여전하다. 하지만 걱정이 없어질 정도로 개발에 몰두해 볼 생각이다.


저도 올해 내로 개인 블로그 사이트를 만드는 게 목표였는데, baealex님의 사이트를 보고나니 더 의욕이 생깁니다. 열심히 공부하시고 포폴을 쌓아가시는 모습이 존경스러워요! 디자인도 깔끔하고 통일성 있고, 여러모로 센스가 넘치십니다. ㅎㅎ — 큐킴님

부담스럽기도 하지만... 정성스레 칭찬해주시니 너무 좋다 ❤


우와 프레임워크 react나 vue 없이 만드신건가요??? — Yeon93님

내 입장에선 사용하지 않은게 부끄러웠는데... 놀라신건지 놀리신건지 잘 모르겠다...


다시 삽질시작

문자열 차이점 탐지

깃허브를 사용하면서 단 한 번도 버전관리가 '어떻게' 작동하고 있는지 관심을 가지지 않았다. 그저 "변경된 '무언가'를 감지하겠지"라는 생각 뿐이었다. 그러다 깃처럼 우리의 블로그에도 버전관리를 넣어보면 어떨까 싶었다. 변경된 점을 유지하기 위함이 아니라 다른 사용자가 내 글에 참여할 수 있는 기능을 위해서 말이다. 그러기 위해선 버전관리라는 시스템에 대해서 이해해야했다.


Git, 대체 어떻게 작동하지?

인터넷에 'Git의 원리'라고 검색하면 깃이 어떻게 작동하고 있는지 알려준다. add를 하고 commit을 하고 push를 하면 어떻게 동작하는지 말이다. 내가 궁금한 것은 그런게 아니라 더 깊은 내용이다. 하지만 검색 키워드를 도통 찾을 수 없어 직접 개발해야 할 것 같다. 이 글은 아마 그 작업에 대한 실험과 고찰이 될 것이다. 우선은 문자열을 가지고 장난쳐 봐야겠다.


문자열 변경 감지
old_text = ["안녕하세요, 배준호입니다.", 0]
new_text = ["안녕하세요, 배진오입니다!", 1]

먼저 위와같은 문자열을 두자. 현재는 테스트를 위해서 배열의 첫번째 인덱스엔 문자열을 두번째엔 정수를 넣었지만 실제로는 정수가 들어갈 부분에 시간을 넣어서 비교할 생각이다. 우선 위 문자가 똑같은지 다른지 비교를하자. 달라진 게 없다면 버전을 관리할 필요가 없으니까.

기본적인 방법으로 길이 혹은 사이즈로 구분하면 위 문자열의 경우 변경되었는지 전혀 분간할 수 없으므로 우선 성능이 제일 빠를 것 같은 길이로 먼저 비교한 뒤 집합으로 변경하여 차집합을 구하도록 하였다.

def is_change(old_text, new_text):
    if len(old_text) == len(new_text):
        if len(set(old_text) - set(new_text)) == 0:
            return False
    return True
old_text = ["안녕하세요, 배준호입니다.", 0]
new_text = ["안녕하세요, 배진오입니다!", 1]

print(is_change(old_text[0], new_text[0])) # True
old_text = ["가", 0]
new_text = ["가가가가가", 1]

print(is_change(old_text[0], new_text[0])) # True
old_text = ["가다", 0]
new_text = ["가다", 1]

print(is_change(old_text[0], new_text[0])) # False

의도한 대로 동작한다.

변경된 지점 탐색
  • 가장 쉽게 버전관리 하는 방법 : 위와같이 다르다는게 입증되면 그냥 데이터베이스에 때려박아 놨다가 사용자가 병합하면 합쳐버리고 데이터를 지운다.
    • 용량이 너무 아까움
    • 사용자한테 바뀐 지점 보여주려면 2번 일해야 됨
    • 자바스크립트보단 파이썬으로 하고 싶음

아마도 해당 함수를 만든다면 아래와 같은 입력에서 다음과 같은 출력을 만들고 싶다.

안녕하세요. 배준호입니다.

BLEX는 모두를 위한 블로그 서비스입니다.
언제까지나 무료입니다.

감사합니다. 배준호입니다.
안녕하세요. 배진오입니다.

BLEX는 모두를 위한 블로그 서비스는 맞지만...
부분유료화가 될지도 모르죠?

감사합니다. 배진오입니다.
[
    [0, "준호", "진오"],
    [1, "입니다", "는 맞지만.."]
    [0, "언제까지나 무료입니다.", "부분유료화가 될지도 모르죠?"]
    [1, "준호", "진오"]
]

첫번째 인덱스의 의미는 인덱스다. '배준호'라는 문장은 2번 나오는데 그중에 첫번째 단어라는 의미이다. (이거 보다는 탐색중인 위치를 기록하는게 여러모로 편리할 듯) 두번째 인덱스의 의미는 원문 세번째 인덱스의 의미는 수정문이다. 일단 다음과 같이 탐색하면 되지 않을까?

원문과 변경글을 순차적으로 탐색하다가 다른 지점을 감지한다. 그 순간부터 배열에 차곡차곡 쌓는다. 그리고 다시 같아지는 지점으로 넘어가면 결과 배열에 넣어준다.

def changed_list(old_text, new_text):
    point = 0
    data = {
        'results': list(),
    }

    new_list = {
        'point': int(),
        'old': str(),
        'new': str()
    }
    while True:
        if not old_text[point] == new_text[point]:
            new_list['point'] = point
            new_list['old'] += old_text[point]
            new_list['new'] += new_text[point]
            break
        point += 1
    data['results'].append(new_list)
    return data

# {'results': [{'new': '\xa7', 'old': '\xa4', 'point': 22}]}

만들다가 급작스런 뇌정지가 발생했다. 고려할 점이 너무나 많았다. 특히 글자 갯수가 다른 경우 어디부터 어디까지 구분시킬지 판단할 수가 없었다. 그리고 길이가 다른 경우 다시 같아지는 지점을 찾는 것은 매우 극악이다. 성능도 너무 구려지고... 우선 공통되는 부분을 전체적으로 삭제하는 방식으로 접근하면 어떨까 싶었다.

import re
def remove_common(old_text, new_text):
    old_word_lists = re.split(' |\n', old_text)
    new_word_lists = re.split(' |\n', new_text)
    return {
        'old': set(old_word_lists) - set(new_word_lists),
        'new': set(new_word_lists) - set(old_word_lists), 
    }

for i in remove_common(old_text[0], new_text[0])['new']:
    print(i)
print('-----')
for i in remove_common(old_text[0], new_text[0])['old']:
    print(i)

"""
부분유료화가
될지도
모르죠?
배진오입니다.
맞지만...
서비스는
-----
무료입니다.
서비스입니다.
배준호입니다.
언제까지나
"""

공통 부분을 제거했다. 이제 이 부분을 원문에서 위치를 감지하고 원레 출력하고자 하는 결과물로 출력하고자 하였다. 다만 또 문제는 어떤 부분이 어떤 문자에서 변형된 것인지 알 방도가 없다. 그냥 수정을 허용하는 사용자에게 원문과 수정문을 보여주고 어떤 부분이 변경되었는지 어떤 부분이 추가되었는지 두개의 분활된 화면으로 보여주면 될 것 같다! 역시 난 똑똑해!

데이터 베이스엔 변경되기 전 부분의 [포인트, 문장]과 변경된 후 부분의 [포인트, 문장]을 저장하자. 중복되는 문장은 어떻게 처리하지? 결국 가장 하고 싶지 않았던 방법인 데이터 통째로 때려넣기를 사용해야 할 것 같다. 아... 점점 산으로 가는구나.

변경사항 적용하기

첫번째 인덱스엔 위치가 있고 두번째 인덱스엔 원문이 있으므로 첫번째 인덱스에서 원문의 길이만큼을 변경문으로 변경하면 된다. 다만 길이가 다른 문자열로 변경되면 첫번째 인덱스의 위치의 의미가 무색해진다. 따라서 변경한 지점의 다음 배열들의 첫번째 인덱스의 값을 변경문과 원문의 길이 차이만큼 더해주어야 한다.


사용자 태그

사용자들이 댓글을 통해서 의사소통을 하는 방법으론 댓글에 답글을 달던지 사용자간에 태그를 하는 방법이 있다 적어도 내가 아는 선에서 말이다. 답글을 다는 것이 일반적인 소통 방법이지만 여기서는 사용자 태그를 이용하였다.

왜? 사용자 태그를?

필자가 egloos에서 얻은 교훈으로 블로그 서비스의 '친목'을 최소화 시키기 위함이다. 댓글을 달리고 답글이 달리기 시작하면 외부에서 접근한 유저는 댓글을 통해서 유저들의 의견을 쉽게 파악할 수 있지만 자신이 참여하기에 어려움이 생길것이라 생각했다.

사용자를 태그하여 얻는 단점

가장 큰 단점은 사용자들이 댓글을 보면서 사용자들의 의견과 의견에 대한 의견을 한눈에 파악하기 어렵다. 또한 자신이 태그됐을때 지속적으로 많은 댓글이 달리고 있는 글이라면 어느 지점에서 태그됐는지 파악하기 어렵다.

그래서 알고리즘
  • 댓글 텍스트 에어리어를 선택하면 현재 글에 댓글을 작성한 사용자 목록을 AJAX를 통해서 호출한다.
    • 현재는 그냥 포스트에 로딩하면 호출되도록 하였다.
  • 댓글 텍스트 에어리어에서 @를 입력하면 사용자를 가져올 준비한다.
    • 자바스크립트의 키코드를 사용하여 구현했는데 모바일에선 사용이 불가하여 버튼으로 대체하였다.
  • 문자를 입력하면 알맞은 사용자를 매칭한다.
    • 텍스트 에어리어 내부에서 수행하기 위해서 키코드를 사용하려 했으나 한글을 탐색하기 어렵고 위와같이 모바일 사용자가 배제된다.
    • 현재는 프롬프트를 통해서 사용자의 이름을 입력하고 작성한 사용자가 목록에 있는 경우 전송할 목록에 추가하고 아니면 없다고 알려준다.
    • 프롬프트는 너무 초라하니까 모달 같은 거로 바꾸고 인풋창의 내용을 비교하면서 사용자를 찾아주도록 하자.
    • 팝오버라는 레이아웃이 있는데 사용자 위에 마우스를 올리면 태그할지 프로필을 볼지 선택할 수 있는 것도 좋을 듯.
  • 여하지간 작성자가 찾으려는 사용자를 드롭다운으로 출력하여 빠르게 찾을 수 있도록 해주자.
  • 댓글을 달자.
  • 사용자에게 알림을 보내자.


포스트 추천

최근 인기

오키에서 주간 베스트라는 항목이 있는데 이 항목에 글들을 어떻게 배치할 수 있을까? 솔직히 매우 간단한 알고리즘이라 생각된다. 일주일 이내에 작성된 글을 먼저 필터링하고 조회수 높은 순서로 배치해주면 되겠다. 굳이 좋아요가 아닌 조회수를 사용한 이유는 좋아요는 친목으로 인해 쉽게 상위로 갈 수 있기 때문이다. 다만 제목으로 어그로를 끄는 경우를 대비하여 조회수 대비 좋아요 비율을 측정하여 포스트를 배치하면 되겠다.

현재는 순전히 조회수 순으로 배치하고 있다.

사용자 맞춤 추천

유튜브에는 사용자 맞춤 영상 추천 알고리즘을 가지고 있다. 어떤 방식으로 돌아가는 걸까 고민하며 나름 나만의 방식으로 추천 방법을 생각해 보았다.

  1. 포스트 목록을 최신순으로 불러온다.
  2. 포스트를 리스트로 변경하고 각각의 포스트 옆에 우선순위 값(100)을 추가한다.
  3. 사용자가 추천한 글과 같은 태그의 글의 우선순위에 1.?를 곱한다.
  4. 사용자가 구독한 사용자의 글을 찾아 우선순위에 1.?를 곱한다.
  5. 사용자가 읽은 글의 시리즈를 찾아 우선순위에 1.?를 곱한다.
  6. 사용자가 읽은 글의 태그를 찾아 우선순위에 1.?를 곱한다.
  7. 사용자가 이미 읽은 글은 우선순위에 0.?를 곱한다.

먼저 위 방식을 사용하기 위해서 가장 필요한 건 사용자가 읽은 포스트에 대한 정보다. 이를 보관하는 방법을 어떻게 하는게 효율적일까? 텍스트 필드에 콤마 형식으로 분할하여 스플릿? 아니면 varchar 형식으로 일일이 저장? 지금은 먼저 전자의 방식으로 진행할 것이다. 또한 이는 사용자의 활동 정보를 수집하는 것이므로 계정 설정에서 허용하거나 해지할 수 있도록 해줘야겠다.

트랜디한 포스트

밸로그에는 트랜디한 포스트를 출력한다. 트랜디한 포스트라... 처음엔 당연히 좋아요가 최근에 많이 된 글을 찾으면 되리라 생각했지만 그 글을 어떻게 찾느냐가 문제였다. 나름 나만의 방식으로 생각해 본 것은 아래와 같다.

최근에 추천을 많이 받았다?

  • 얼마나 주기가 짧은지 비교해야 된다.
  • 마지막으로 추천된 날짜를 보관하고 사용자가 추천하면 그 텀을 비교한다.
  • 식은 (20/(내가 추천한 날짜 - 마지막 추천된 날짜 + 1)) 정도면 되지 않을까?
  • 좋아요를 지속적으로 좋아요 취소하는 경우를 대비하여 취소시 과감하게 -20을 제공한다.
  • 트랜디 수치는 절대로 0 이하로 내려가지 않게 한다.
  • 해당 점수는 데이터베이스에 트랜디값으로 보관하고 트랜디값으로 정렬하면 될듯하다.
  • 또한 수치를 일정 기간에 한번 감소시키던 초기화하던 해야겠다.

최종적으로 현재는 트랜디한 포스트 + 최근인기 포스트 방식을 조합하여 경향 포스트를 나열하고 있다.


사용자와 포스트와 시리즈의 관계

사용자와 시리즈의 관계
  • 사용자는 시리즈를 여러개 가질 수 있다.
  • 시리즈는 오직 한 명의 사용자만 가질 수 있다.
  • 사용자 1 - N 시리즈
시리즈와 포스트의 관계
  • 시리즈는 포스트를 여러개 가질 수 있다.
  • 포스트는 오직 하나의 시리즈만 가질 수 있다.
  • 시리즈 1 - N 포스트

원래는 위 관계가 정석인데 내가 무슨 생각으로 시리즈와 포스트의 관계를 다대다로 만들어 버렷다. 포스트에다 시리즈를 외래키로 넣었으면 끝날일을... 구현하다가 문득 "앗! 뭔가 잘못됐다"라는 것을 느꼈다. 그래서 일단 멈추고 진지하게 개선할 방법을 찾아야했다. 시리즈와 포스트의 이 잘못된 관계를 개선할 방법을 말이다.

  • 시리즈는 포스트를 여러개 가질 수 있다.
  • 포스트는 시리즈를 여러개 가질 수 있다.

이미 이러한 관계는 '태그'라는 존재가 수행하고 있으므로 중복되는 관계다. 하지만 곰곰히 생각해보니 시리즈도 여러개면 좋은 경우가 있었다. 예를들어 한 작가가 '소설 A'를 집필하고 외전을 냈다.

  • 소설 A 1권
  • 소설 A 2권
  • 소설 A 외전

그리고 소설 B를 집필하셨다.

  • 소설 B 1권
  • 소설 B 2권
  • 소설 B 3권
  • 소설 B 외전

그런데 사람들이 외전을 좋아하면 작가는 '외전 시리즈'를 만들어 사람들에게 제공해 줄 필요가 있다. 이렇게 하려면 어떠한 추가정보가 필요한가?

  • 소설 A 외전
    • 소설 A 시리즈
    • 외전 시리즈

소설 A 외전은 아래와 같이 두가지의 시리즈를 가지고 있을 것이다. 사용자가 소설 A 외전에 접근 했을때 사용자가 진정으로 원하는 시리즈는 무엇일까? 1권에 접근했으면 당연히 A 시리즈를 보고 싶은거겠지만 외전에 먼저 접근했다면 외전 시리즈를 보기 위함으로 생각된다.

  • 접근한 게시물의 시리즈가 2개일 때
    • 시리즈의 가장 최상위 게시물이 접근한 게시물인 시리즈를 연결한다.

하지만 위와같이 설정한 경우 문제가 있겠다. 소설 A 외전 이후 소설 A 3권을 집필 하셨다면 문제가 커진다. '소설 A 1권'을 통해서 '소설 A 외전'으로 이동한 경우 다음 편으로 무엇을 추천할까? 시리즈를 통해서 다음으로 넘어간 경우 GET으로 series를 보내서 올바른 시리즈를 유도하자 잘못된 관계로 시작했지만 끝은 창대해졌다.


무한스크롤

무한 스크롤에 대한 인식

페이징은 반드시 사용되어야 하는 기술이다. 왜냐하면 시스템 입장에서 데이터 베이스의 모든 내용을 사용자에게 전달하는 것은 매우 비효율 적이며 많은 트래픽을 발생시킨다. 모든 웹사이트에서 페이징은 기본적으로 들어간 기술이 되었다. 그 와중에 페이지를 자동으로 넘기는 방식이 무한 스크롤이다. 필자는 이 무한 스크롤 기술을 매우 좋아한다. 온전히 기술적으로 좋아보이고 구현이 간단하다. 하지만 안타깝게도 많은 사람들은 무한 스크롤에 대한 거부감을 가지고 있다.

무한 스크롤 정말 짜증나지 않나요?

무한 스크롤이 언제부턴가 거의 대부분의 SNS 사이트들이 기본으로 채용하는 UI가 되었죠.. 근데 사실 이거 무지하게 불편한 UI 아닙니까? 무한 스크롤의 단점은 첫째 과거 게시물에의 접근성이 거의 0에 수렴한다는겁니다. 10페이지 뒤쪽 게시물을 보기 위해서 페이지 로딩 10번을 해서 차근차근 접근해야 한다니.. 이건 한마디로 보지마! 라고 말하는거나 다름없죠. 니 인내심이 이기나 내가 이기나 싸우자는겁니다. 게다가 웃긴건 간신히 몇번이고 스크롤을 아래로 댕기고 새로운 페이지를 로드하고 그래서 원하는 게시물을 찾았다고 치죠. 그걸 클릭해서 본 다음에 뒤로 돌아가면 다시 목록 처음부터 리셋!!!

출처 : XETOWN

사실 읽고나면 정말 공감가는 메세지다. 이 글 외에도 대부분 무한 스크롤에 반감을 가진 사람들은 같은 반응이다. 뒤쪽에 있는 게시물을 보기가 힘들고 대부분의 사이트는 무한 스크롤 된 게시물을 보고 뒤로가면 1-2페이지에서 미아가 된다. 사실 BLEX도 같은 문제를 앓고 있으며 필자는 그것을 애써 모른척하고 있었다.

무한 스크롤 이슈 해결 방법
  1. 가장 뒤에 있는 글도 보고싶다.
  2. 스크롤 된 글을 읽고 뒤로가기 문제.

가장 뒤에 글은 오래된 순으로 정렬하면 그만일 것 같고 스크롤 된 글을 읽고 뒤로가기 문제에 대해서 찾아보니 해결하는 방법이 몇가지가 있었다. Hash를 이용하여 해결하는 방법, pushState를 이용하여 해결하는 방법. 전자의 경우에는 검색 엔진에서 난리가 날 것 같고 후자의 경우에는 뒤로가기시 이동한 페이지의 이전 페이지를 볼 수 없다는 단점이 있다. 현재는 Cache를 사용해서 임시적으로 해결했는데 썩 좋은 방식은 아닌 것 같다.


팀블로그 고안

사실 이 블로그 서비스의 제 1 타겟을 개발자로 생각하고 있었는데 개발자의 경우는 팀 단위로 움직이는 경향이 많으므로 확실히 좋은 의견이라고 생각했다. 또한 동호회나 전문가 모임이 참여할 수 있는 길도 열린다. 다만 나는 팀 블로그를 운영한 경험이 없었다. 그냥 팀의 블로그를 내가 운영하거나 이곳에서 팀 블로그를 운영했는데 간단히 계정을 새로 만들어 운영하였다.

데이터 모델

팀 블로그를 사용하려면 어떤 데이터들이 필요할까? 우리 팀을 기준으로 생각하자.

  • 이름 : Comma, Dev.
  • 로고 : PNG Image
  • 가입된 날자 : 2019-12-31
  • 소유자 : baealex
  • 회원(편집자, 작성자) : DeCrazy, Imakara38
  • 글 : 소개(팀 공식 글), 활동(baealex), 활동(baealex), 프로젝트(팀 공식 글), 활동(DeCrazy)
  • 소개 : About page
  • 짧은 소개 : Bio
  • 홈페이지 링크 : https://commadev.org

티스토리를 살펴보면 회원도 소유자와 편집자와 작성자로 나뉘어져 있는데 여기서도 같은 방식을 사용해야 겠다.

  • 소유자 : 글을 삭제할 권한이 있음. 사용자를 초대하거나 추방할 수 있으며 다른 사용자에게 소유 권한을 넘겨줄 수 있음. 만일 결제 시스템까지 이어진다면 결제 대상이 됨(하위 포함)
  • 에디터 : 글을 편집할 권한이 있음. 팀 공식 글을 작성할 수 있음(하위 포함)
  • 작성자 : 글을 작성할 권한이 있음

팀 글의 경우에는 기존의 글 모델을 그대로 활용하기엔 어려움이 보인다. 다만 섞으려면 약간의 귀찮음이 존재할 것 같다. 또한 댓글의 경우도 완전히 새로 만들어야 되고 말이다... 결국 기존의 글 모델을 사용하는게 좋을 듯 싶은데... 어떻게 확장시킬지 약간은 고민을 해봐야겠다.

  • 팀 : 외래키(글 작성시 사용자가 속한 팀을 선택할 수 있다. 혹은 팀 페이지에서 글을 작성하도록 할 예정이다.)
  • 공지 : 팀에 속하지 않은 글이면서 공지인 글은 BLEX의 공지가 된다. 팀에 속한 글이면서 공지인 글은 팀의 공지가 된다. 다만 이와같이 설정한 결우 일반 사용자가 BLEX의 공지를 설정할 수 있게 될지도 모르겠다. 뷰에서 처리를 해줘야 할 듯. 아니면 팀 글 작성을 별도로 생성해야겠다.

미디엄을 보면 팀 카테고리도 존재하는데... 굳이 팀을 위해서 카테고리를 추가하는 건 불필요해 보인다. 사용자 페이지와 마찬가지로 태그로 카테고리를 생성하되 팀스러운 느낌을 주기위해 별도의 레이아웃을 사용해야 할 것 같다.

URL

사용자의 경우에는 @라는 키워드를 사용하고 있는데 팀은 좀 다른 키워드를 사용해야 할 것으로 보인다. 현재는 @와 같이 URL에서 사용할 수 있는 키워드 중에 :을 염두해 두고있다.

/:team
/:team/about
/:team/post-name
/:team/@author/post-name
  • /:team : 로고와 짧은 소개와 작성한 글을 표시하는 페이지, 팀에 속한 멤버라면 글을 쓸 수 있는 버튼을 표시하도록 하자.
  • /:team/about : 마크다운 양식으로 작성된 소개가 보여지며 멤버의 목록이 출력된다.
  • /:team/post-name : 팀의 공식 글로 팀만 표시된다.
  • /:team/@author/post-name : 팀의 개인 글로 팀과 작성자가 표시된다.


중간댓글 고안

드롭박스 페이퍼나 구글 독스와 같은 문서 중간에 댓글을 다는 기능을 추가하고 싶었다. 다만 이 기능을 어떻게 구현해야 할지 감이 잡히지 않는다. 고려해 본 방법은 다음과 같은데 확신이 들지 않는다.

  • 사용자가 댓글을 달았던 지점의 높이를 기억한다.
  • 사용자가 댓글을 달았던 키워드를 기억한다.

일단 첫번째 방식의 문제점은 곂치는 댓글이 존재할 수 있다는 것이다. 굉장히 방해요소가 될 가능성이 크다. 두번째 방식의 문제점은 몇번째 키워드인지 저장해야하는데 해당 글이 수정된다면 해당 댓글은 의도하지 않은 위치에 달리게 된다.

  • 사용자가 댓글을 달았던 키워드 주변에 태그를 임의로 삽입한다.

친구가 같이 고민해 준 방법인데 이 방법의 문제는 해당 글이 수정된다면 댓글이 전부다 삭제된다는 점이다. (해당 댓글들이 삭제된다면 일반 댓글로 전환시키는 건 어떨까? 괜찮은 방법인 것 같다.) 그렇다면 만약 해당 키워드에 댓글이 달리고 또 댓글이 달린 건 어떻게 저장하며 표한할 것인가... 하...

우선 걍 높이를 기억하는 방식을 사용하여 임시로 구현하되 프리미엄 전용으로 하던 글에 방해가 되지 않도록 해야겠다.


배터리

최초에는 사용자의 활동 기반의 경험치 시스템을 도입하려고 했으나 이를 배터리라는 개념으로 변경하게 되었다. 배터리는 사용자의 활동에따라 점차 상승하며 사용자가 활동하지 않을시 감소한다. 이를 통해 다른 사용자들은 해당 사용자가 근래에 활동하는 사용자인지 판단할 수 있다. 행동에 대한 경험치는 고정적이며 감소는 포스트 트랜드 수치와 동일한 방식으로 적용된다. 배터리 수치는 포스트의 트렌디 수치가 조정되는 같은 시간에 5씩 감소하게 된다.


스레드

글은 하나로 끝나는 경우도 많지만 대게는 여러 시리즈를 통해서 완성된다. 장편 글의 시리즈라면 현재의 포스트 방식으로 작성하는 것이 좋겠지만 단편 글(TIL, 개발노트 등)인 경우 사용자를 짧은 시간내에 다른 페이지로 이동시키는 등 불편함이 존재한다. 이를 개선하기 위해서 스레드라는 외국에서 흔히 사용되는 게시판 양식을 가져왔다.

  • 포스트 : 정적으로 채워놓은 글
  • 스레드 : 동적으로 채워가는 글


스토리

스레드는 기존 포스트에서 댓글이라 불리는 스토리가 핵심 컨텐츠가 된다. 글과 같은 페이지에 아무 제약없는 댓글을 달아 컨텐츠를 생성해 나간다. 쉽게 생각하면 주제를 생성하여 타임라인을 채워간다고 생각하면 좋겠다. 실제로 스레드 양식을 만들게 된 근본적인 이유는 개발자가 개발노트를 작성하거나, 신입사원으로서의 경험을 짧게 시간별로 공유하고 싶었는데 기존의 방식으로는 작성하기가 어려웠따.

동적으로 채워가는 글이기에 생성한지 오래되어 하위에 노출된다면 의미가 없을 것이다. 따라서 스레드는 플로트 형식으로 정렬된다. 스토리가 달린 날짜를 기준으로 이전에 생성한 스레드임에도 상위에 노출할 수 있게 된다. 최신순으로 상위에 노출된 가능성이 높으므로 트렌드 알고리즘을 적용하지 않는다.

스레드 형식의 글에서는 타 사용자도 참여할 수 있도록 설정할 수 있다. 기존의 글로 따지면 댓글 차단과 유사한 기능이다. 다양한 사용자들이 한 스레드에 대해서 고품질의 글을 완성시킬 수 있다. 또한 스레드를 구독하여 빠르게 스레드에 올라오는 스토리를 확인할 수 있다.


마무리하며

블렉스의 개발은 이렇게 마무리 되었고 앞으로도 지속적인 개발은 계속 될 것이다. 아직 추가하고 싶은게 너무 많다. 현재 나는 내가 깃허브 블로그를 계속 사용할지 BLEX로 옮겨올지 깊은 고민에 빠져 있었는데 나조차도 고민되는 서비스였다는 생각에 아차! 싶었다. 사용자에게 매력적인 서비스를 만들기 위해서 노력해야겠다. 이사를 할 수 밖에 없는 매력적인 서비스로 거듭나자!

'블렉스 이야기' 시리즈

블렉스는 제가 애정을 가지고 개발하는 하나의 취미이자 한명의 자식(?)이며 처음으로 서비스를 시작한 프로젝트입니다. 혼자서 풀사이클 개발을 하다보니 부족한 점이 많을 수 있지만 부족한 점은 채워나가면 된다고 생각합니다. 이곳은 블렉스의 부족한 점을 채워나가는 모습을 그려놓은 시리즈입니다.
mildsalmon
8개월, 4주전

다른 곳에는 없는 다대다 방식이 blex만의 개성이라고 생각할 수도 있겠네요 !

baealex
8개월, 4주전

@mildsalmon 저도 그렇게 생각했는데 생각보다 쓸일이 없더라구요 😂

frieden
5개월, 4주전

글이 일기 읽듯이 쭉쭉 내려가네요 ㅎ ㅎ 이 글 읽고 회원가입했습니다!

baealex
5개월, 4주전

@frieden 앗 감사합니다 👍 저도 덕분에 이 글을 다시 봤는데 감회가 새롭습니다 :)

댓글을 작성하기 위해 로그인이 필요합니다.