해외주식 백오피스를 개발하면서 사용된 Server Driven User Interface (SDUI)의 개념, 예시, 개발자 관점, 활용 경험에 대해 정리했다. SDUI는 화면 구조와 속성을 서버에서 정의하고 클라이언트가 해석하여 UI를 렌더링하는 방식으로, 웹/앱 배포 없이 서버 수정만으로 UI 변경이 가능하다.
1. SDUI란? (Server Driven UI)
SDUI는 화면 구조와 속성을 서버에서 정의하고, 클라이언트가 해석하여 UI를 렌더링하는 방식이다. 웹/앱 배포 없이도 서버 수정만으로 UI 변경이 가능하다.
서버가 JSON 스키마를 내려주면, 프론트엔드는 이를 컴포넌트로 매핑해 화면을 그린다.
앱 배포 없이 서버 수정만으로 UI 변경이 가능하고, 동일 API를 여러 플랫폼에서 재사용할 수 있다.
2. Server Driven Grid
Grid JSON
{
"headers": [
{ "headerName": "권리 ID", "field": "scheduleId", "type": "string" },
{ "headerName": "계좌번호", "field": "accountNo", "type": "string" },
{ "headerName": "기준일", "field": "baseDate", "type": "date" }
],
"result": {
"content": [
{ "scheduleId": 1, "accountNo": "", "baseDate": "2025-02-18" },
{ "scheduleId": 2, "accountNo": "", "baseDate": "2025-02-18" }
],
"pageable": { "pageNumber": 0, "pageSize": 50 },
"totalElements": 13
}
}
Grid 컴포넌트
<SelectionGrid
columnDefs={data?.headers}
rowData={data?.result?.content || []}
...props
/>
→ 컬럼 정의(headers)와 데이터(content)는 모두 서버에서 제어하며, 프론트는 공통 Grid 컴포넌트에 매핑하는 역할만 수행하면 된다.
3. Server Driven From
Form JSON
{
"details": [
{
"formLayout": {
"fieldGroups": [
{
"items": [
{
"@type": "COLLAPSIBLE_SECTION",
"collapsed": false,
"label": "",
"formType": "CollapsibleSection",
"level": 1
}
]
},
{
"items": [
{
"@type": "FIELD",
"name": "actionId",
"label": "Action Id",
"formType": "TextField",
"readOnly": true
}
]
}
]
},
"content": { "actionId": 1 }
}
]
}
ServerDrivenForm 컴포넌트
export function ServerDrivenForm({ methods, data }: Props) {
return (
<FormProvider {...methods}>
<form>
{data?.fieldGroups.map((group, gi) => (
<Flex key={gi}>
{group.items.map((item, ii) =>
item.formType === 'CollapsibleSection'
? <CollapsibleSection key={ii} {...item} />
: <ControlledForm key={ii} {...item} control={methods.control} />
)}
</Flex>
))}
</form>
</FormProvider>
);
}
컴포넌트 매핑
export function ControlledForm(props: ControlledFormProps) {
switch (props.formType) {
case 'TextField': return <ControlledTextField {...props} />;
case 'BasicSwitch': return <ControlledBasicSwitch {...props} />;
...
default: return <span>{props.formType} 없음</span>;
}
}
4. 백엔드/프론트엔드의 역할
백엔드
1) 화면 스펙(JSON)을 정의: 컬럼, 필드, 라벨, 섹션 상태 등
2) 요구사항 변경 시 API 응답만 수정하면 된다. → 프론트 배포 불필요
프론트엔드
1) 서버에서 내려준 JSON을 해석하고, 재사용 가능한 컴포넌트로 매핑
2) 테이블, 폼, 토글 등은 공통 컴포넌트만 구현하면 화면별 중복 구현 불필요
5. 활용 경험
운영팀, 기획팀에서 필드 추가/변경 요청이 잦았고, 그때마다 슬랙 요청 → 프론트 수정 → 서버 수정 → 양쪽 모두 배포라는 번거로운 과정을 거쳐야 했다.
서버드리븐 방식을 적용한 이후에는 서버 응답(JSON)만 수정하면 되었고, 프론트 배포 과정을 생략할 수 있어 운영 효율이 크게 향상되었다.
새로운 타입 추가나 인터페이스 변경 같은 근본적 수정이 필요한 경우에는 초기 커뮤니케이션 비용이 컸고, 이 부분이 중요했다.
6. Server Driven Form 내 CollapsibleSection 개발
내가 받은 요구사항은
“서버드리븐 폼을 나타내고, on/off 가능한 섹션을 만들어주세요.”
기존에 있던 서버드리븐폼 파일을 참고하면서 해결 방법을 고민했다.
고민 포인트 1 - JSON 구조 유지 vs 필드 확장
처음에는 아예 새 구조를 생각했지만, 기존 서버드리븐폼 JSON 구조를 최대한 유지하고 싶어 새로운 타입 추가를 통해 확장성을 확보할 수 있었다.
고민 포인트 2 - 섹션 열림 여부 제어를 어느쪽에서?
요구사항으로 필수 섹션 / 참고 섹션(정보량이 많아 초기엔 닫힘)의 구분이 필요했다. 단순히 프론트에서 제어하면, 섹션 초기 열림 여부를 바꿀 때마다 프론트 배포가 필요하기에 비효율적이었다. JSON에 collapsed 필드를 추가하여 서버에서 제어하도록 변경하였다.
고민 포인트 3 - 계층(depth) 확장 가능성
요청사항의 설계서에는 depth가 1, 2 까지만 존재했지만, 추후 depth가 더 깊어진다면? 혹은 비슷한 요구사항이 있을때 이 컴포넌트를 재활용해야한다면? level 필드를 추가하여 계층 구조를 확장할 수 있도록 설계하였다. (지금 생각해보면 depth 라는 이름이 더 직관적이지 않았나 하는 아쉬움이..)
아쉬운 점 & 회고
level 필드를 통한 계층 시각화는, 사실상 formType: CollapsibleSection 내부에서 padding을 조정해 아이콘을 표기하는 방식(일종의 트릭)에 불과했다. 섹션 자체가 너비·높이를 갖지 않고, 렌더링 여부 함수에만 의존했기 때문에 구조적으로 깔끔하지 못했다.
2개월 차 시점에 급히 라이브 반영해야 했던 기능이라 설계 단계에서 사수님의 피드백을 충분히 구하지 못한 점이 아쉽다. 당시 적극적으로 피드백을 구했다면, 더 빠르고 안정적인 결과물을 얻을 수 있지 않았을까 싶다. 지금 와서 리팩토링하려니 아쉬움이 크지만, 그래도 이 과정 자체가 재미있다.
'Programming > React' 카테고리의 다른 글
React Quill Text Editor 도입 후 이슈 해결하기 (0) | 2025.09.27 |
---|---|
useOverlay close 시 컴포넌트가 unmount되도록 처리하기 (0) | 2025.09.14 |
React Hook Form 렌더링 최적화 (0) | 2025.06.22 |
React 프로젝트 의존성 버전 업그레이드 하기 - axios (0.18.0 → 1.7.7) (1) | 2024.11.21 |
Kakao 소셜 로그인: Local 환경에서는 되고, S3 + Cloudfront 배포 후는 안되는 문제 해결 (1) | 2024.09.23 |