카카오스타일에서는 서버 드리븐 UI(Server Driven UI, 이하 SDUI)를 통해 UI의 유연성을 가져가고 있습니다. 서버가 클라이언트 UI를 동적으로 제어하므로써 빌드 및 배포 없이도 UI 업데이트를 할 수 있고, A/B 테스트도 효율적으로 수행할 수 있었습니다. 다만 이렇게 구축된 시스템을 몇년간 운영하다보니 몇가지 문제점이 발생했습니다.
이 글에서는 초기 구조에서 어떤 문제가 발생했는지 살펴보고, 전시팀에서 어떤 방식으로 개선해 나갔는지에 대해 이야기 해보려고 합니다.
이전 포스팅을 먼저 읽으시면 좋습니다. 기존 SDUI는 하나의 쿼리로 한 화면에 그려지는 모든 데이터를 내려받는 구조입니다.
이러한 문제점들은 사용자 경험을 저하시킬 뿐만 아니라, 개발 및 유지보수 과정에서도 복잡성을 증가시켰습니다. 따라서 더 유연하고 효율적인 구조로 개선이 필요했습니다. 새로운 구조는 이러한 문제들을 해결하고, 시스템의 유연성과 확장성을 높이며, 사용자 경험을 향상시키는 방향으로 설계되어야 했습니다.
기존의 단일 쿼리 구조에서 발생한 문제들을 해결하기 위해, 각 컴포넌트별로 독립적인 API를 호출하는 구조로 변경하였습니다.
어떻게?? 코드를 보면서 이야기 해봅시다!
서버에서는 template 쿼리를 통해 화면에 그려져야할 컴포넌트의 최소 정보(type, id)를 제공합니다.
type Query {
"""
page_id에 맵핑되는 현재 컴포넌트 순서를 리턴한다.
"""
template(page_id: String!): [Component!]!
}
type Component {
id: ID!
type: ComponentType!
}
enum ComponentType {
BANNER
MENU
ITEM
}
아래 쿼리를 통해 특정 페이지(ex. BRAND
)에 어떤 컴포넌트들이 배치될 것인지를 서버로부터 받아옵니다.
query FetchTemplate {
template(page_id: "BRAND") {
components {
id
type
}
}
}
template
쿼리 요청을 통해 컴포넌트의 id
와 type
을 리턴 받습니다.
type
에 매핑되는 API를 호출할 때 노출되어야 할 데이터를 매핑하기 위해 사용됩니다.서버에서는 다음과 같은 방식으로 데이터를 반환합니다.
fun template(page_id: String): List<Component> {
return dbRepository.findAllByPageId(page_id)
.map { it.toComponent() }
}
요청을 받은 서버는 별다른 비즈니스 로직 없이 저장된 값을 반환하기 때문에 빠른 속도로 응답을 전달할 수 있습니다.
{
"data": {
"template": [
{
"type": "BANNER",
"id": "0"
},
{
"type": "MENU",
"id": "0"
},
{
"type": "ITEM",
"id": "0"
},
...
]
}
}
앱에서는 해당 값을 전달받고, type
형태에 맞는 스켈레톤을 그립니다.
이후 각 컴포넌트 type
에 대응하는 API를 호출하게 됩니다. (데이터 지연 로딩(lazy loading))
GraphQL의 장점으로 뽑히는 것 중 하나가 데이터를 한번에 요청해 라운드 트립(Round-trip)을 줄일 수 있다는 부분입니다. 그렇기 때문에 카카오스타일 SDUI에서도 한번에 요청하는 것으로 초기 모델을 설계했습니다.
하지만 레이턴시 증가, 단일 장애점(SPOF) 같은 문제를 겪었고, 이를 해결하기 위해 설계를 다시 고민했습니다. 그리고 쿼리를 분할하는 간단한 방법으로 많은 문제를 해결할 수 있었습니다.
향후에도 SDUI를 지속적으로 개선하고 발전시켜 나가면서, 더 나은 사용자 경험과 효율적인 개발 환경을 제공해 나갈 계획입니다.