오늘의집 Serving Platform을 통해 비효율성 해소하기
개발 생산성과 시스템 안정성 강화를 위한 여정
2024년 11월 4일Serving Platform Working Group

오늘의집은 다양한 서비스 간 데이터를 통합하고 관리하는 과정에서 여러 도전에 직면해 왔습니다. 그중에서도 서비스마다 클라이언트와 서버 간 통신 방식과 관리 주체가 달라 비효율성이 발생했는데요. 이는 MSA(Microservice Architecture) 기반의 각기 다른 데이터 요청과 응답 방식에서 비롯된 문제였습니다.

오늘의집 Serving Platform Working Group과 Service Components팀은 이러한 문제점을 해결하기 위해 Mobile API(BFF), Super Root로 구성된 Serving Platform을 구축하여 운영하고 있습니다. 이 플랫폼은 통합된 데이터 요청 및 응답 구조를 통해 비효율성을 해소하며, 다양한 기능의 빠른 기능 테스트 및 배포 환경을 조성한다는 강점이 있는데요. 이를 통해 개발 생산성을 높이고 시스템 안정성을 강화하는 데 중점을 두고 있습니다.

이번 글에서는 Serving Platform의 설계 방향과 실제 적용 사례를 소개하고, 향후 계획을 말씀드리고자 합니다.


Architecture

Serving Platform Overview

Serving Platform은 크게 Mobile API와 Super Root로 구성되어 있습니다.


Mobile API

Mobile API는 앱과 서버 간의 단일 엔드포인트로 Backend for Frontend(BFF) 역할을 수행합니다. 클라이언트가 요청한 페이지의 모듈을 구성하고, 필요한 데이터를 Super Root 혹은 Corpus Root로부터 받아서 렌더링에 적합한 형태로 가공하는 역할을 합니다.


Super Root

기존에도 오늘의집에는 분산된 MSA에서 정보를 모아오는 여러 개의 aggregator 서비스가 존재했습니다. 하지만 서비스마다 관리자와 요청 방식이 달라서 비효율적인 부분이 많았습니다. 이에 Super Root는 이러한 aggregator들을 하나로 통합해 효율적인 교통 정리 플랫폼이 되는 것을 목표로 했습니다.

Application에서는 사용자에게 보여지는 개별 화면(예: 홈, 통합 검색 페이지)마다 노출되는 데이터가 다르므로, 페이지별로 Page Request Handler가 따로 등록되어 있습니다. 한 페이지에는 여러 개의 독립적인 섹션 또는 모듈(예: 베스트 상품 섹션, 광고 섹션)이 노출됩니다. 이때 Page Request에서 들어온 모듈 요청은 기존 요청을 확장하거나 새로 생성한 후에 Data Collection Service라는 aggregation 서비스를 통해 처리합니다. Data Collection Service는 모듈 요청들을 각 백엔드로 보내고, 응답을 수집하여 필요한 데이터를 제공합니다. 또한 공통 정보(사용자 정보, 디버그 출력, Feature Flags 등)를 담은 Page Request Context를 생성하여 모든 모듈이 해당 정보를 사용할 수 있도록 합니다.

페이지마다 비슷한 데이터가 노출되더라도 로직이 조금씩 다를 수 있기 때문에, 각 지면에 맞는 pre/post processing 로직을 Page Request Handler에서 설정할 수 있습니다. 예를 들어 검색 화면에서는 데이터를 가져오기 전에 자동으로 검색어를 교정하거나, 데이터를 가져온 후에는 사용자가 차단한 콘텐츠를 필터링할 수 있는 로직이 적용됩니다.

구현 세부 사항

Mobile API

Mobile API의 주요 특징은 다음과 같습니다.

Feature flag를 이용한 신규 Feature의 점진적 배포

Feature flag를 활용하면 신규 기능을 안전하게 배포하고, 서비스 안정성을 유지하면서도 유연한 기능 배포가 가능합니다. 이를 통해 실험적인 기능을 리스크 없이 테스트하고, 필요시 빠르게 롤백할 수 있습니다.

다국어 처리

오늘의집은 글로벌 서비스를 지원하기 위해 i18n과 Spring Framework LocaleContextResolver를 활용해 다국어 처리를 제공합니다. 이로써 글로벌 사용자에게 일관된 경험을 제공하고, 손쉽게 다국어 서비스를 확장할 수 있습니다.

Server-Driven UI를 이용한 동적 UI 구성

