글 목록으로 이동

봄가을 블로그

회고2024년 10월 05일--views

필요한 물건을 직접 만들어 갖다 쓰기

방송통신대를 무사히 조기졸업하게 해준 내쓸내만 서비스, Farming Paper에 대한 이야기입니다.

파밍페이퍼 간략 설명

개요

방통대를 무사히 조기졸업하는 성과를 가져다준 Farming Paper라는 내쓸내만(내가 쓸려고 내가 만든) 서비스가 어떻게 탄생할 수 있었는지, 어떤 기술을 썼는지 소개합니다. 후기도 첨가합니다. Node.js(Typescript)나 프론트엔드 기술에 대한 사전지식이 있으면 읽기가 더 수월할 테지만 웹서비스 기술과 연이 있다면 어느정도 읽기 쉽도록 구성했어요.

방송통신대를 다닌 이유와 목표

참고: 방통대 다니는 것과 관하여 예전에 쓴 글

인생을 돌아보면 좀 망나니 같은 10대 후반과 20대 초반을 보냈습니다. 도전정신 가득한 어린 나는 수능을 보지 않겠다고 결정했죠. 고등학교 1학년 까지만 다니고 학교를 나와 이것저것 했습니다. 낭만적인 인생이었지만 슬슬 현실도 생각해야 하는 시기 - 20대 중반에 이르렀습니다. 대학을 가지 않고도 그럭저럭 잘 살아온 나에게 주위 사람들은 굳이 학위를 딸 필요가 있겠냐 의문을 표했지만, 나는 마크 주커버그나 빌 게이츠가 아닌 걸요. 학위 없이 매번 스스로를 증명해야 하는 상황을 상상했을 때 피곤해졌습니다. 실제로 나중에 본인 사업을 하거나 했을 때 학위가 없다는 게 걸림돌이 되기도 쉬웠구요. 더이상의 깊은 고민을 하진 않고 방송통신대 입학 신청 했습니다. 공교롭게도 첫 회사 입사와 동시에 첫 학기를 시작했습니다(지금 생각하면 미친 짓이긴 했습니다). 으아아 21학번! 본래 특기대로 “컴퓨터과학과”를 선택했습니다.

하는 김에 좀 더 스펙타클하게 하기 위해 3년 만에 끝내자 라는 목표를 세웠어요. 학점 4.0이상을 유지하며 있는 학점 없는 학점 여기저기 다 끌어다 모으면 졸업학점을 3년만에 채우는 게 가능했습니다. 가능은 했습니다. 너무 빡센 게 문제였죠.

사실 방통대는 그렇게 공부를 열심히 하지 않아도 졸업은 할 수 있는 구조입니다. 모든 평가는 절대평가라서 평균적인 학생 수준에 맞춘다면(C-B) 그렇게 어려울 게 없습니다. 공부를 많이 안하고 가도 1/3정도 맞출 수 있을 수준이구요.

그런데 고득점을 노린다면 이야기가 달라집니다. 열심히 하는 사람들은 열심히 하기 때문에 변별력을 위해 만점 받기는 아주 어렵습니다. 한 과목의 총점이 95점 이상: A+, 90점 이상: A를 받는데, 중간과제물 실수 하나 하면 1~2점은 그냥 썰립니다. 기말고사 25문제는 한 문제당 2점이라서 3개 틀리기만 해도 94점이 되니 A+를 받지 못합니다.

3년만에 조기졸업을 하겠다는 목표는 그것 자체로도 공부를 성실하게 하게 해준 장치가 되었습니다. 이제 공부를 어떻게 효율적으로 할 것이냐가 남았죠. 저는 Farming Paper를 솔루션으로 삼았구요.


Farming Paper 이전의 역사

반복 문제 풀이의 맛

곰곰이 생각해보니 역사는 초딩때까지 거슬러 올라가네요. 2000년대, 학원 다니는 게 아주 익숙했던 때였습니다. 요즘보단 덜 경쟁적인 분위기였죠. 영어, 수학 뿐만 아니라 태권도, 피아노, 미술 등등의 학원들이 엄청 많았어요. 저는 한자시험 급수를 따기 위해 한자 학원에 다녔습니다.

