데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      클라이언트 배포 없이 UI를 바꾼다고? 에이닷 Server Driven UI 도입기

      mcshin 25.04.09
      6,360 8 3
      DEVOTEE 요약
      에이닷은 AI 비서 앱으로, 사용자의 일상과 궁금증 해결을 돕습니다. 빠르게 변화하는 AI 기술에 발맞춰, 앱은 Server Driven UI(SDUI)를 도입하여, 서버에서 UI를 정의하고 클라이언트가 이를 동적으로 렌더링함으로써, 앱을 다시 배포할 필요 없이 유연한 UI 변경이 가능합니다. 이는 빠르게 변화하는 서비스 환경에서 효과적인 대응을 가능케 합니다.
      DEVOTEE 추천 블로그

      안녕하세요.

      에이닷은 AI 비서 앱으로서 사용자의 일상을 돕고, 궁금증을 해결하며 더 나은 경험을 제공하기 위해 끊임없이 발전하고 있습니다.

      AI 기술은 빠르게 변화하고 있으며, 저희도 이에 발맞춰 앱을 더욱 유연하고 효율적으로 개선하기 위해 다양한 기술적 시도를 하고 있습니다.

      그 과정에서 에이닷 개발팀은 추가적인 빠른 UI 추가 및 변경을 지원하며, 사용자 맞춤형 UI를 유연하게 제공할 수 있는 솔루션을 고민하게 되었습니다.

      다양한 방식을 검토한 끝에, 서버에서 UI를 정의하고 클라이언트가 이를 동적으로 렌더링하는 Server Driven UI 방식을 도입하게 되었습니다.

      SDUI를 활용하면 앱을 배포하지 않고도 UI를 실시간으로 변경할 수 있어, 빠르게 변화하는 AI 서비스 환경에서 보다 효과적으로 대응할 수 있습니다.

      이번 글에서는 SDUI를 적용하게 된 배경과 설계 방식, 그리고 프로덕션 적용 과정에서의 경험을 공유하려고 합니다.

      그럼, SDUI가 무엇인지부터 차근차근 살펴보겠습니다.

      Server Driven UI

      Server Driven UI란?

      Server Driven UI(이하 SDUI)는 서버에서 UI의 구조와 동작을 정의하고, 클라이언트는 이를 받아서 화면을 동적으로 구성하는 방식입니다.

      서버가 UI 데이터를 JSON 같은 형식으로 내려주면, 클라이언트는 이를 해석하여 동적으로 화면을 렌더링합니다.

      image.png

      기존 Client Driven UI 개발 방식과의 차이

      기존의 Client Driven 앱 개발 방식에서는 UI가 클라이언트 코드에 구현되어 있습니다.

      버튼의 위치, 리스트의 구성, 화면의 전환 방식 등이 모두 코드로 정의되며, UI를 변경하려면 앱을 다시 빌드하고 배포해야 합니다.

      하지만 Server Driven UI는 UI 구성 요소를 서버에서 정의하고 클라이언트는 이를 렌더링하는 역할만 하기 때문에, 앱 재배포 없이도 실시간으로 유연한 UI 변경이 가능합니다.

      다음은 두 방식의 차이점과 각각의 장단점입니다.

      항목

      Client Driven UI

      Server Driven UI

      UI 업데이트 방식

      앱 업데이트 필요

      서버 변경 즉시 반영

      유연성

      제한적

      매우 높음

      배포 속도

      느림(스토어 심사 필요)

      빠름(즉시 반영)

      개발 난이도

      상대적으로 쉬움

      클라이언트에서 동적 UI 렌더링해야 해서 복잡해질 수 있음

      네트워크 의존성

      독립적 (오프라인에서도 동작 가능)

      연결 필수

      테스트/디버깅

      쉽고 안정적

      동적인 UI 처리로 디버깅 어려울 수 있음

      왜 Server Driven UI를 도입해야 할까?

      SDUI가 유용한 이유는 여러 가지가 있지만, 특히 빠르게 변화하는 서비스에서 그 진가를 발휘합니다.

      저희가 개발하는 AI 비서 앱은 앞으로 UI가 자주 변경될 가능성이 높습니다. AI 기술이 발전함에 따라, 사용자의 요청에 대한 응답 방식도 더욱 다양해지고 있습니다.

      단순한 텍스트 답변을 넘어, 리스트, 카드 형태, 인터랙티브한 요소 등 다양한 포맷으로 정보를 표현해야 할 필요가 커지고 있습니다.

      이러한 변화에 빠르게 대응하기 위해, UI를 앱 배포 없이도 동적으로 조정할 수 있는 방법에 대한 필요성이 커지고 있는 상황입니다.

      기존 방식대로라면 UI 변경이 있을 때마다 앱을 업데이트해야 하고, 이는 스토어 심사 과정에서 시간 지연이 발생할 수 있습니다.

      반면, Server Driven UI를 적용하면 서버에서 UI를 즉시 조정할 수 있어 빠르게 변화하는 AI 서비스의 요구사항을 효과적으로 반영할 수 있습니다. 

      또한, AI 비서 앱의 특성 상 추후 사용자별 맞춤형 UI 제공이 필요할 수도 있구요. 

      이런 기능을 Server Driven UI로 구현하면 클라이언트 코드 수정 없이 서버에서 모든 조작이 가능해지고, 배포 속도를 혁신적으로 단축할 수 있습니다.

      물론 Server Driven UI는 클라이언트가 동적으로 UI를 렌더링해야 해서 코드가 복잡해지고, 서버-클라이언트 간 데이터 연동이 중요해지는 단점도 있습니다.

      하지만 AI 비서 앱처럼 빠른 변화와 개인화된 UI가 중요한 서비스에서는 SDUI가 매우 적합한 솔루션이 될 수 있습니다.

      따라서, 저희 팀은 이러한 장점을 활용하기 위해 Server Driven UI를 도입해 보기로 결정하였고, 이를 어떻게 설계 및 구현했는지 다음 챕터에서 자세히 설명해 드리겠습니다.

      Server Driven UI 설계 및 구현

      Server Driven UI를 적용하기 위해서는 서버와 클라이언트 간 UI를 어떤 방식으로 표현할 것인지에 대한 정의, 클라이언트에서의 렌더링 로직,

      그리고 UI를 쉽게 추가, 수정하고 배포할 수 있도록 Server Driven UI Admin 시스템 설계가 필요합니다.


      이 챕터에서는 이러한 SDUI의 설계 및 구현 과정을 자세히 설명하겠습니다.

      Server Driven UI Specification

      SDUI 상세 스펙을 설계할 때 다양한 UI 화면을 재사용 가능하고 일관되게 표현하려면 구조적인 정의가 필요했고, 저희는 이를 위해 UI를 3가지 크게 타입으로 나누는 방식을 채택했습니다.

      그 결과, SDUI에서 UI를 구성하는 요소는 다음과 같이 세 가지 핵심 구조로 정의되었습니다.

      • Screen: 하나의 독립적인 전체 화면을 표현

      • Component: UI를 구성하는 시각적 요소들. Layout과 Element로 구분

      • Action: 사용자의 상호작용(예: 버튼 클릭, 화면 이동 등)에 대한 동작 정의

      또한, UI 정의 포맷으로는 범용성, 가독성 등을 고려하여 JSON 포맷을 사용하기로 하였습니다.

      이러한 기준을 바탕으로, 저희는 JSON 포맷 기반의 화면(Screen)을 중심으로 Layout과 Element를 포함한 Component를 모델링하고, 여기에 Action을 연결하는 구조를 정의하게 되었습니다.

      이제 각 타입에 대해 조금 더 자세히 설명드릴게요.

      Screen

      Screen은 SDUI에서 하나의 독립적인 화면(페이지)을 정의하는 최상위 단위입니다. 클라이언트는 Screen 객체를 기반으로 어떤 레이아웃으로 화면을 구성할지 판단하게 됩니다.

      저희는 여러 타입의 Screen을 정의할 수 있도록 구조를 열어두었고, 타입에 따라서 필요한 속성도 달라지게 됩니다.

      다양한 레이아웃 중에서도 가장 일반적으로 사용되는 레이아웃 형태는 상단 앱바와 본문 컨텐츠 영역을 표시하는 레이아웃이 가장 많이 사용되는 케이스입니다.

      이를 지원하는 케이스를 BasicScreen 타입으로 정의하였고, 상단 앱바(topBar), 본문(contents), 그리고 선택적으로 화면 갱신(swipeToRefresh) 기능을 포함할 수 있도록 설계했습니다.

      아래는 BasicScreen의 상세 스펙 예시입니다.

      image.png

      • type: 이 Screen의 유형을 명시합니다. 여기서는 "BasicScreen"으로, 가장 일반적인 단일 화면 구조를 의미합니다.

      • topBar: 화면 상단의 앱바를 정의하는 영역이며, 주로 title, 뒤로가기 버튼, 설정 아이콘 등의 요소를 포함하는 Component로 구성됩니다.

      • contents: 실제 화면의 주된 내용을 표현하는 부분이며, 내부에 리스트 등 다양한 Component들을 이 영역 안에서 배치합니다.

      • swipeToRefresh: true로 설정하면, 화면을 아래로 끌어당겨 새로고침할 수 있는 기능을 활성화합니다.

      • swipeToRefreshAction: swipeToRefresh가 true일 경우, 새로고침 했을 때 수행할 Action들을 정의할 수 있습니다.

      Component

      Component는 SDUI에서 화면을 구성하는 실제 렌더링 대상 UI 요소들을 정의하는 스펙입니다.

      하나의 Screen 안에는 여러 개의 Component가 포함되며, 이들이 모여 최종적으로 사용자에게 보여지는 UI를 구성하게 됩니다.

      저희는 Component를 크게 다른 Component들을 묶어서 표현하는 Layout과 최소 렌더링 단위 요소인 Element 두 가지 타입으로 나누어 설계했습니다.

      Component::Layout

      Layout은 자식 컴포넌트를 포함할 수 있는 그룹 역할의 컴포넌트입니다. 즉, 여러 Component들을 내부적으로 어떻게 배치할지에 대한 구조적 정의를 담당합니다.

      대표적인 Layout으로는 다음과 같습니다.

      • VerticalLayout: 세로 방향으로 자식 컴포넌트를 배치

      • HorizontalLayout: 가로 방향으로 자식 컴포넌트를 배치

      • OverlayLayout: 여러 컴포넌트를 겹쳐서 배치 (예: 배경 이미지 위에 버튼을 띄우는 경우 등)

      Component::Element

      Element는 더 이상 자식 컴포넌트를 가지지 않는 화면의 최소 구성 단위입니다.

      텍스트, 이미지, 여백 등 실제 사용자에게 보여지는 UI 요소들을 정의하며, 가장 많이 사용되는 컴포넌트 타입입니다.

      대표적인 Element로는 다음과 같습니다.

      • Text: 텍스트 출력

      • Image: 이미지 표시

      • Spacer: 빈 영역을 차지하는 컴포넌트

      Component::Attributes

      Component들은 화면에 그려질 때 크기, 여백, 스타일 등 자신만의 속성들을 가질 필요가 있습니다.

      아래는 모든 Component가 가질 수 있는 공통 속성들의 일부 예시입니다.

      • width: Component의 너비. Dp, ExpandToFill, WrapContent 3가지 타입 지정 가능함.

      • height: Component의 높이. Dp, ExpandToFill, WrapContent 3가지 타입 지정 가능함.

      • margin: Component의 외부 여백

      • padding: Component의 내부 여백

      • background: Component 렌더링 배경 스타일

      • onClick: Component를 사용자가 클릭했을 때 수행할 Action List

      공통 속성 이외에도, 특정 Component들은 자신만의 속성을 추가로 가질 수도 있습니다.

      예를 들어, Text Element의 경우에는 text, fontSize, fontColor 등의 텍스트 출력 관련 추가 속성을 가질 수 있고,

      VerticalLayout의 경우에는 isScrollable이나 content같이 스크롤 가능 여부나 내부 리스트를 어떤 Component로 채울지에 대한 속성을 가질 수 있습니다.


      Component들로 이루어진 컨텐츠 예시를 한번 살펴보도록 하겠습니다.

      image.png

      위 예시는 VerticalLayout 안에 Text와 HorizontalLayout이 포함되어 있고, 그 안에 이미지와 텍스트가 나란히 배치되는 구조입니다.

      조금 더 상세하게 설명하면 아래와 같은 레이아웃 구조임을 알 수 있습니다.

      화면 전체는 VerticalLayout으로 감싸져 있고, 세로 방향으로 내부 요소들이 배치됩니다.

      첫 번째 요소는 "Hello, world!"라는 텍스트이며, 글자 크기는 20sp입니다.

      두 번째 요소는 HorizontalLayout으로, 좌우로 이미지와 텍스트가 나란히 배치됩니다.

      왼쪽에는 정사각형(48x48dp) 사이즈의 이미지가 있고,

      오른쪽에는 "Image Description"이라는 텍스트가 위치하며, 좌우에 12dp의 마진을 가지고 있습니다.

      이렇게 Layout과 Element, 그리고 적절한 속성 설정을 통해서 다양한 UI를 표현할 수 있습니다.

      Action

      Action은 SDUI에서 사용자의 터치나 클릭 같은 인터랙션에 반응해 동작을 수행하는 기능을 정의합니다. 

      예를 들어 버튼을 누르면 화면이 이동하거나, 토스트 메시지가 뜨는 등의 동작을 서버에서 JSON으로 정의할 수 있습니다.

      이러한 Action들은 Component의 속성으로 함께 정의되며, 클라이언트는 이를 해석해 적절한 기능을 실행합니다. 

      SDUI의 장점은 이 동작까지도 코드 변경 없이 서버에서 동적으로 제어할 수 있다는 점입니다.

      아래는 미리 정의된 Action 타입 예시입니다.

      • NavigateToAction: 새로운 화면으로 이동합니다. 주로 버튼이나 리스트 아이템에 연결되어, 다른 Screen으로 전환할 때 사용됩니다.

      • NavigateUpAction: 현재 화면을 종료하고 이전 화면으로 돌아갑니다. Android의 '뒤로 가기'와 동일한 동작입니다.

      • RefreshScreenAction: 현재 화면을 서버에서 다시 불러오고, 특정 파라미터에 따라 내용을 갱신합니다. 실시간 데이터나 상태 기반 UI에 유용합니다.

      • ToastAction: 간단한 텍스트 메시지를 화면 하단에 띄워줍니다. 사용자에게 피드백을 줄 때 사용됩니다.

      Action의 타입마다 추가적으로 필요한 속성이 있을 수 있습니다.

      예를 들어, NavigateToAction은 이동할 화면 deeplink 속성이 필요하고 ToastAction은 토스트메시지에 표시할 message 속성이 필요합니다.

      아래 예시는 Text 컴포넌트를 활용하여 버튼 형태로 렌더링하고, 클릭 시 토스트 메시지를 띄우는 ToastAction을 정의한 예시입니다.

      image.png

      위처럼 Screen, Component, Action 스펙을 기반으로, 이제 클라이언트에서는 이 JSON 데이터를 받아 실제 UI로 렌더링하는 역할을 수행하게 됩니다.

      다음 챕터에서는 저희가 Android Compose 기반으로 구현한 렌더링 로직과, 각 스펙 타입을 어떻게 해석하고 동적으로 UI로 변환하는지에 대해 소개드리겠습니다.

      클라이언트 렌더링 로직

      서버에서 전달받은 JSON 스펙은 클라이언트에서 실제 UI로 렌더링되어야 하는데요, Android 내에서 구현된 방식을 간략하게 소개하고자 합니다.

      Android에서는 기본적으로 Compose를 이용하여 아래와 같은 단계를 거쳐 Server Driven UI를 렌더링하도록 설계되었습니다.

      1. JSON을 data class로 파싱

      먼저 서버에서 전달받은 JSON 문자열은 kotlinx.serialization을 사용하여 미리 정의해둔 data class로 변환됩니다.

      계위와 type에 따라 Screen, Component, Action 객체로 매핑되며, 이를 지원하기 위해 JsonContentPolymorphicSerializer를 적극 차용하였습니다.

      아래는 Component Type에 따라 알맞는 Component 객체로 역직렬화하기 위한 Serializer의 예제 코드입니다.

      internal object SDUIComponentTypeSerializer: JsonContentPolymorphicSerializer<Component>(Component::class) {
          override fun selectDeserializer(element: JsonElement): DeserializationStrategy<Component> {
              val typeString = element.jsonObject["type"]?.jsonPrimitive?.content ?: throw SerializationException("Missing 'type' field")
              val type = Component.Type.valueOf(typeString)
              return when (type) {
                  Component.Type.HorizontalLayout -> SDUIHorizontalLayout.serializer()
                  Component.Type.VerticalLayout -> SDUIVerticalLayout.serializer()
                  Component.Type.OverlayLayout -> SDUIOverlayLayout.serializer()
                  Component.Type.Text -> SDUIText.serializer()
                  Component.Type.Image -> SDUIImage.serializer()
                  ...
                  else -> throw Throwable("Unknown Type")
              }
          }
      }
      2. SDUIScreen 별도 처리

      화면 단위의 구조(Screen)는 서버에서 내려주는 가장 최상위 요소이므로 다른 컴포넌트들과 구분하여 따로 처리하였습니다.

      예를 들어 BasicScreen 타입인 경우, topBar, contents, swipeToRefresh 등 각 요소를 별도로 분해해 렌더링 로직을 구성합니다.

      3. Component 타입 기반 Composable 호출

      파싱된 Component 객체는 최상위 Composable 함수인 SDUIComponent Composable에 전달됩니다.

      이 함수는 전달받은 객체의 type을 확인하고, 해당 타입에 맞는 실제 렌더링 Composable 함수를 호출합니다.

      예를 들어 VerticalLayout 타입이면 VerticalLayout Composable 함수를, Text 타입이면 Text Composable 함수를 호출하는 방식입니다.

      @Composable
      fun<T> T.SDUIComponent(component: Component) {
          when (component) {
              is SDUIOverlayLayout -> OverlayLayout(component)
              is SDUIHorizontalLayout -> HorizontalLayout(component)
              is SDUIVerticalLayout -> VerticalLayout(component)
              is SDUIText -> Text(component)
              is SDUIImage -> Image(component)
              ...
          }
      }
      4. 속성 처리: Modifier 기반 스타일 적용

      각 Component Composable은 자신에게 전달된 속성(Attribute)을 기반으로 Compose Modifier를 적용하도록 구현되었습니다.

      padding, width, background, clip 등의 스타일은 모두 Compose의 Modifier 체인으로 적용합니다.

      이 방식은 Compose의 선언형 구조와도 잘 맞아떨어지고, 재사용성과 확장성도 뛰어납니다.

      internal fun<T> Modifier.applyCommonAttributes(parentScope: T, component: Component): Modifier {
          return this
              .applyMargin(component.margin)
              .applyWidthIn(component.minWidth, component.maxWidth)
              .applyHeightIn(component.minHeight, component.maxHeight)
              .applyWidth(parentScope, component.width)
              .applyHeight(parentScope, component.height)
              .applyRatio(component.ratio)
              .applyAlign(parentScope, component.align)
              .applyBackground(component.background?.color, component.background?.shape)
              .applyBackgroundGradient(component.background?.gradient, component.background?.shape)
              .applyBorder(component.background?.borderWidth, component.background?.borderColor, component.background?.shape)
              .applyClip(component.clip)
              .applyAlpha(component.alpha)
              .applyClick(component.onClick)
              .applyPadding(component.padding)
      }
      
      @Composable
      fun<T> T.Text(component: SDUIText) {
          BasicText(
              modifier = Modifier.applySDUICommonAttributes(this, component),
              text = component.text,
              ...
          )
      }
      5. Layout Component의 재귀 렌더링

      Layout Component의 경우, 내부에 포함된 자식 컴포넌트 목록을 순회하면서 각 자식에 대해 다시 SDUIComponent Composable함수를 호출합니다.

      이를 통해 구조적으로 중첩된 UI도 재귀적으로 자연스럽게 렌더링할 수 있습니다.

      @Composable
      fun<T> T.OverlayLayout(component: SDUIOverlayLayout) {
          Box(
              modifier = Modifier.applySDUICommonAttributes(this, component),
              contentAlignment = component.defaultOverlayAlign.toAlignment() ?: Alignment.Center
          ) {
              component.content.forEach {
                  SDUIComponent(it)
              }
          }
      }
      6. ActionHandler

      SDUI Component에서 Action의 실행을 요청할 위치는 다양한 곳이 될 수 있습니다.

      Compose에서 UI Event를 전달하는 방식은 이벤트 처리 함수를 파라메터로 전달하는 방식이 일반적이지만,

      Server Driven UI 렌더링 방식의 구조 상 Composable의 depth가 깊어지고 람다 함수를 매번 전달하기에는 불필요한 중복 로직이 많아지기 때문에 CompositionLocalProvider를 활용하여 이벤트를 전달하기로 하였습니다.

      CompositionLocalProvider(
          LocalSDUIActionHandler provides SDUIActionHandler { /* ActionList 처리 함수 등록 */ },
          ...
      } {
          SDUIScreen(..)
      }
      
      
      /**
       * Component Composable들의 공통 Click 처리를 담당하는 Modifier
       * click 이벤트 발생 시 onClick 속성에 담겨 있는 Action 객체들을 ActionHandler에 전달한다.
       */
      internal fun Modifier.applyClick(
          onClickActionList: ImmutableList<SDUIAction>?
      ): Modifier {
          return if (!onClickActionList.isNullOrEmpty()) {
              this.composed {
                  val handler = LocalSDUIActionHandler.current
                  clickable {
                      handler.invoke(onClickActionList)
                  }
              }
          } else {
              this
          }
      }

      위의 설명을 도식화하면 대략적으로 아래와 같은 그림으로 표현할 수 있습니다.

      image.png

      덧붙여서, iOS도 SwiftUI 기반으로 동일 구조를 채택하여 Server Driven UI를 렌더링하도록 설계 및 구현되었습니다.

      SDUI Admin 시스템 설계 및 구현

      Server Driven UI를 실제 서비스에 잘 녹여내기 위해 마지막으로 꼭 필요한 요소는 바로 Admin System입니다.

      아무리 클라이언트가 유연하게 JSON 기반 UI를 렌더링할 수 있다고 하더라도, Backend에서 직접 복잡한 Layout JSON을 일일이 작성하고 관리하는 건 현실적으로 어렵고 비효율적입니다.

      따라서 저희는 UI 구조는 Admin에서 정의하고, 데이터는 Backend에서 채워넣는 구조를 지향하여 설계하기로 하였습니다.

      Admin의 가장 이상적인 방식은 WYSIWYG(What You See Is What You Get) 기반의 UI 에디터를 만들어 마우스 클릭만으로 UI를 설계할 수 있게 하는 것이겠지만,

      그만큼의 개발 리소스를 투입하기는 어려운 상황이었기에, 현실적으로 다음과 같은 간결한 구조를 채택했습니다.

      1. SDUI 템플릿 정의 기능

        • Admin에서 SDUI Layout JSON 템플릿을 직접 작성하고 저장할 수 있습니다.

        • 템플릿은 재사용 가능하며, 클라이언트에서 사용하는 스펙과 1:1로 대응됩니다.

      2. Mustache 기반 변수 치환 지원

        • 템플릿 내에서 동적으로 채워질 부분은 {{variable}} 형식의 Mustache 포맷을 사용해 정의할 수 있습니다.

        • 예를 들어 버튼 텍스트, 이미지 URL, 딥링크 URI 등 다양한 콘텐츠를 Backend에서 제공한 데이터로 유연하게 삽입할 수 있습니다.

        • image.png

      3. 템플릿 중첩 지원

        • 하나의 템플릿 안에서 다른 템플릿을 include할 수 있도록 하여, 복잡한 화면 구성도 모듈화할 수 있게 만들었습니다.

        • 이를 통해 반복되는 UI 조각을 재활용하고 관리 효율성을 높였습니다.

      4. 완성된 Layout 반환 API 제공

        • Backend 개발자가 templateId와 필요한 변수 값들을 전달하면 Admin 시스템은 해당 템플릿에 변수들을 치환하여 완성된 Layout JSON을 리턴해 줍니다.

        • Backend는 이 JSON에 추가로 데이터를 붙이거나 가공하여 클라이언트에 전달하면 됩니다.

      위와 같은 설계를 그림으로 도식화하면 아래와 같은 구조로 표현할 수 있습니다.

      image.png

      그리고 Admin 페이지는 아래와 같은 화면으로 구현될 수 있습니다.

      image.png

      위와 같은 Admin 시스템 구축을 통해, 각 담당자들의 업무가 명확하게 구분될 수 있게 되었습니다.

      • Layout 설계 업무 담당자는 Admin에 접속해 필요한 화면 템플릿을 정의합니다.

      • Backend 개발자는 등록된 템플릿의 ID와 필요한 변수들을 확인한 후,

      • Admin 시스템에 연동된 API를 호출하여 완성된 Layout JSON을 받아 클라이언트에 전달합니다.

      이렇게 구조를 분리한 덕분에,

      • Backend 개발자는 비즈니스 로직과 데이터 가공에만 집중할 수 있고

      • Layout 추가 및 변경은 디자이너, 클라이언트 개발자가 직접 Admin에서 관리할 수 있어 운영 효율도 크게 높아졌습니다.

      이 시스템은 초기 구현 난이도가 낮으면서도 실전에서 충분히 유용하게 활용할 수 있었고, 이후에도 레이아웃 작성에 도움이 될 수 있는 다양한 기능을 추가하여 더 발전시킬 예정입니다.

      프로덕션 적용

      Server Driven UI를 실제 프로덕션에 적용하기 위한 첫 번째 단계로, 저희는 에이닷 대화방 영역 중 일부에 SDUI를 도입하기로 하였습니다.

      대화방은 에이닷 사용자 경험의 중심이 되는 화면이며, 특히 AI가 정보를 상세하게 제공하는 컴포넌트 영역은 사용자와의 상호작용이 많고, 지속적으로 다양한 형태의 응답 표현이 요구되는 곳입니다.

      image.png

      기존에는 이 영역에 대해 30개가 넘는 컴포넌트 타입이 존재했고, 각 타입 별로 클라이언트 코드에 별도의 레이아웃을 구현해두는 방식으로 개발되고 있었습니다.

      이 방식은 초기에는 효과적이었지만, 새로운 컴포넌트를 추가할 때마다 클라이언트 코드 수정을 동반했고, 그에 따른 앱 배포와 QA 작업이 반복적으로 발생하는 문제가 있었습니다.

      이에 따라 저희는 다음과 같은 방향으로 부분 적용을 시도했습니다.

      1. 에이닷 내 뮤직 에이전트 대화방에서 사용되는 대화방 컴포넌트들을 대상으로 일부를 Server Driven UI 방식으로 전환하여 동작 검증

      2. 새로운 컴포넌트 필요 시 앱 배포 없이 SDUI Admin에서 제어하여 추가

      3. 클라이언트는 SDUI 스펙에 따라 동적으로 렌더링만 담당

      image.png

      위 그림처럼 기존 뮤직 에이전트 대화방에서 주로 사용되던 6개의 컴포넌트들을 Server Driven UI 방식으로 전환하였고,

      이렇게 부분적으로 적용함으로써 위험 부담을 최소화하면서도 새로운 레이아웃의 유연한 도입과 관리 효율성 향상이라는 SDUI의 장점을 실전에서 경험할 수 있었습니다.

      Lesson & Learn

      SDUI를 설계하고 실제 프로덕션에 도입하는 과정에서 여러 가지 시행착오를 겪었고, 그 과정에서 다양한 Lesson & Learn을 얻게 되어 아래와 같이 함께 공유드리고자 합니다.

      플랫폼 간 UI 차이로 인한 스펙 정의의 어려움

      가장 먼저 부딪힌 벽은 Android(Compose)와 iOS(SwiftUI) 간의 UI 구성 방식 차이였습니다.

      의외로 서로 지원하지 않는 기능도 있었고, 같은 UI라고 하더라도 명칭이나 레이아웃 구현 방식이 달라 스펙을 공통으로 정의하기가 쉽지 않았습니다.

      결론적으로 SDUI Spec을 정의할 때는 양 플랫폼 개발자가 함께 논의하며 공통으로 처리할 수 있는 최소 렌더링 단위를 잘 설계해야 한다는 교훈을 얻었습니다.

      Admin System 구현과 개발 리소스 비용 간 균형점

      SDUI를 제대로 활용하려면, Admin 시스템과 클라이언트 SDK가 어느 정도 잘 갖추어져 있어야 합니다.

      하지만 너무 완성도 높게 만들려다 보면 개발 리소스가 많이 들고, 반대로 최소 수준만 구현하면 화면 구성이 오히려 더 어렵고 비효율적이 될 수 있습니다.

      이번 프로젝트를 진행하면서 느낀 가장 큰 숙제는, 그 중간 단계의 적절한 균형점을 찾는 것이었습니다.

      실제로 저희는 완전한 WYSIWYG은 아니지만, 템플릿 정의 + Mustache 변수 치환 방식으로 현실적인 시스템을 구성했고, 이를 통해 충분히 생산성을 확보할 수 있었습니다.

      또한 Admin System에 대해 장기적인 목표를 세워서 점진적으로 개선하는 것을 목표로 하고 있습니다.

      SDUI Spec 버전 관리의 중요성 인식

      Spec이 점점 복잡해지고 활용 범위가 넓어질수록, 클라이언트와 서버 간의 스펙 호환성이 점점 더 중요해졌습니다.

      특히 새로운 속성이나 타입이 추가될 때, 하위 버전의 클라이언트가 이를 알지 못해 생기는 문제를 방지하기 위해,

      Spec 자체의 버전 정보를 관리하고 클라이언트에서 대응 가능 여부를 판단하는 체계가 필요하다는 것을 느꼈습니다.

      앞으로는 이 부분을 체계화하여, 스펙 변경에 대한 안정성을 확보하는 것이 중요한 과제가 될 것입니다.

      앞으로의 방향

      Server Driven UI의 구현과 도입을 통해 다양한 가능성과 어려운 점을 모두 경험할 수 있었고, 이를 바탕으로 앞으로 더욱 안정적이고 확장 가능한 구조로 발전시켜 나가려고 합니다.

      다음은 에이닷 개발팀에서 고려하고 있는 Server Driven UI의 주요 개선 및 확장 방향입니다.

      SDUI Spec 버전 및 하위 호환성 전략 설계

      현재는 클라이언트 앱 버전을 기준으로 SDUI Spec 대응 여부를 판단하고 있으며, 이후 명확한 스펙 버전 체계를 도입하는 방안을 검토 중입니다.

      이전 버전과 호환성을 유지할 수 있는 fallback 전략과 함께, Admin 시스템 내에서 Spec 버전별로 템플릿을 관리할 수 있는 구조를 설계해나갈 계획입니다.

      이를 통해 스펙 변경이나 확장 시에도 안정적인 대응이 가능하도록 체계를 마련할 예정입니다.

      다양한 Layout과 Element 구현

      현재는 기본적인 레이아웃과 컴포넌트를 중심으로 UI를 구성하고 있지만,

      앞으로는 GridLayout, TabLayout, ExpandableList, Badge, Divider 등 보다 풍부한 표현력과 복잡한 화면 구성에 대응할 수 있는 Layout 및 Element를 단계적으로 확장해나갈 계획입니다.

      더 나은 인터랙션 / Action 지원

      기본적인 화면 전환이나 토스트 메시지 외에도 Dialog, BottomSheet, Animation, Text 입력, API 호출 및 결과 핸들링 등 더 다양한 사용자 인터랙션과 앱 동작을 유연하게 제어할 수 있는 Action 타입들을 추가해 나갈 예정입니다.

      이를 통해 SDUI로 표현 가능한 UX 범위를 넓히는 것이 목표입니다.

      Admin System 개선

      현재 Admin System에 대해서 필요성과 개발 리소스 여부에 따라 UI 프리뷰, WYSIWYG 편집기 등 사용성을 개선할 수 있는 기능들을 단계적으로 도입할 계획입니다.

      이를 통해 더욱 더 직관적으로 템플릿을 구성하고 관리할 수 있도록 발전시켜 나갈 예정입니다.

      마무리하며

      SDUI는 단순한 기술 적용을 넘어, 서비스의 변화 속도와 운영 효율성을 함께 끌어올릴 수 있는 중요한 전략적 수단입니다.

      UI를 서버에서 정의하고 제어함으로써 앱 업데이트 없이도 실시간으로 화면을 변경할 수 있고, 사용자 맞춤형 UI 제공이나 실험적인 기능 배포도 훨씬 유연해졌습니다.

      물론, 이러한 장점만큼이나 개발 리소스가 많이 들고, 시스템을 안정적으로 구축하기 위한 진입 장벽도 분명 존재합니다.

      초기 설계 단계부터 플랫폼 간 UI 차이, 스펙 구조, 렌더링 방식, 버전 관리, 테스트 전략 등 고려할 점이 많고, Admin 시스템이나 SDK 구현도 일정 수준 이상은 갖추어야 합니다.

      결국 SDUI를 도입할 때 가장 중요한 것은, 단점을 어떻게 적절히 극복하면서도 그 안에서 최대한의 장점을 취할 수 있을지를 고민하는 것이라고 생각합니다.

      저희 에이닷 개발팀도 이 방향성을 기준으로, 한번에 무리하게 전체를 전환하기보다는 작은 단위부터 점진적으로 적용 범위를 넓혀가며 안정성을 확보하고자 노력하고 있습니다.

      앞으로도 다양한 시도를 통해 SDUI를 우리 서비스에 맞는 방향으로 계속 발전시켜 나갈 계획이며, 이번 사례가 비슷한 고민을 하고 있는 다른 팀들에게도 작은 참고가 되었으면 좋겠습니다.

      감사합니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      mcshin 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기