제품을 바닥부터 다시 만들게 된다면 꼭 지킬 것들

제품을 바닥부터 다시 만들게 된다면 꼭 지킬 것들

제품 만들기라는 후회 덩어리를 굴리며

두 가지의 개발이 있는 것 같다. 처음부터 쌓아올려야 하는 개발이 있고, 만들어진 바닥을 고치며 쌓아올려야 하는 개발이 있다. 회사에서는 후자의 일을 많이 하다보니, 급격하게 늘어나는 제품의 스케일(양)과 복잡도를 틀어막으면서도 ‘이렇게 하면 안되는구나’ 하는 참회가 늘어난다.

만약 내가 제품을 바닥부터 다시 만들게 된다면 무엇을 해야할까.

모든 날짜는 UTC+0으로 주고 받는다

글로벌 진출을 하거나 급격한 성장을 겪게 되면 여러 변경사항이 생긴다. 그중에서 여러 요구사항에 맞춰 다양한 데이터 스토리지와 클라우드를 쓴다는 점이 있는데, 놀랍게도 각 환경들에서 말하는 America/Los_Angeles 의 시간이 모두 다를 수 있다.

타임존 정의는 IANA에 의해 관리되며 DST(서머타임) 적용과 해제, 수도 이전 등 다양한 이유로 1년에도 여러 번 업데이트된다(ex. 2021c, 2022b). 하지만 DB든 클라우드든 소프트웨어니 타임존이 항상 동기화되지 않는다. 반면 프론트엔드는 에버그린 브라우저라면 타임존 동기화가 어느 정도 잘 된다. 두 개의 맞물리지 않는 환경에서 서로 타임존을 적용한 상태로 날짜를 처리하면… 프론트엔드가 백엔드와의 정합성을 위해 브라우저의 Intl 모듈을 버리고 커스텀 타임존을 구현하거나, 백엔드에서 들어오는 날짜의 타임존을 최대한 제거하거나, 혹은 둘 다 해야한다. 서로에게 매우 골치아픈 상황이다.

이를 일차적으로 해결하기 위해서는 초기 단계부터 프론트엔드와 백엔드 모두 날짜는 타임존 오프셋(ex. +0900)이 포함된 ISO 포맷으로 데이터를 주고 받는 것이 좋다.
하지만 만약 A 지역의 타임존에서 모인 데이터를 B 지역의 타임존으로 변경하는 확장성을 고려한다면, 저장 단계에서 원본 날짜와 함께 무조건 타임존을 제거한 UTC+0의 날짜로도 함께 저장하는 것이 좋다(Europe/London이 아니다. 런던도 타임존이 있다). 이를 통해 불필요한 연산과 복잡도를 줄일 수 있을 것이다. 이를 조금 더 빠르게 했다면 좋았을텐데.

word-break: keep-all을 섣불리 적용하지 않는다

word-break 는 문장의 줄바꿈을 어떻게 할 것인지 정하는 옵션이다. 처음에는 한국어, 영어만 제공했고 디자이너분들의 요청사항 중 ‘단어 기준’으로 줄바꿈이 되었으면 좋겠다는 요청이 많았다. 그래서 전역 CSS에 keep-all 속성을 적용했다. 하지만, 다국어 지원 중 일본어가 추가되며 문제가 생겼다.

일본어는 한국어/영어와 다른 지점이 있다. 예를 들어 마침표(。), 쉼표(、) 같은 문장 부호들이 다르며, 띄어쓰기가 없기에 모든 문장이 쭉 이어진다. 이러한 특성으로 인해 띄어쓰기 기준의 단어 판별 기준이 작동하지 않기에 문장이 영역 바깥으로 삐져나오게 된다. 이를 제어하기 위해 또 overflow-wrap: anywhere 같은 옵션을 추가할 수도 있지만 무슨 문제가 일어날지 알 수 없어 무서움은 있다.

