Server Driven UI의 설계 철학에 대해

eaasurmind·2026년 2월 14일
post-thumbnail

Server Driven UI?

Server Driven UI에 대하여 이야기하기전에 Server Driven UI가 생소한 분들을 위해 어떤 개념인지 설명하고자합니다.

Server-Driven UI는 앱/웹의 UI를 서버에서 내려주는 JSON(or DSL) 기반 스키마로 정의하고,
클라이언트는 이 스키마를 해석하여 화면을 렌더링한다.

Airbnb를 필두로 많은 노코드 툴을 서비스하지 않는 기업들에서도 SDUI를 사용하고 있습니다.

과거 PHP등 서버에서 html을 내려주는 것 또한 SDUI라고 이야기 할 수 있지만
오늘 제가 이야기해보고 싶은 부분은 모바일이 등장하고 크로스플랫폼이 등장하고, 많은 기업들은 점점 빠른 주기로 배포하기를 원함에 따라
스토어 업데이트의 족쇄에서 벗어나 레고 블록을 자유롭게 조립하듯이 화면을 변경하는 기술에 대해서 입니다.

그렇다면 왜 굳이 규약을 정의하고 이를 JSON형태로 만들고 또 이를 클라이언트에서 해석해서 그리는 귀찮은 과정들을 할까요?

그 답은 아래와 같은 이점이 있기 때문입니다.

  • 앱을 업데이트하지 않아도 UI에 대한 다양한 실험을 진행할 수 있습니다.
  • A/B 테스트에 용이합니다.
  • 단순하고 반복적이고 많은 양의 페이지들을 찍어낼 수 있습니다.
  • 새로운 페이지를 만들기 위해 개발자가 필요하지 않습니다.

달콤한 이야기를 한가득했지만 꼭 좋은면만 있는 것은 아닙니다.

간단한 마케팅 페이지들은 아래와 같은 간단한 규약만으로도 마법이 펼쳐지지만, 문제는 더 많은 것들을 만들 때 벌어집니다.

{
  "type": "Card",
  "children": [
    { "type": "Text", "value": "Hello" },
    { "type": "Button", "title": "Click" }
  ]
}

최근에는 많은 회사들이 앞서 언급했던 이유들로 소급적으로 적용해보고 있는 듯한 느낌을 받았고

저는 SDUI를 switch / case문으로 컴포넌트를 랜더링하는 단순한 기술 이야기를 하고자 하는 것은 아닙니다.
노코드툴을 다년간 개발하면서 이점의 이면에 다양한 문제들과 팀내에서 철학을 세우는 과정에 대한 이야기를 하고자 합니다.

편하게 얘기를 나누기 위해서 페이지를 구성하는 가장 작은 단위의 SDUI 컴포넌트를 "블록"이라고 부르도록 하겠습니다.

블록을 나누는 기준

간단한 버튼이 있다고 가정해봅시다.
버튼을 JSON 형태로 표현하기 위해서 어떻게 해야할까요?

{
  "type": "button"
}

버튼을 좀 더 작은 최소 단위로 쪼갤 수 있을까요?

아래와 같은 방식도 생각할 수 있겠죠

{
  "type": "box",
  "border": ...,
  "padding": ...,
  "children": [
  		{
        	"type": text
            "content": "Button"
        }
	]
}

조금 짓궂은 예시이긴하지만 어떤 것이 정답일까요?

화면을 구성하는 사람 입장에서는 박스와 텍스트와 눌렀을 때 벌어지는 일을 매번 끼워넣는 것이 수고스러울 수 있습니다.

반면 박스에 onClick에 대해 정의해두면 무궁무진하게 확장할 수 있습니다.

박스안에는 이미지도 동영상도 다양한 콘텐츠도 넣을 수 있습니다.
원하는 유연하게 원하는 것을 만들 수도 있을 것입니다.

이번엔 조금 더 복잡한 예시를 들어보겠습니다.

상품 카테고리에 속해있는 상품 3개를 보여주는 블록입니다.
어떤 기준으로 쪼개야할까요?

주요 쟁점은 상품 카테고리를 박스, 텍스트, 리스트로 구성할지
혹은 박스 + 텍스트 + 리스트가 하나의 블록이라고 정의할지 입니다.

앞선 예시와 비슷한 장단점들이 존재하지만 아주 중요한 변화가 있습니다.

텍스트와 상품 리스트 사이에 무언가가 들어갈 수 있는 가능성입니다.

앱과 웹이 동시에 사용하고 있다면 문제는 커집니다.
미리 잘게 쪼개놓았다면 크게 문제가 되지 않았지만 뭉쳐놓았다면 앱도 함께 쓰고 있기때문에 버저닝, 하위호환, 마이그레이션등의 옵션들을 함께 고려해야합니다.