거기에는 그 곳만의 공부법이 있었습니다. 일단 이해 여부와 상관없이 진도를 한번 쫙 뺍니다. 그러곤 죽이되든 밥이되든 기출문제 문제집을 일단 풀기를 시작하고, 모르는 문제는 일단 넘어가고, 답지를 보며 못푼 문제를 한번 리마인드 하고, 다시 다음 기출문제를 풀고… 무제한 수레바퀴에 올라탑니다! 기출문제란 아래와 같은 느낌이었습니다.

한자시험 기출문제

저런 문제를 한 20회 동안 풀고 있으면 완전 생면부지인 한자였어도 어느덧 머릿속의 데이터베이스에 차곡차곡 쌓여나갔습니다. 그 감각은 쾌감을 불러 일으켰죠. 왠지 정복한다는 느낌. 생각도 하기 전에 답을 쓰고 있는 제 자신을 발견.

씸플한 빈칸 문제를 반복해서 푸는 방법이 학습에 아주 유용하다는 걸 이때부터 깨닫게 됩니다. 아쉽게도 중학교 시절 문제집은 그런 짤막한 주관식 구성으로 되어 있지 않았어요. 실제 시험에 나오는 문제와 비슷한 형태로 5지선다가 많았죠. 정복해나간다는 감각이 그리웠습니다. 그러다가 결국 빈칸 문제를 직접 만들고야 맙니다.

일단 컴퓨터 앞에 앉아 한글2010 프로그램을 엽니다. 그리고 요약이 잘 정리되어 있는 자습서를 키보드 앞에 꺼내놓습니다. 문장들을 하나하나 타이핑하여 옮겨 쓰고 결정적인 단어나 외워야 할 연도를 빈 칸으로 만듭니다. 괄호 열고, 띄어쓰고, 괄호 닫고. 다단을 2단으로 하여 종이 공간 효율이 최대한으로 나게끔 했습니다. 손수 제작한 문제 더미를 대여섯 복사본 프린트해서, 무제한 반복합니다. 이것도 성과는 나름 잘 나왔습니다. 실제로 문제집보다 성과가 좋았는지는 잘 모르겠지만 이렇게 나만의 방법을 개발하여 공부를 해서 성과를 봤다는 것에 흡족했죠.

한글2010

CLI 프로그램

시간은 흐르고 흘러, 이제 방통대를 다니는 어엿한 성인이 되었습니다. 그러나 이제는 한글이나 워드도 더이상 만지기 싫었습니다. 나름 개발자니 간단한 CLI로 랜덤 주관식 문제 풀이 프로그램을 만들었습니다.

CLI(Command Line Interface) 프로그램이란 텍스트 입출력만으로 구동하는 프로그램입니다. 명령어 입력 + Enter 키로 프로그램이 시작되며 그래픽과 관련된 게 하나도 없어서 마우스가 쓸모 없습니다. 왜 CLI 라는 게 존재하냐? 사실 “그래픽”이라는 개념이 비교적 최근에 들어온 것이고 그 전에는 이런 CLI 프로그램 밖에 없었습니다. CLI 프로그램은 아직까지도 다양하고 많은 곳에 쓰이는데요, 일부 업무나 태스크에서는 CLI 프로그램이 그래픽 기반 프로그램보다 월등히 강력하기 때문입니다.

파밍페이퍼 간략 설명

처음에 pnpm run main 명령으로 프로그램을 시작합니다. 위 프로그램을 보면 3개 줄이 반복하는 걸 볼 수 있습니다.

  1. 문제. ? 로 시작합니다. 빈칸 문제가 나옵니다.
  2. 이제 답을 입력해야 합니다. >> 가 출력된 채로 기다리며, 사람이 타이핑 해서 엔터를 칩니다.
  3. 결과를 보여줍니다. 정답은 정답! 으로, 오답은 오답!과 함께 답을 알려줍니다.

문제 데이터는 아래처럼 하드코딩 되어 있었습니다.


