개인 블로그 관리 시스템 뼈대를 처음부터 직접 설계해 띄운 이야기
목차
새 프로젝트를 처음부터 차려 올렸다. 개인 블로그 운영용 관리 시스템 - 거창할 것 없는 규모지만, 막상 첫 커밋까지 오는 데 생각보다 손을 많이 탔다. "어차피 나만 쓸 건데"라고 대충 시작하다 며칠 뒤 스파게티 코드가 된 경험이 있어서, 이번엔 뼈대부터 제대로 다지기로 했다.
프로젝트 초기화의 작은 신중함
initial commit이라고 해서 아무 파일이나 던지는 건 아니다. 1인 개발이고 당장 나만 쓸 프로젝트라도, 나중에 코드를 공개하거나 같이 손댈 사람이 생길 수 있다. 그 가능성이 1%여도 처음부터 정리해두는 게 낫다. 귀찮아서 안 한 게 나중에 기술 부채로 돌아오는 건 한 번 경험해봤으면 안다.
그래서 .env.example과 .gitignore를 제일 먼저 준비했다.
.env.example은 실제 값이 들어있지 않다. 어떤 키 이름이 필요한지 알려주는 문서 역할이다. 팀 온보딩에서 "이 환경변수는 뭐예요?"라는 질문이 안 나오게 해주는, 거의 공짜 문서화다. 몇 달 뒤의 나 자신도 낯선 사람이나 다름없으니, 그냥 쓰는 셈이다.
# .env.example
SECRET_KEY=your-secret-key-here
DATABASE_URL=sqlite:///blog.db
ADMIN_PASSWORD=change-this
실제 .env는 당연히 .gitignore에 박아두었다. DB 비밀번호나 서명 토큰 같은 게 커밋 히스토리에 한 번 들어가면, 레포를 private으로 돌린다고 해도 안심하기 어렵다. git filter-repo로 히스토리를 통째로 재작성하는 사태를 피하려면 처음부터 차단하는 게 맞다. .gitignore에서 빠뜨리기 쉬운 항목도 같이 정리해뒀다.
__pycache__/,*.pyc- Python 바이트코드, 자동 생성됨.env,*.local- 환경 변수 파일instance/- Flask 인스턴스 폴더, SQLite db 파일이 여기 들어감.DS_Store- macOS에서 개발한다면 빠뜨리지 말 것
이 네 줄 챙기는 데 2분도 안 걸린다. 안 챙기면 나중에 훨씬 더 걸린다.
백엔드와 프론트엔드 구조 설계
app/main.py를 진입점으로 두고, app/ 디렉토리 아래로 모듈을 모으는 구조를 잡았다. 플라스크 기준으로 블로그 운영에 필요한 기능은 크게 두 가지다 - 포스트 CRUD와 관리자 인증. 그 이상은 당장 필요 없다.
| 파일/디렉토리 | 역할 |
|---|---|
app/main.py |
앱 팩토리, 라우팅 진입점 |
app/templates/ |
Jinja2 HTML 템플릿 |
app/static/style.css |
전역 스타일시트 |
.env.example |
환경변수 명세 문서 |
.gitignore |
버전 관리 제외 목록 |
초기 구조를 이렇게 잡아두면 나중에 app/models.py, app/routes/ 같은 모듈을 추가할 때 자연스럽게 붙는다. 반대로 처음에 main.py 하나에 다 쑤셔 넣으면, 라우트가 10개 넘어갈 때 분리하는 작업이 생각보다 피곤하다. 어떤 함수가 뭘 참조하는지 일일이 추적하면서 파일을 쪼개야 한다. 시작할 때 30분 투자하면 나중에 몇 시간을 번다.
프론트엔드는 CSS 최소화로 시작했다. 개인 블로그 관리 툴이라 화려할 필요가 없고, 처음부터 Tailwind나 Bootstrap 같은 걸 셋업하면 정작 기능 만들 시간이 줄어든다. 나중에 언제든 붙일 수 있다. 초기엔 기능 완성이 우선이다. 디자인은 기능이 다 돌아간 다음에 손봐도 늦지 않는다.
관리자 페이지 분리와 initial commit이 담는 것
admin_dashboard.html과 admin_post_form.html을 처음부터 템플릿으로 준비했다. 혼자 쓸 블로그인데 이렇게까지 나눌 필요가 있냐 싶겠지만, 이유가 있다.
포스트 목록을 보는 화면과 포스트를 작성하는 화면은 역할이 다르다. 하나의 템플릿에 조건부로 다 욱여넣으면 HTML 자체가 복잡해지고, 수정할 때 어디를 건드려야 할지 한눈에 안 들어오게 된다. 거기다 라우트 구조가 자연스럽게 따라온다는 장점도 있다. /admin/ → 대시보드, /admin/post/new → 작성 폼. 이 구조를 머릿속에 그려두고 템플릿을 나눠두면, 라우트 구현할 때 흐름이 깔끔하다.
# 염두에 둔 대략적인 구조
@app.route('/admin/')
def admin_dashboard():
posts = Post.query.order_by(Post.created_at.desc()).all()
return render_template('admin_dashboard.html', posts=posts)
@app.route('/admin/post/new', methods=['GET', 'POST'])
def admin_post_new():
if request.method == 'POST':
# 저장 로직
pass
return render_template('admin_post_form.html')
나중에 관리자 로그인 기능을 추가할 때도 편하다. /admin/ 하위 라우트 전체에 @login_required 데코레이터 하나로 묶을 수 있다. 섞여 있으면 하나씩 붙여야 한다. 초기 설계에서 관심사를 분리해두면, 이런 확장이 자연스럽게 따라온다.
스캐폴딩 도구를 쓰면 10초 만에 파일 구조가 생긴다. 근데 그렇게 생성된 구조는 범용적이라서 이 프로젝트가 뭘 하려는지 의도가 담기지 않는다. 손으로 차린 initial commit은 다르다. 어떤 파일이 먼저 필요한지, 어디에 뭘 두기로 결정했는지가 그 자체로 설계 의도다. 나중에 코드를 보는 사람 - 팀원이든 미래의 나든 - 이 "왜 이렇게 나눠놨지?"를 파악하는 데 훨씬 빠르다.
개인 프로젝트일수록 이 규율이 더 필요하다고 느낀다. 팀 프로젝트면 코드 리뷰나 컨벤션 강제가 어느 정도 구조를 잡아주는데, 혼자 하면 그런 외부 압력이 없다. 흐지부지 시작하면 빠르게 무너진다. 처음 30분을 더 투자해서 skeleton을 다져놓으니 지금 기분이 훨씬 낫다.
다음은 main.py에 실제 라우트를 구현하고 SQLite를 붙이는 단계다. 템플릿들도 기능이 들어갈수록 살이 붙을 것 같다. 규모는 작지만 의도가 담긴 시작이다.
댓글 0
첫 댓글 달아줘.