프론트엔드 엔지니어가 컴포넌트를 어떻게 쪼개고 재활용할건지에 대한 고민의 연장선입니다.
다만 조금 다른 점은 모든 프론트엔드 엔지니어뿐 아니라 백엔드, 디자이너등 다양한 직군들이 납득할만한 기준이 있어야 한다는 것이죠.

팀내에서는 SDUI를 다년간 설계하면서 많은 논의들을 했고
인원이 들어오고 나감에 따라 각자 생각하는 "블록"이란 무엇인가에 대한 답이 달랐습니다.

가령 일부 인원은 최소한의 기능을 담은 Atom 단위의 컴포넌트를 블록으로서 정의하고 싶은 니즈가 있었습니다.
디자인적인 자유도와 기능적인 유연함이 주된 이유였습니다.

그렇지만 위와같은 설계는 결국 이해관계자들에게 복잡함을 안겨주는 일이 되었습니다.
하나의 기능을 구현하기 위해 더 많은 설정들과 창의력을 요하게 되었고 "제품이 어렵다"라는 피드백으로 이어졌습니다.

결국 팀내에서는 "블록"의 정의를 최소한의 기능을 제공하는 원자와 같은 개념이 아니라 충분히 유의미한 기능을 제공하는 Preset에 가까워야 한다는 원칙을 세웁니다.

이해를 위해 아토믹 디자인 시스템에 빗대어 표현해보자면
Atom 보다는 Organism에 가까운 개념입니다.

설정의 재사용

블록을 잘개 쪼개지 않았다고 해서 재사용성을 고려하지 않는 것은 아닙니다.

여러 유사한 블록들에서 필요로 하는 "설정"에는 공통점들이 존재합니다.
블록은 나뉘어있지만 설정은 처음부터 확장가능성과 재사용성을 고려해 작성해나갔습니다.

만들게 될 블록들을 쭉 살피고
사용자들은 각 블록들에게 어떤 기대를 갖고 있는지 파악하는 것이 굉장히 중요합니다.

예시를 다시 볼까요?

미리 관리자가 설정한 Product Category에 포함된 상품 일부를 보여주는 블록을 "Product Category Preview" 블록이라고 임시로 명명해보겠습니다.

범위를 좁혀 상품 여럿을 보여주는 List에 어떤 니즈를 파악해보겠습니다.

상품 리스트에는 관리자들이 어떤 것들을 설정할 수 있기를 기대할까요?

생각나는 것들을 얘기해보도록 하죠.

  • 미리보여줄 상품 최대 개수
  • 표시할 열과 행 개수
  • 열과 행의 Gap
  • 상품 썸네일의 위치
  • 상품 리뷰 개수
  • 상품 별점

각 항목마다 우리는 해당 항목이 다른 리스트에도 보편적으로 쓰일 수 있는가를 항상 판단해야합니다.
리스트에 대한 동일한 설정이 블록 이름이 다르다고 해서 매번 다른 이름 혹은 형식으로 쓰인다면 관리에 굉장히 불리하기 때문이죠.

표시할 열과 행 개수, Gap은 왠지 다른 리스트에도 쓰일 수 있을 것 같군요!

공통화는 다음과 같이 될 것 입니다.

listSetting: {
	rowGap: number,
	columnGap: number,
    rowCount: number,
    columnCount: number,
}

눈치채신 분들도 있겠지만 중요한 것은

일반적으로는 디자인이 변하면 이를 개발자들에게 전달하고 바로 코드에 반영하고 배포를 진행합니다.
노코드툴의 경우 이런 변경들은 조심스럽습니다.
고객사의 경우 이미 구축된 디자인적 일관성이 본인의 의도와 상관없이 빈번하게 바뀌는 것을 원하지는 않을 것 입니다.

그렇다면 기존의 디자인을 유지하기 위해서는 변경사항은 곧 설정의 추가로 이어집니다.

이렇게 1개씩 추가하다보면 어느새 상품 미리보기를 그리는데에도 수십가지 config들이 생겨나는 모습들을 볼 수 있습니다.

팀에서 가장 신경 쓴 부분은 다양한 엔티티를 사용하는 컴포넌트를 그리는데 있어서 공통적인 config를 구축하는 것이었습니다.

예를들어 상품, 콘텐츠등의 목록을 만든다고 했을 때
공통적으로 목록의 column, row, gap등 "목록"이라는 것을 구성하는 기본적인 설정에 대해서는 재사용성을 고려해 이름을 명명하고 이것들이 묶인 field로서 관리하죠. 위 예시에서 listSetting은 다른 리스트들에서도 쓰일 수 있는 설정입니다.

다만 미리보여줄 상품 최대 개수, 상품 썸네일 위치, 상품 리뷰,별점 등등은 공통화가 가능한 영역일까요?
서비스마다 다를 것입니다.
상품콘텐츠를 다루는 서비스라면 썸네일 위치는 공통화 할 수 있을 것입니다.

