왼손잡이해방연대 아지트

사용자 확인중...

아지트 개발일지 1

왼손잡이해방연대,

25

2

개발 일지를 써볼까~

오늘은 커밋을 6개 올렸다.

Handle Error Properly

tryLogin 처리 중 발생하는 오류를 잘 처리하도록 수정하였다. 일단 tryLogin이 뭔지 설명해야겠지...

아지트에서 "네이버로 로그인"을 클릭해서 네이버 쪽에서 로그인이 되면, 아지트에는 /login/naver/callback으로 리다이렉트된다. 이 주소에 이렇게 네이버를 거쳐서 정상적으로 접근한다면 딱 한 번 접근권(access token)을 받을 수 있는 코드(authorization code)가 주소에 쿼리로 전달된다. 아지트의 클라이언트 코드에서는 Next.js 서버 액션으로 tryLogin(code)를 호출한다. tryLogin은 네이버 계정의 아이디를 알아내 그 아이디로 등록된 사용자가 있으면 로그인하고, 없으면 사용자 등록을 위한 코드를 만들어 리턴한다. 그렇게 동작하는 tryLogin의 타입은 다음과 같다:

type tryLoginResult =
  | Register({code: string, naverName: string})
  | Login(loginResult)
let tryLogin: string => promise<tryLoginResult>

이때의 문제는 사용자가 일부러 또는 실수로 콜백 주소를 잘못된 코드로 접근했을 때 블로그가 뻗어버린다는 것이다. 그건 서버 내부에서 수정해야할 에러가 아니라 사용자의 잘못이고 예측가능하다. 따라서 다음과 수정한다:

type tryLoginSuccess =
  | Register({code: string, naverName: string})
  | Login(loginResult)
type tryLoginError =
  | Unauthorized
let tryLogin: string => promise<result<tryLoginSuccess, tryLoginError>>

정확히는, 네이버에 접근권을 요청했을 때 응답 데이터를 까봤더니 접근권이 없거나, 접근권을 잘 받았는데 그 접근권으로 네이버 아이디를 요청했는데 이번엔 응답 데이터에 아이디가 들어있지 않을 때, 그런 때에 Unauthorized로 리턴한다. 그렇게 리턴해도 프론트엔드에서는 에러메시지를 보여줄 뿐이지만... 아지트 자체가 뻗어버리는 것보다는 낫다.

이걸 고치면서 예외처리에 대한 철학이 섰다. 예상가능한 예외만 처리하자! 예를 들어 코끼리 SQL 서버가 응답을 안하는 일은 있어서는 안 되고, 그런 일이 일어난다고 해도 아지트에서 할 수 있는 일은 없다. 따라서 그건 굳이 result 써가면서 고생한 필요 없이 예외로 던져버리면 그만이다. 데이터베이스에 물어보는 함수는 이렇게 바뀐다:

let query = async (f, param) => {
   try {
     let result = await client->f(param)
     await client->release
     // 기존 코드: result->Ok
     result
   } catch {
   | e => {
       await client->release
       // 기존 코드: e->Error
       raise(e)
     }
   }
 }

OkErr니 해서 result를 만들지 않고 에러로 던져버린다. 야호! 사실 회원가입하거나 할 때 이름 중복 체크를 데이터베이스에 맡기기 때문에 예외를 까봐야할 때가 없지는 않지만, 그거야 말로 예외처리의 예외 상황이니 매번 오류를 무시하는 코드를 귀찮게 넣기보다는 예외를 까보는 코드를 귀찮게 쓰는 게 맞다.

Create Actions.resi

Actions.res'use server'로 시작하는, 서버 액션을 모아놓은 '서버 모듈'이다. Next.js의 파격적인 행보랄까, 한 모듈에서 내보내는(export) 모든 함수를 API 엔드포인트처럼 노출하겠다, 그런 이야기다. 덕분에 편해지긴 하지만 이것이 Rescript와 결합하여 좋지 않은 효과를 연출한다. Rescript 모듈은 기본적으로 모든 함수와 선언을 내보내도록 컴파일된다. 그 말은 예를 들어 사용자 정보만을 받아 바로 접근권을 만들어 리턴하는 login: userProfile => promise<loginResult> 같은 함수를 전역에서 정의할 수 없다는 뜻이다. 보안과 코드 재사용성 사이에서 유쾌하지 못한 줄타기를 해야한다. 그 지점에서 이제는 미뤄왔던 일을 해야할 때가 왔음을 알았다. 바로 문서화...! 문서화라고 하기도 뭣하지만 "돌아가게만" 하는 것 이상의 코드를 써야 한다는 점이 한발짝 나아가는 일이라고 볼 수 있다. 잔뜩 의미부여하고 있지만 지금까지도 돌아가게만 짜지는 않았었다. 정말로는 그냥 안 쓰던 파일 확장자를 하나 쓰게 된 것 뿐이다. 어쨌건 그런 일이 하나의 커밋으로 남았다.