import { Question } from "./types";
export const questions: Question[] = [
{
type: "short",
message:
"생산자가 어떤 상품을 한 단위 더 생산하고자 할 때 받고자 하는 가장 낮은 가격",
correct: "한계수용의사금액",
},
{
type: "short_order",
message: "한계지불의사금액은 ___곡선의 ___",
correct: ["수요", "높이"],
},
// ...중략
];

type 은 문제의 유형을 나타내며, 위 예시 코드에서는 단순주관식("short")과 순서 기반 다중주관식("short_order") 가 있음을 볼 수 있습니다.

문제는 아래 코드로 출제됩니다. 코드 자체는 간단합니다. CLI 프롬프트는 inquirer를 이용했습니다. prompts 함수를 실행하면 사용자의 입력을 받게 됩니다. 무한 반복하며, 랜덤으로 문제를 뽑으며, 문제를 맞출 때마다 그 문제는 확률이 0.1배 되고(맞춘 문제는 다시 풀 필요가 없으니까), 문제를 틀릴 때마다 그 문제의 확률이 10배가 됩니다(다시 풀어야 하니까).


import { createQuestionGenerator } from "./question-generator";
import { questions } from "./questions/adsp";
import { createPromptModule } from "inquirer";
import { Question } from "./questions/types";
const prompts = createPromptModule();
const generator = createQuestionGenerator(questions);
while (true) {
const { question, index } = generator.gen();
switch (question.type) {
case "short": {
const response = await prompts({
// 중략. 문제 출력 및 사용자의 입력을 받음.
});
if (response.answer === question.correct) {
console.log("정답!");
generator.updateWeight(index, 0.1);
} else {
console.log(`오답! (정답: ${question.correct})`);
generator.updateWeight(index, 10);
}
}
// 중략
}
}

이정도만 해도 사실 꽤 쓸만합니다. 문제를 만들기도 쉽고 풀기도 쉬워요. 문제 푸는 이력이 저장되지 않아서 껐다 키면 수정된 가중치는 전부 날라가지만 불편함을 느끼진 않았습니다.

이 CLI 프로그램에는 치명적인 단점이 하나 있었는데요, 바로 폰으로 할 수가 없었습니다. 개발환경이자 실행환경인 맥북을 떠나 폰으로 프로그램을 실행시키려면 뭔가 어딘가에 배포하여 다른 기기로도 접근할 수 있어야 했습니다. 그래서 자연스럽게 웹서비스를 떠올렸습니다.

Farming Paper 화면 구성

Farming Paper라는 이름은 “파밍”이라는 게임 용어에서 따왔습니다. 노가다를 통해서 아이템을 얻는다는 뜻인데요, 사실 이 단어도 마치 농사꾼이 농사일을 열심히 해서 농작물을 수확하는 과정에 그 유래가 있습니다. 효율적인 방법으로 시간을 꾸준히 투자하면 정직하게 효과를 본다는 숨은 뜻이 닿기를 바랐습니다.

컨닝페이퍼와 비슷한 어감도 조그마한 유래입니다.

이 서비스는 공부(특히 암기)에 도움이 되는 도구라는 정체성을 가지고 있습니다. 마치 공책과 연필처럼요. 자유도나 편리성을 크게 살려야 하는 게 미션입니다. 학습을 게임화하여 재밌게 하자는 취지는 전혀 없었죠.

화면을 살펴보면 크게 2~3개 밖에 되지 않습니다.

대시보드 화면

마치 노션 페이지처럼 문단을 자유롭게 추가할 수 있는 느낌을 원했습니다. 하나의 문단은 사실 하나의 “문제”이며 텍스트를 포맷팅하는 것처럼 “빈칸”이라는 것을 설정하며 추후에 이 빈칸이 문제로 출제됩니다. 태그로 필터링하여 문제들을 확인할 수도 있고 태그를 설정할 수도 있습니다.