Server-Driven UI(a.k.a SDUI)는 ‘서버가 UI를 결정하고, 클라이언트는 서버에서 전달받은 데이터에 따라 UI를 렌더링’하는 방식입니다. 이 방식은 클라이언트 코드의 변경 없이 UI를 실시간으로 업데이트할 수 있는 장점이 있습니다. 오늘의집의 Mobile API는 SDUI를 통해 다양한 페이지를 서버에서 동적으로 구성하고, 클라이언트에서 유연하게 UI를 반영하여 사용자에게 최적의 경험을 제공합니다. 그 결과 새로운 기능을 빠르게 테스트하고 배포할 수 있습니다.

Response 예시
Response 예시
{ "body": { "id": "commerce_today_deal", … "components": [ { "image": { "style": "banner", "url": "/uploads/banners/today_deal/171134828224593309.jpg", … }, "type": "image" }, { "tabContainer": { "style": "tab_sticky_scroll", "tabs": [ { "tab": { "highlighted": null, "label": "전체", "action": "select", "backgroundColor": null, "selectedColor": null, "target": "commerce_today_deal_all", "iconNormal": "/uploads/category/store_home_categories/170176089027898655.png", "selected": true … }, "log": { "object": { "url": "https://ohou.se/commerces/today_deals", … }, "categories": [ "click" ] }, "type": "tab" }, { "tab": { … }, "type": "tab" }, …

Super Root와의 gRPC 통신

gRPC를 통해 Super Root 시스템과의 효율적인 통신을 수행합니다. gRPC는 고성능 원격 프로시저 호출(Remote Procedure Call) 프레임워크로, 서버와 클라이언트 간의 빠르고 신뢰성 있는 통신을 제공합니다. 결과적으로 대량의 데이터를 신속하게 처리하고, 낮은 지연 시간을 기반으로 복잡한 연산을 분산시킬 수 있습니다. Super Root와의 gRPC 통신을 활용함으로써 시스템 간 통합이 원활하게 이루어지며, 클라이언트는 안정적인 서비스를 지속적으로 제공받을 수 있습니다. 이를 통해 서비스의 확장성과 성능을 극대화할 수 있습니다.


Super Root

Super Root의 주요 특징은 다음과 같습니다.

작업량 감소

Super Root는 기존 코드 구조를 재사용하여 손쉽게 페이지와 필요한 데이터를 추가할 수 있으며, 이는 작업 흐름을 간소화하고 개발 시간을 크게 단축시킵니다. 또한 기존에 구현된 데이터 연동 및 비즈니스 로직을 활용할 수 있어 코드 중복을 줄이고, 신규 기능이나 페이지가 추가될 때 발생할 수 있는 잠재적인 오류를 최소화할 수 있다는 장점이 있습니다. 그 결과 새로운 요구사항에 대한 대응이 더 신속해지며, 개발자가 반복적인 작업에서 벗어나 더 중요한 로직에 집중할 수 있게 됩니다.

통일된 인터페이스

Super Root는 요청/응답에 표준화된 인터페이스를 제공합니다. 또 코드 재사용성을 늘리고 개발자 간 원활한 커뮤니케이션을 할 수 있게 만들어 줍니다.

message Doc { string id = 1; user.v1.User writer = 2; repeated common.Comment comments = 10; common.Reaction reaction = 11; ... }

코드 확장성 강화

Super Root는 재사용성을 고려한 설계를 적용하여 기본적인 설정만으로 아래와 같은 여러 요구사항에 유연하게 대응할 수 있어 실수를 줄이고 서비스의 일관성과 안정성을 높일 수 있습니다.

  • timeout 설정
  • circuit breaker 적용
  • fallback 처리
  • 좋아요, 스크랩 등 공통 UI 설정
  • 정렬, 필터 기능 등

이러한 설정들은 복잡한 코드 작성 없이도 자동으로 처리되어 예외 상황에서의 오류를 최소화하는 등 시스템을 안정적으로 유지할 수 있게 해줍니다.

일관된 사용자 경험

Super Root는 모든 데이터를 한 곳에서 관리하여 사용자에게 일관된 경험을 제공합니다. 기존에는 aggregator마다 MSA로의 요청이 제각각이기에 데이터가 일관적이지 않았습니다. Super Root를 통해 각 서비스에서 발생하는 데이터를 표준화해 오류를 줄이고, 안정적인 서비스를 제공함으로써 일관된 사용자 경험을 제공할 수 있습니다.


적용 예시

개발 UI 시나리오

다음과 같이 작성자 프로필, 이미지, 상품 정보, 리액션, 댓글로 구성된 Feed Content라는 모듈과 상품 정보만으로 구성된 광고 카루셀 모듈로 구성된 AWESOME PAGE라는 신규 스펙을 구현하고자 합니다.


1. Proto 파일에 신규 페이지 정보 추가: 예시를 위해 AWESOME_PAGE라는 새로운 페이지 정보를 Proto 파일에 추가합니다.

enum PageType { PAGE_TYPE_UNSPECIFIED = 0; HOME = 1; INTEGRATED_SEARCH = 2; SEARCH_RESULTS = 3; CATEGORY_LISTING = 4; AWESOME_PAGE = 5; // 신규 페이지 }

2. 세부 설정 추가: AWESOME_PAGE에 대한 구체적인 설정을 YML 파일에 추가합니다. 이 설정에는 circuit breaker, cache 등이 포함됩니다.

page: AWESOME: timeout: 4500 // page 전체 타임 아웃 설정 module-configs: default: timeout: 2200 // 모듈별 타임 아웃 설정 max-concurrent-calls: 100 // 최대 동시 실행 가능한 모듈 수 circuit-breaker-config: // circuit breaker 설정 failure-rate-threshold: 50.0 wait-duration-in-open-state-millis: 500 sliding-window-size: 1000 permitted-number-of-calls-in-half-open-state: 1000

3. 필터 추가: 상품 정보 조회 및 차단된 사용자의 콘텐츠 필터링을 위해 관련 필터를 추가합니다.

@Configuration class AwesomePageConfig { @Bean("Awesome") fun pageConfiguration(): PageConfigurationCustomizer = object : PageConfigurationCustomizer { override val pageType = PageType.AWESOME override fun customize(pageConfigBuilder: PageConfigBuilder) = with(pageConfigBuilder) { pageFilterConfig(FeedBlockFilterPageFilter::class) // 차단 필터 pageFilterConfig(FeedResolverPageFilter::class) // 응답 필터 } }

4. Mobile API에서 페이지 호출: 마지막으로 Mobile API를 통해 해당 페이지를 호출하고 데이터를 가져옵니다.

{ "page_type": "AWESOME_PAGE", "module_requests": [ { "module_type": "CARD_COLLECTION_MODULE" }, { "module_type": "ADS_PRODUCT_MODULE", "module_request_id": "", "ads_product_request": { "size": 20 } } ] }

이러한 절차를 통해 요구사항들을 간단한 코드 작업으로 대응할 수 있습니다. 예를 들어, 통합 검색 서비스의 경우 기존에 복잡한 정렬, 검색어 교정 등의 도메인 로직이 산재해 있어 개발이 복잡했지만, Super Root기반의 Mobile API를 활용해 로직의 가시성도 높이고 간단하고 빠르게 개발이 가능했습니다.

마무리하며

  • 기능 확장 및 최적화: 현재 운영 중인 Mobile API와 Super Root의 기능을 더욱 확장하고, 성능 최적화를 통해 대규모 트래픽 처리와 안정성을 강화할 예정입니다.
  • 글로벌 서비스 지원 강화: 다국어 처리와 글로벌 사용자 경험을 개선하기 위해 i18n 확장 및 지역별 요구 사항을 반영한 기능을 추가할 계획이며 현재 프로퍼티로 관리되어 있는 메시지 파일들을 Admin을 통해 관리할 수 있도록 기능을 강화하고자 합니다.
  • 자동화된 테스트 및 배포 환경: 많은 도메인에서 Serving Platform을 사용함에 따라 보다 빠르고 안전한 배포를 위해 자동화된 회귀 테스트 및 배포 프로세스를 도입하여 배포 주기를 단축하고 서비스 안정성을 확보할 예정입니다.


오늘의집 Serving Platform Working Group과 Service Components팀은 복잡한 문제를 해결하는 과정을 즐기고, 빠르게 변화하는 환경 속에서 더 나은 방법은 없을지 끊임없이 고민하며 나아가고 있습니다. 함께 몰입하고 성장하며 새로운 가능성을 열어갈 동료분들을 기다리고 있겠습니다. 감사합니다!

오늘의집에서 당신을 찾고 있습니다!
Technical Lead & Manager, BackendTechnical Lead & Manager, FrontendTechnical Lead & Manager, AndroidSenior Software Engineer, BackendSenior Software Engineer, FrontendSenior Software Engineer, Machine LearningSenior Software Engineer, Machine Learning, XRSoftware Engineer, BackendSoftware Engineer, Backend, XRSoftware Engineer, Backend, AdsSoftware Engineer, DataSoftware Engineer, FrontendSoftware Engineer, Frontend, XRSoftware Engineer, Machine LearningQA Engineer, AutomationDatabase Administrator
목록으로 돌아가기