Rescript의 모듈 구현 파일은 .res 확장자를 쓴다. 일반적으로는 이 파일만 있어도 되지만 코드의 내장을 다 드러내기 싫을 때는 .resi 파일에 공개할 함수와 값의 타입만 적을 수 있다. 이름하야 Rescript 모듈 인터페이스 파일이다. 서버 모듈이라는 특수한 경우, 클라이언트가 내장을 마음대로 주무르게 할 수 없기 때문에 이 인터페이스 파일은 필수라고 할 수 있다.

사실 이 커밋에서는 Utils.res라는 파일도 만들고 디렉토리도 2개 만들어서 파일들을 정리했다. 커밋이란 게 다 그렇지 않은가. Utils.res에는 result와 option 타입을 async/await으로 쉽게 다루기 위한 함수들을 정의해놓았다. Rescript는 확실히 만들다만 언어라는 느낌이 드는게, 표준 라이브러리가 다른 함수형 언어에 비해 빈약하다. 다른 사람들이 만든 거라도 있으면 좋겠지만 쓰는 사람이 없으니... 내가 만들어쓰는 수 밖에 없다.

잡다한 커밋들

  • Add Logout: 로그아웃 기능을 추가했다. 별 일은 없었고 그냥 기존 함수를 그대로 옮겨왔다. 쿠키를 지우고, 재발급권(refresh token)을 데이터베이스에서 지우는 정도의 동작이다.
  • Add Responsive Menus: 왼쪽에 있는 메뉴를 추가하고 스크롤할 때 헤더가 들어갔다 나왔다 할 수 있게 했다. 예전에 타입스크립트로 개고생해서 써놓은 게 있어서 그대로 옮겼다. CSS를 Tailwind로 바꾸는 것도 꽤나 일이었다. 이거 유지보수 되는 건가? 하는 생각이 들지만 유지보수 안 되는 건 CSS 파일에 모아놔도 마찬가지이지 않을까. 이거 쓰면서도 결국은 브라우저 계속 띄워놓고 작업했으니 말이다.
  • Fix main X Overflow: 코드 너비에 따라 페이지가 양옆으로 마구 늘어나는 버그를 해결했다. 어떻게 해결했나를 알려면 CSS를 해독해야 하는데..., 되게 짧은 커밋이라 금방 해독했다. flex로 정렬된 자식 요소들은 min-width: 0을 먹여주지 않으면 flex가 관리를 포기한 금쪽이가 되어버린다. premin-width: 0 먹였다가 잘 안 되었었다. 이건 기억하고 있는게 아니라 커밋 내역에 나와있다. 그냥 둘까 하다가 나중에 해독할 일이 있을 수도 있으니 추가 커밋으로 제거. 아 이런 때는 amend를 했어야 했나? 이렇게 밑바닥에 깔린 커밋에도 어멘드가 되던가... 어멘드 자체를 많이 안 해봐서 모르겠다.
  • Change Misc Styles: 그냥 이것저것 했다. 모바일에서 메뉴 열 때 스르륵 색깔 변하는 걸 구현해보려다 관둔 흔적이 있다. 이 부분은 나중에 또 건들테니까 그냥 둬야지...

이상 말많은 개발일지 1일차를 마친다. 이런 식으로 써서는 2일차는 쓸 엄두가 안 날 것 같다. 이번엔 레포를 깃허브에 올리긴 했는데 공개할지는 고민중이다. 결국은 공개할 것 같지만 지금은 다른 할 일이 기한이 애매해서 미뤄놓고 몰래 하는 느낌이라... 당분간은 숨김 상태다.

  • 삼권분립의 현장. 각각 Next.js, Rescript, PgTyped 명령어다. 저장할 때마다 뭔가 다시 컴파일한다. 각자의 자리에서 최선을 다하길. 가끔 서로 말이 안 맞으면 말이 맞을 때까지 한놈씩 죽여본다. 사이좋게 지내렴^^
첨부파일


목록