대시보드 화면
  1. 태그 선택: 태그를 선택하여 문제를 필터링합니다. 태그를 선택해야 문제 풀기 버튼이 활성화됩니다. 태그는 문제 편집를 편집할 때 추가할 수 있습니다.
  2. 추가 버튼: 문제를 추가합니다. 새 문단을 추가하는 것 같은 느낌입니다.
  3. 문제 풀기 버튼: 현재 필터링된 문제들 기준으로 문제 풀기를 시작합니다. Solve 화면으로 넘어갑니다.
  4. 문제 편집: 노션에서 문서를 편집하는 것처럼 문제를 편집할 수 있습니다. 특정 텍스트가 빈칸이라면 포맷팅이 조금 다르게 됩니다. 선택한 후 빈칸을 설정할 수 있습니다. 편집하게 되면 문제는 자동으로 저장됩니다. 문제의 태를 추가/삭제하거나 문제 자체를 삭제할 수도 있습니다. 문제는 아래로 스크롤 내리면 쭉쭉 있습니다.

Solve & Result 화면

문제 풀기 화면

문제 풀기 버튼을 누른다면 위와 같은 화면이 표시됩니다. 답을 모두 쓰고 Submit 버튼을 눌러 제출합니다. 그 외 동작은 아래와 같습니다.

  • Pass: 문제를 풀기 싫고 그냥 답만 봅니다.
  • Edit: 이 문제의 오타 등을 수정하고 싶을 때 즉시 편집합니다.
  • Reroll: 새로운 문제를 뽑습니다.
문제 풀기 결과 화면

문제를 풀면 결과 페이지로 옵니다. 정답이거나 오답을 알려줍니다.

  • Regard as Correct: 오타 등으로 인해 억울하게 틀렸을 때, 혹은 대충 넘기고 이 문제를 덜 풀고 싶을 때 정답으로 간주합니다. 정답이면 출제 확률이 줄어듭니다.
  • Ignore This Try: 이 풀이를 마치 처음부터 없었던 것처럼 간주합니다. 확률에 변동이 없습니다.
  • Edit: 이 문제의 오타 등을 수정하고 싶을 때 즉시 편집합니다. (Solve 화면과 동일)

폰에서 문제 풀기

염원이었던 폰에서 풀기가 이제 가능해졌습니다! 이제 출퇴근길에서도 열심히 공부할 수 있게 되었습니다 ㅠㅠ

휴대폰에서도 사용할 수 있는 파밍페이퍼

Remix

웹서비스 구현은 좀 힙해보이는 Remix로 해봤습니다. 최근 ChatGPT가 프론트엔드 프레임워크를 Next.js에서 Remix로 바꿔 끼웠다는 뉴스를 봤는데요, 사실 예전에도 Shopify가 든든한 협력자가 된다(2022, 링크)는 뉴스로 좀 핫해졌던 바가 있습니다.

Remix는 정말 사용하기가 쉽더군요. Next.js와 비슷한듯 좀 다른 느낌이었습니다.

Remix 의 경로 지정 방식
Remix에서도 폴더 구조와 파일명을 잘 설정하여 페이지 경로를 지정합니다.

본래 업무에서는 Next.js의 Pages Router 위주로 사용해왔고 App Router도 끼적대본 적이 있지만 Remix로는 그보다 쉽게 웹서비스를 만들 수 있었어요. React Router 기반이라서 이미 거기에 익숙한 분들은 더 쉽게 녹아들 수도 있습니다. 개발 접근법이 Next.js와 다른 부분이 있어 좀 헷갈릴 수 있지만 Remix의 규칙에 익숙해지면 고민해야 할 영역들이 크게 줄어든다는 걸 체감할 수 있었습니다. <form>기반 액션이 큰 것 같아요. 캐시 같은 부분도 Next.js는 블랙박스처럼 느껴지는 부분들이 있는데 Remix는 그런 게 적었구요. 아주 많은 사용자가 이용하는 서비스라면 API 서버를 분리하는 등(ChatGPT처럼) 다양한 전략이 있을 수는 있겠네요.

Farming Paper는 소규모의 웹앱이고 거기에 맞춰 Remix로 기능을 빠르고 편리하게 뽑아낼 수 있었습니다. 그와 관련한 자세한 내용은 “Remix로 웹서비스 만들기 (추가 예정)”를 참고 바랍니다.