가장 좋은 방법은 word-break 를 지정하지 않는 방법이 있을 것이다. 언어의 기본값을 따르게 만들어 별도의 처리 로직을 두지 않을 수 있다. 또다른 방법으로는 html lang 속성을 사용하여 html[lang="ko"] 와 같이 언어 별 스타일을 지정하는 것도 방법일 것이다.
하지만 이마저도 RTL, LTR 같이 읽는 방향이 바뀌는 언어들까지 대응하는 것은 아니다. 서비스가 중동 지역까지 진출해 다국어를 지원한다면…? 가슴이 여러 의미로 두근거린다.

일관적인 쿼리 파라미터 형식을 만들어 기능 간의 연결성을 확보한다

여러 해 동안 제품을 만들며 느끼는 점 중 하나는, 제품 내의 기능들이 얼마나 유기적으로 연결되어있는지가 완성도를 보여준다는 것이다. 그런 생각으로 여러 기능을 모두 모아 쉽게 접근할 수 있는 커맨드 팔래트를 만들었다. 커맨드 팔레트에서 아이템을 선택하면 해당 기능으로 이동하고, 상태를 업데이트해, 특정 기능을 활성화시켜야한다.

이 작업을 하면서 URL, 특히 쿼리 파라미터의 중요성을 깨달은 적이 있다. 전역 레이아웃 상태를 일일이 수정하지 않아도 되고, 주소를 복사하여 동일한 화면과 동작을 공유할 수 있기에 강력하다.
하지만 너무 급하게 만들다보니 체계화를 하지 못했다. 예를 들어 모달을 여는 액션의 쿼리 파라미터는 isModalOpen=true 라고 짓지 않고 action=invite 와 같이 나타냈다면 더 좋지 않았을까.

액션(action), 파라미터(params) 등의 데이터, UI 상태(ui) 등으로 잘 구조화해 쿼리 파라미터에 표현할 수 있다면 유기적인 흐름을 더 만들 수 있지 않을까.

기능 단위별 독립된 상태 관리를 추구한다

제품이 작을 때에는 거대한 단일 상태 스토어도 잘게 쪼개진 상태 스토어도 괜찮은 느낌이 든다. 앞으로 무엇이 어떻게 될지는 가봐야 아는 것이고, 상태들이 어느 정도 제어 범위 내에 있기 때문이다.

지금의 제품이 처음 만들 때에는 사실 선택지가 별로 없었던 것 같다. React Context도 정식 API가 없었을 때고, Redux 외에는 별도의 전역 상태 관리의 사례도 없었던 것으로 파악된다. 하지만, 약 6년이 지나 제품이 적어도 10배로 성장하며 고민이 생겼다. 사용자들은 하나의 기능을 쓴 뒤에 데이터가 남아있길 바라지 않았고, 데이터의 양도 조금 크다보니 전역에 데이터를 들고 있다는 것만으로 불필요한 저장소를 쓰는 건 아닐까, 이로 인해 성능이 느려지는 것은 아닐까 하는 생각에서 벗어날 수 없었다.

이를 해결하기 위해 지금은 점진적으로 Atomic한 상태 관리 방법들을 적용하고 있지만, 이마저도 상태의 적용 범위(Boundary)를 명확하게 가져가지 못해 불필요한 reset() 동작을 하고 있다.

만약 상태 관리 구조를 재설계한다면 기능의 언마운트 시에 상태가 같이 날아가는 형태의 독립된 형태로 만들 것 같다. 예를 들어, Jotai에서는 Provider에 Store를 지정함으로서 화면의 언마운트 시에 상태를 날릴 수 있다.


그외에도 모노레포, 마이크로 프론트엔드를 성급하게 적용하지 않음으로서 인프라의 간결성을 확보한다는 점, Skew Protection을 고려해 넥서스를 구축하는 것 등 여러 생각이 든다.

지난 시간 동안 생각은 많이 쌓았지만 시간이 없다며 시도를 많이 못한 것들이 많다. 일주일에 못해도 3시간은 이러한 ‘바닥 고치기’에 시간을 써보려고 한다. 더는 돌아오지 못할 강을 건너 부채가 파산하기 전에.