Here's my dents in the universe

백엔드 설계, 무엇부터 시작할까?


지난 1월, 신규 프로젝트 팀의 첫번째 백엔드 엔지니어로 입사했다. 합류하는 팀에서 가까운 미래에 ‘NFT가 포함된 게임’을 개발할 것이라는 이야기만 들은 채로, 순전히 사람만 보고 이직을 선택했다. 게임 기획자로 경력을 쌓아오신 팀 리더분께 배울 점이 많을 것이라고 기대했기 때문이다(실제로도 많이 배우고 있다.)

프로젝트의 방향성을 정하는 데에만 한 세월이 걸렸다. 우리가 내놓을 프로덕트는 어떤 유저를 타게팅한 것인지, 기존 시장에는 우리와 비슷한 프로덕트를 가진 어떤 플레이어가 있고 우리는 그들과 어떻게 같고 어떻게 다른지. 그렇게 몇 달 간의 치열한 토론을 거친 끝에야 팀의 방향성이 정해졌다.

로드맵 상 개발팀이 만들 첫번째 프로덕트는 ‘유저가 NFT를 구매할 수 있는 플랫폼’을 만드는 것이었다. 아티스트와 UI/UX 디자이너, 게임 기획자 분들이 각자 직군에 맡는 역할을 진행하기 시작했다. 나도 이제 개발에 돌입하기만 하면 되었다. 그런데 아뿔싸, 어디에서부터 시작해야할지 감이 오질 않았다.

‘데이터베이스부터 설계하면 되는걸까?’, ‘아니, API를 먼저 설계하고, 그에 따라서 테이블을 나눠야 하나?’, ‘그럼 어떤 API를 만들 것인지는 어떻게 정하지? 시퀀스 다이어그램을 그려야하나?’ 머리가 복잡했다. 무언가 꼭 필요한 것이 정해지지 않았다는 생각이 들었지만, 그게 무엇인지는 알 수가 없었다. 어느 정도 설계가 진행되고나면 괜찮아질 것이라는 믿음으로 설계를 시작했다.

DB first!

데이터베이스를 먼저 설계하기로 했다. 우리가 만들어야할 프로덕트에 필요해보이는 테이블을 죽 나열하고, 그들간의 관계를 정의하기로 했다. 웹사이트에 방문하는 유저를 등록하고 관리하는 user 테이블이 필요할 것이고, 웹사이트에서 판매할 NFT에 대한 정보가 저장되는  nft_collectible 테이블이 필요할 것이었다. 그리고 유저 간 NFT 거래 내역을 쉽게 추적할 수 있도록 transaction 테이블을 만든다면 편리할 것이다.

‘실수로 빼먹은 테이블은 없을까? 개발하다보면 떠오르겠지!’ 테이블에 대한 구분은 이정도로 하고, 이제 테이블 간 관계를 정의하기로 했다. ’user가 NFT를 2개 이상 소유할 수 있으니 nft_collectible과 1:N 관계를 맺겠지? 그리고 transaction에는 유저와 NFT가 모두 관여하니 user, nft_collectible 테이블의 id 값을 외래키로 가지게 될거야.’ 각 테이블 간 관계를 정리하며 필드를 조금씩 추가하고 정리해보니, 괜찮아보이는 ERD 하나가 완성되어 있었다.

문제는 이후에 발생했다. 만들어놓은 테이블에 맞추어 API를 만들어내다보니, 테이블에 대한 기본적인 연산(CRUD, Create, Read, Update, Delete) 이상의 API가 만들어지지 않았다. ‘서비스가 다루어야 하는 로직은 CRUD 범위를 넘어서는데, DB를 먼저 설계하는 것은 선후 관계가 잘못되었다. API를 설계하고, 그에 맞춰서 테이블을 설계하는 것이 옳겠다.’

API first!

이번에는 API를 먼저 만들어보기 시작했다. DB보다는 서비스 로직에 가까운 API를 먼저 만들고, API를 통해 주고 받는 정보들을 DB에 저장하는 식이라면 조금 더 괜찮은 설계가 가능할 것이라 믿었다. ‘어떤 API들이 필요할까?’ 전 직장의 레거시 코드를 떠올리며, 하나씩 API 명세를 작성해나갔다. ‘유저가 처음 회원가입할 때 호출하는 POST auth/signup 이 필요하겠지? Body에는 유저의 가입 시기와 닉네임을 담아서 보내주면 될 것이고, JWT 토큰을 response로 반환해야겠다. POST auth/refresh-token와 같은 API도 필요하지 않을까?’