Supabase

Supabase 화면

백엔드는 Supabase를 선택했습니다. Supabase란 간편하게 백엔드를 구성할 수 있는 Baas로서 DB, 스토리지, Auth(로그인) 기능 등이 있습니다.

Supabase 에서 주구장창 강조하는 RLS(Row Level Security)는 거의 사용하지 않았습니다. RLS란 권한관리를 Row Level, 즉 하나의 테이블에서 누가 요청을 주냐에 따라 접근할 수 있는 Row를 다르게 정의할 수 있다는 개념입니다. 근데 좀 거부감이 듭니다. 클라이언트에서 엔드포인트로 직접 요청을 줄 때 권한을 Supabase 측에서 체크한다는 건데, 왠지 불안합니다. DB 커넥션을 클라이언트에서 직접 한다는 느낌도 영 좋지 못합니다. 권한과 관련된 로직을 Policy로 Supabase 내부에서 관리해야 한다는 점도 불편합니다. 그래서 모든 데이터 접근은 서버를 통해 Remix loader로 통해서 내려주도록 했습니다.

Supabase에서 자체적으로 제공하는 자바스크립트 클라이언트는 Auth 관련 기능밖에 사용하지 않았어요. DB 접근을 할 때도 ORM으로서 안되는 기능들도 많고, 이런저런 버그들을 겪어서 안정화가 덜 되었다는 생각이 들었습니다. 기능이 훨씬 풍부하고 안정화도 되어있고 DB 마이그레이션 등등도 더 편리하여 DX 측면에서 더 우수한 Prisma를 선택했습니다. 개발/스테이징 환경과 운영 환경의 Supabase를 다르게 설정해놨는데, CI/CD 과정에서 자동화 하는 것도 Prisma가 미묘하게 더 편했습니다.

Edge Functions 또한 쓸 일이 없었습니다. 성능적인 이점이 필요한 부분은 크게 없었습니다.

Auth를 자체적으로 지원하다보니 구글 로그인 등의 구현은 어렵지 않았지만 세션 정보를 Remix와 잘 버무려서 관리를 해야 하는데, 그 부분은 구체적인 이해가 필요해서 좀 어려웠어요. 아직 Auth0를 써본 적은 없는데, 한번 써보고 더 편하다고 하면 Auth 관련된 것도 이걸로 갈아타려고 합니다.

약간 단점만 적어놓은 것 같지만 Supabase는 무료 요금제여도 어느정도 사용할 수 있는 여유가 있는 게 가장 큰 이점입니다. DB 인스턴스는 보통 다른 데서 아무리 저렴하게 띄워도 한 달에 50달러 정도는 나가니깐요. 부담없이 시작할 수 있다는 건 굉장하죠.

Vercel

Vercel 화면

배포는 간단하게 Vercel로 했습니다. Vercel이란 Netlify처럼 웹서비스를 쉽게 배포해주는 서비스입니다. 보통 Next.js와 궁합이 좋지만 배포할 수 있는 웹서비스 기술은 다양합니다. Remix도 포함되어 있구요. MAU가 3,000명 안넘을 것 같다면 막 써도 괜찮을 거 같아요. 배포가 편하니까 부담이 없네요.

Vercel이 쓰다보면 헤어나오지 못하는 느낌이 있습니다. 프로젝트 성장에 야망이 있으시다면 배포만 딱 Vercel로 하고 언제든지 떠날 수 있도록 나머지 Analytics나 Insight 등을 따로 측정할 수 있는 방법을 마련해두면 좋을 것 같습니다. 전 직접 겪은 적은 없지만 서비스가 좀만 커져도 비용이 쭉쭉 늘어난다고 하더라구요.

업무 관리

GitHub: https://github.com/farming-paper/farming-paper

GitHub 커밋 내역

고도화란 끝이 없습니다. 지금의 모습도 사실 이리저리 많이 바뀐 결과물인데요, 적당히 하기 위해서 나 자신을 통제해야 했습니다. 안그래도 공부할 시간도 없는데 이거 만진다고 시간을 다 허비한다면 주객전도가 되는 상황이니깐요. 시험 공부는 대충 기말고사에 맞춰서 1달 정도를 잡았고, 그 전에 불편했던 부분들을 최대한 개선하려고 했던 것 같아요.