그렇다면 리뷰 개수와 별점은요? 공통화를 할 수 없는 세팅값들은 어디에 넣어야 할까요?

1가지 해결책은 List의 전체의 정렬만 공통화하고 리스트의 아이템들에 해당하는 setting은 데이터 소스에 따라 별도로 정의하는 것입니다.

아래와 같이 말이죠.

listSetting: {
	rowGap: number,
	columnGap: number,
    rowCount: number,
    columnCount: number,
},
productItemSetting: {
	showReview: boolean,
    showRating: boolean,
    ...
}

정답이 있는 것은 아닙니다.
서비스에서 사용되는 데이터 소스들과 이를 표현하는 UI들의 종류를 파악하고 공통점을 찾아내는 것이 핵심입니다.

블록, 페이지, 서비스의 일관성에 대해

다시 한 번 좀전의 설정을 천천히 들여다봅시다.

listSetting: {
	rowGap: number,
	columnGap: number,
    rowCount: number,
    columnCount: number,
},
productItemSetting: {
	showReview: boolean,
    showRating: boolean,
    ...
}

문득 이런생각이 떠오르시지는 않나요?

별점을 보여주는 것과 리뷰를 보여주는 것은 서비스 전체에 일관되게 적용되어야하는 것은 아닐까?

설정을 만들 때에는 항상 위와 같은 고민에 부딪힙니다.

와닿지 않을 수 있으니 다른 예시를 들어보겠습니다.

“콘텐츠 목록”에 들어가는 콘텐츠 카드들에는 좋아요 기능이 있습니다.
근데 이 좋아요 여부를 나타내는 아이콘을 “하트” 혹은 “엄지“ 로 변경가능하게 기능을 정의하고 싶습니다.
편의상 likeIcon이라는 필드로 불러보겠습니다.

그렇다면 likeIcon라는 항목은

콘텐츠 목록의 명세에 정의되어야 하는 내용일까요?
혹은 페이지 명세에 정의되어야 하는 내용일까요?
아니면 서비스 설정에 들어가야 하는 내용일까요?

우선 생각합니다.

서비스 전체에 예외를 만들어내는 상황은 절대 없을 것인가.

서비스에는 하트로 되어있지만 특정 페이지만 엄지로 바꾸어야하는 경우가 있는가를 생각합니다.
모든 사람들이 보편적으로 그렇게 생각하고 있고 그럴 일이 없다면 블록에 setting값을 넣을 이유는 없습니다.

그렇다면 텍스트 블록의 글꼴은 어떤가요?
반대로 특정 텍스트만 글꼴을 달리 줄 수 있으니 합리적입니다.
다만 페이지 설정은 필요할까요?
특정 마케팅 페이지만 글꼴을 달리주어야 할 니즈는 없을까요?

저희 조직은 다년간의 경험을 통해 절대 없을 것이다라는 사실에
모두가 고개가 끄덕여지지 않는 한 필요한 세팅값은 서비스 -> 페이지 -> 블록 전부 세팅을 설정할 수 있게 해둡니다.

즉 일부 세팅값은 블록 -> 페이지 -> 서비스의 값을 폴백으로서 사용합니다.

마치며

SDUI는 결국 우리팀만의 규칙이 담긴 문서를 만들어 가는 일과 같습니다.
확장 가능하고 지속 가능한 문서를 만들기 위해서는 이를 관통하는 일관된 철학이 필요합니다.

우리는 SDUI를 개발할 때에는 늘 관리의 용이함과 자유도를 저울에 두고 고민하게 됩니다.
관리가 용이할수록 자유도가 떨어지고
자유도가 높을수록 관리자도 개발자도 PM도 디자이너도 실수하기 쉽고 복잡함을 경험합니다.

정답은 없습니다.
SDUI를 어떤 목적으로 쓰이는지 단순 마케팅 용도인지 그렇지 않은지에 따라 달라질 수 있습니다.

1가지 확실한 것은 모두가 생각하는 형태가 다르다는 것이고 이것을 모두가 이야기하여 이해를 합치시키는 과정이 굉장히 중요하다는 것 입니다.

참고: SDUI에 대한 다양한 회사의 글

https://engineering.mercari.com/blog/entry/20241210-f7c478382a/
https://devblog.kakaostyle.com/ko/2021-12-16-1-server-driven-ui
https://medium.com/airbnb-engineering/a-deep-dive-into-airbnbs-server-driven-ui-system-842244c5f5
https://toss.im/slash-23/session-detail/B1-3
https://toss.im/slash-23/session-detail/A1-2
https://flex.team/blog/2024/12/31/server-driven-ui/
https://ridicorp.com/story/rigrid-server-driven-ui/
https://techblog.woowahan.com/2719/
https://medium.com/catchtable/how-to-create-complex-web-pages-without-a-developer-5fe97483f4fa

profile
You only have to right once

0개의 댓글