이 방식 역시 금세 한계를 드러냈다. 각 API를 통해 클라이언트와 서버가 주고 받아야 하는 정보를 정의해야 하는데 언제, 어떤 정보가, 어떤 방식으로, 어떤 순서로, 어떤 주체들 간 주고 받아지는지 알기 어려웠다. 클라이언트와 서버가, 그리고 그 이외의 주체가 요청과 응답을 주고 받는 시퀀스 다이어그램이 필요했다.

Sequence diagram first!

클라이언트와 서버, 그리고 이더리움과의 통신을 담당하는 모듈로 주체를 나누어 정보를 주고 받는 sequence diagram을 그리기 시작했다. 유저가 회원 가입을 진행하고, 토큰을 발급/갱신하는 과정, NFT를 구매하고, 판매하는 과정이 시퀀스 다이어그램으로 표현되었다. 그 과정에서 발생하는 HTTP req, res에 어떤 값들이 담겨있어야 하는지 표현했고, 나름의 만족스러운 결과물이 탄생했다. 이제 이 sequence diagram으로부터 API를 도출해내고, 이를 보조하는 테이블을 설계하면 될 것이었다.

그런데 여전히 만족스럽지 않은 부분이 있었다. 시퀀스 다이어그램이 비즈니스의 요구사항을 완전히 커버하지 않았다. 즉, 비즈니스의 문제가 완전히 해결되고 있지 않았다. UI 디자이너 논의할 때마다 시퀀스가 조금씩 추가되거나 수정되었고, 이 방식에도 무언가 문제가 있음이 직감적으로 느껴졌다.

Finally, User action first!

비즈니스 요구사항이 해결되지 않는다는 문제가 반복적으로 발생하자, 스크럼 중 누군가 제안했다. ‘우리 유저 journey map을 그려봅시다. 그리고 그에 맞춰서 다시 설계합시다.’ 우리는 유저가 우리 서비스에 입장하는 시작점부터, 나가는 시점까지의 행동을 리스트 업해보았다. 결과는 이전 접근과 완전히 달랐다. ‘유저는 첫 화면에서 회원가입 버튼을 보고 싶을까? 어쩌면 회원가입 기능조차 필요없는 것은 아닐까?’, ‘지금 이 부분은 아직 기획도, 개발도 커버하지 못하고 있었네. 이 부분은 어떻게 커버해야하지?’

막연히 ‘있어야 할 것’이라고 생각했던 기능들이 실은 필요없거나, 개발이 아닌 기획으로 해결될 수 있었고, 필요한 줄도 몰랐던 기능이 실은 필요했다는 것을 알게 됐다. 결국 돌고 돌아 우리는 이 결론에 도달했다. ‘우리의 모든 설계는 유저의 행동으로부터 출발했어야 했다.’

유저의 행동으로부터 시작하니 모든 것이 자연스러워졌다. 먼저 유저가 할 수 있는/하기를 원하는 행동을 리스트업하고, 각 행동에 대한 sequence diagram을 그린다. 유저와 서비스가 주고받는 정보를 통해 DB 테이블과 API를 도출해낸다. 비즈니스가 요구하는 모든 사항을 커버하는 서버가 만들어졌다.

망치를 들면, 모든 것이 못으로 보인다.

서버 설계는, 아니 기획/디자인/UIUX 등 모든 설계는 결국 유저의 행동으로부터 시작해야한다. 개발과 기획, 디자인, UI/UX는 모두 유저의 행동을 넛지하는 보조 수단일 뿐이었다. ‘망치를 들면 모든 것이 못으로 보인다’는 유명한 말처럼, 나 역시 개발의 관점에서만 비즈니스를 바라보고 있던 것이다.

문득 우아한형제들 CEO 분께서 하신 말씀이 떠올랐다. ‘엘리베이터의 속도가 느리다는 민원이 들어왔다면, 엔지니어링으로 엘리베이터의 속도를 높여서 해결하는 것도 방법이지만, 내부에 거울을 설치하는 것도 방법이 될 수 있다. 개발이 모든 문제의 해결책이 아닐 수 있다’

영상을 보던 당시에는 제가 이 말을 이해했다고 생각했다. 하지만 머리로 이해하는 것과 실천하는 것은 완전히 다른 문제였다. 이제서야 조금 더 자신있게, 그 말을 약간이나마 이해하게 되었다고 말할 수 있을 것 같다.