고도화하고 싶은 것들을 우선 쫙 GitHub Issues로 리스트업한 다음 거기서 우선순위를 매겨서 진행하곤 했습니다. 하는 김에(라는 말이 제일 무섭긴 하지만) 글로벌 서비스도 해볼까? 가볍게 생각해본 적이 있어서 그냥 이슈와 커밋을 전부 영어로 하기도 했어요. 이제 ChatGPT가 있어서 대충 느낌적인 느낌으로 영어 써도 잘 고쳐주더라구요.

GitHub 이슈 내역

이슈 하나하나를 좀 쪼개어 썼는데, 혼자서 하는 프로젝트 치고 좀 오버하는 느낌이었을까요? 한두 달만 지나도 어떤 작업이 남았고 중요하다고 생각하던 부분들이 뭐였는지 깔끔하게 잊어버리는 제 자신을 몇 번 겪고 나니, 그냥 후임자에게 인수인계한다는 느낌으로 일들을 적었던 것 같아요. 실제로 개발을 재개할 때 도움이 많이 됐습니다.

후기

뿌듯하죠. 결국에 미션을 성공했으니까요. 사실 3년 조기졸업을 할 필요가 있던 건 아니었지만 스토리가 이쁘죠. “목표를 그렇게 잡았고, 목표를 달성하기 위해 이런저런 방법을 시도했고, 결국 성공했다” 라는 거니깐요. 그것도 회사를 다니면서! Farming Paper의 개발에만 몰두한 게 아니라 실제로도 잘 썼다는 것도 기분이 좋습니다. 2년 동안 2,500개 가량의 문제를 만들어 풀었습니다.

노력의 분배도 기가 막혔던 것이, 총 학점 130점 중 가장 마지막에 마지막에 혼자서 따로 결과 발표나는 3학점짜리 과목 하나가, B를 기준으로 그 이하로 받는다면 평균이 3.99… 로 떨어지고 초과라면 4.00… 였어요. 마이크로 턱걸이가 성공하냐 못하냐의 기로에 섰었죠. 그때만 생각하면 지금도 가슴이 벌렁벌렁하네요.

네. 그리고 전 다시는 학교를 다니지 않을 것 같습니다. 기말고사 시즌이 올 때마다 거의 우울증에 온 것처럼 힘들었어요. 숨이 턱 막히고 그랬습니다. 그런 구렁텅이로 다시는 스스로를 몰아넣지 않겠습니다.

사실 Farming Paper를 만들지 않아도, 그 미션에만 집중했어도 성공했을지 모릅니다. 그러나 특히 필요한 도구를 만들어나간다는 재미가 확실히 있었던 것 같아요. 이게 바로 개발자의 장점이죠. 필요한 걸 만들어서 갖다 쓸 수 있다는 것. Farming Paper를 개발하면서 저는 마치 “나는 자연인이다”에 등장하는 기인이 된 듯한 기분이었습니다. 팔릴 만한 물건을 만든 것도 아니고 그냥 살아가는 데 필요한 나만의 도구를 만들어나간다는 느낌. 개발과 관련 없는 지인들과 이야기를 나눌 때 요새 뭐하고 지내는지 질문을 받으면, 이딴 걸 만들고 있다 하면은, 자연인을 보는 듯한 반응이었습니다. 자기들도 한번 써보고 싶다곤 하지만 이미 세상에 도구들은 많이 나와있으니 굳이 이걸 계속해서 쓰진 않더군요. 제게만 꼭 맞는 도구라는 거죠.

아쉬운 점은 크게 없습니다. 아직 서비스 자체적으로 개선할 여지는 어마어마하게 많지만, 원하는 성과를 냈으니 대체로 만족하는 것 같아요. 나중에 또 이 Farming Paper를 쓸 일이 있다면 그때 다시 개발에 들어가지 않을까 싶네요.

즐거운 개발 하면서 삽시다. 다들 파이팅.