hswork.win

혼자 만들기로 했으니, 만들지 않을 것부터 정했다

2026-09-09 · 설계

조직이라는 개념을 두지 않았다

조직이 있고 그 아래 강사가 있고 강사 아래 수강생이 있는 형태. 학습 시스템을 설계하면 거의 반사적으로 이 구조가 나옵니다. 기관용 시스템에서는 이게 맞습니다. 한 학교에 교사가 수십 명이고 소속과 권한이 나뉘기 때문입니다.

그런데 제가 잡은 사용자는 혼자 가르치는 강사와 두세 명이 일하는 공부방입니다. 여기에 조직 계층을 넣으면 가입할 때부터 "조직을 먼저 만드세요"가 나오고 조직이 하나뿐인 사람에게 그 화면은 그냥 귀찮은 단계입니다. 권한 검사도 두 배로 늘어납니다.

그래서 조직을 없애고 사용자 계정 하나를 구독의 주체로 뒀습니다. 모든 사용자는 기본적으로 수강생이고 강좌를 하나 만들면 그 강좌에 한해 강사가 됩니다. 별도의 가입 절차도 역할 선택 화면도 없습니다.

나중에 "여러 강사가 한 학원에 소속" 요구가 실제로 생기면 그때 조직을 추가하기로 하고 문서에 확장 경로만 한 줄 적어 뒀습니다. 쓰지 않는 계층은 유지비만 내는 자산이라 미리 만들어 두지는 않습니다.

관리자 페이지를 딱 하나만 만들었다

처음 계획은 관리자 페이지를 아예 안 만드는 것이었습니다. 운영자가 볼 화면이 생기면 거기에 사용자 조회가 붙습니다. 그다음은 콘텐츠 관리, 그다음은 통계입니다. 한 번 열어 주면 계속 늘어납니다.

결국 하나는 만들었습니다. 환불 처리 화면입니다. 규정상 일부 환불은 사람이 판단해서 실행해야 하는데, 그걸 데이터베이스 접속해서 직접 처리하는 건 사고가 나기 좋은 방식이라 화면을 뒀습니다. 대신 그 화면에 다른 기능을 얹지 않기로 하고 문서에 "유일한 예외"라고 이유까지 적어 뒀습니다. 이유를 적어 두면 나중에 두 번째 예외를 만들려 할 때 스스로 걸립니다.

운영에 필요한 숫자는 화면 대신 메일로 받습니다. 화면을 하나 더 만들면 그것도 관리 대상이 되지만 메일은 읽고 지우면 끝입니다. 주 1회 지표가 오면 그걸로 충분했습니다.

안 쓸 최적화를 미리 하지 않기

예상 규모를 먼저 적고 시작했습니다. 강사 수십에서 수백 명, 동시 접속 수십 명. 그리고 이 규모를 넘어서는 최적화는 하지 않는다고 못 박았습니다. 캐시 계층도, 작업 큐 워커도, 데이터 분산도 넣지 않습니다.

이 줄이 실제로 여러 번 제 발목을 붙잡아 줬습니다. 개발하다 보면 "나중에 트래픽 늘면 여기가 병목일 텐데" 하는 생각이 자꾸 듭니다. 그 생각이 들 때마다 문서를 다시 읽었습니다. 오지 않을 트래픽을 위해 만든 구조는 성능을 올려 주는 게 아니라 버그가 숨을 곳을 늘려 줍니다.

용어를 먼저 정한 것이 의외로 컸다

코드를 쓰기 전에 용어집부터 만들었습니다. 한글 용어와 코드 식별자를 일대일로 묶은 표입니다.

초기 설계에서 이름 충돌이 하나 나왔기 때문입니다. 강의 한 회차를 뜻하는 단어로 흔히 쓰는 말이 로그인 세션과 코드상 이름이 겹쳤습니다. 둘 다 흔한 단어라서 그대로 두면 "세션이 만료됐다"가 로그인이 풀린 건지 수강 회차가 끝난 건지 알 수 없는 문장이 됩니다. 실제로 이런 이름은 몇 달 뒤 본인이 쓴 코드를 다시 읽을 때 가장 크게 물립니다.

그래서 어느 쪽이 그 이름을 쓸지 표에 박아 두고 다른 쪽은 처음부터 다른 단어를 쓰게 했습니다. 화면 문구, 데이터베이스 컬럼, 에러 메시지까지 같은 단어를 쓰게 되니 나중에 검색 한 번으로 관련된 코드를 다 찾을 수 있게 됐습니다.

강좌·차시·영상·평가의 계층 구조
강좌·차시·영상·평가의 계층 구조

학습 기록을 어디에 매달 것인가

콘텐츠는 재사용할 수 있어야 합니다. 작년에 만든 영상을 올해 강좌에 그대로 넣고, 같은 회차를 초급반과 심화반에 함께 쓸 수 있어야 합니다. 그러려면 영상과 회차는 특정 강좌에 묶이지 않는 독립적인 라이브러리여야 합니다.

문제는 진도입니다. 설계에서 가장 오래 고민한 지점이 여기였습니다. 진도를 영상에 직접 매달면, 같은 영상을 쓰는 두 강좌의 진도가 하나로 섞입니다. 초급반에서 본 영상 때문에 심화반 진도가 올라가 버립니다. 실제로 이건 이런 구조에서 가장 저지르기 쉬운 실수라, 문서에 굵게 적어 뒀습니다.

그래서 규칙을 하나로 통일했습니다. 콘텐츠는 라이브러리에 두고 학습 기록은 전부 "누가 어느 강좌를 듣고 있는가"라는 수강 단위에 매답니다. 진도든 점수든 이수 여부든 예외 없이 그쪽에 붙습니다. 이 규칙 하나 덕분에 나중에 강좌를 복제하거나 기수를 새로 여는 기능을 붙일 때 고민할 게 거의 없었습니다.

진도를 영상에 매다는 경우와 수강에 매다는 경우
진도를 영상에 매다는 경우와 수강에 매다는 경우

요금제 제한은 한 파일에만

유료 플랜과 무료 플랜을 나누는 서비스에서 흔히 새는 자리가 기능 제한입니다. 이 화면에서는 막았는데 저 화면에서는 안 막혀 있고 화면은 막혔는데 API를 직접 부르면 통과하는 식입니다.

제한을 코드 곳곳에 흩어 두면 반드시 구멍이 납니다. 그래서 어떤 플랜이 무엇을 쓸 수 있는지를 정의하는 파일을 딱 하나 두고, 나머지 코드는 전부 그 파일에 물어보게 했습니다. 기능이 늘어도 고칠 곳은 그 파일 하나입니다.

그리고 검사는 반드시 서버에서 합니다. 프런트가 버튼을 감추는 건 화면을 정돈하는 일입니다. 버튼이 없어도 요청은 보낼 수 있습니다.

코드를 쓰기 전에 쓴 며칠

여기까지가 코드를 거의 쓰지 않고 정한 것들입니다. 시간으로 치면 며칠, 문서로 치면 몇 쪽입니다.

그 며칠이 아까울 때도 있었습니다. 빨리 화면 하나라도 띄우고 싶었습니다. 그런데 만들면서 판단이 필요한 순간이 올 때마다 문서를 펴고 "그때 이렇게 정했지"를 확인하고 넘어갈 수 있었던 게 훨씬 컸습니다. 혼자 하는 프로젝트에서 제일 비싼 시간은 코드를 쓰는 시간이 아니었습니다. 같은 고민을 두 번 세 번 다시 하는 시간이었습니다.

← 개발 기록 목록