swift

2 개의 포스트

AttributedString 구조로 풀어낸 대규모 iOS 설정 시스템 (새 탭에서 열림)

LINE iOS 앱의 성장으로 인해 기존의 일체형 서비스 설정 시스템은 의존성 관리, 안정성, 개발 생산성 측면에서 한계에 봉착했습니다. 이를 해결하기 위해 LINE은 각 모듈이 독립적으로 설계를 정의하면서도 타입 안전성을 확보하고, 동시성 환경에서도 안전하게 작동하는 새로운 아키텍처로의 전환을 시도했습니다. 특히 Apple의 `AttributedString` 설계 방식을 벤치마킹하여 대규모 프로젝트에 적합한 확장성 있는 설정 관리 체계를 구축하고자 했습니다. **서비스 설정 시스템의 역할과 구조** * LINE은 2주마다 정기 배포를 진행하므로, 개별 서비스의 신규 기능 출시나 롤백을 앱 업데이트 없이 수행하기 위해 '서비스 설정' 시스템을 활용합니다. * 서버는 사용자의 지역, 기기, OS 버전 등에 따라 최적화된 설정값을 문자열 형태의 키-값 쌍(JSON)으로 클라이언트에 전달합니다. * 이 시스템은 기능 토글뿐만 아니라 A/B 테스트, 오류 수집 샘플링 비율 조정, UI 정책 결정 등 다양한 용도로 사용되며 현재 약 700개의 키가 운용되고 있습니다. **일체형 구조로 인한 순환 의존성 딜레마** * 과거에는 모든 설정 키를 단일 파일에서 관리했으나, 프로젝트 규모가 커지며 해당 파일이 7천 줄에 달하는 등 관리가 어려워졌습니다. * 설정 시스템이 특정 서비스 모듈의 전용 타입(예: 사진 품질 타입)을 반환하려 하면 모듈 간 순환 참조가 발생하여, 결국 타입 안전한 객체 대신 날것의 문자열을 노출하고 각 모듈에서 매번 파싱해야 하는 비효율이 발생했습니다. **불완전한 추상화와 구현 세부 사항의 노출** * 서버 규약에 따라 불리언 값을 "Y"/"N" 문자열로 처리해야 했고, 이를 위해 `decodeBoolIfPresent` 같은 비표준 메서드를 별도로 구현해야 했습니다. * 이 과정에서 표준 메서드와의 혼동으로 인한 버그가 잦았으며, 용도가 미묘하게 다른 기본값을 세 번이나 중복 정의해야 하는 설계 결함이 존재했습니다. * 이러한 복잡성은 신규 개발자에게 암기 위주의 온보딩 지식을 강요하여 생산성을 저하시켰습니다. **스레드 안전성 부재로 인한 런타임 오류** * 기존 시스템은 동시성을 고려하지 않고 설계되어, 여러 스레드에서 설정값을 읽는 과정에서 지연 평가 및 인스턴스 해제 타이밍이 겹치는 문제가 있었습니다. * 이로 인해 메모리 해제 후 사용(use-after-free) 오류가 발생하여 매일 수백 건의 크래시가 기록되는 등 앱 안정성에 심각한 영향을 미쳤습니다. **테스트 및 디버깅 효율성 저하** * 시스템 자체에 오버라이드 기능이 없어 QA 과정에서 설정값을 임시로 변경하려면 다수의 파일을 직접 수정해야 하는 번거로움이 있었습니다. * 싱글턴 구조의 의존성 때문에 각 모듈은 테스트를 위해 별도의 프로토콜과 테스트 대역을 각자 만들어 관리해야 했으며, 이는 실제 구현체와의 동작 괴리를 유발하는 원인이 되었습니다. **성장을 위한 설계의 재정립** * 대량의 키-값 쌍을 타입 안전하게 관리하면서도 각 모듈이 독립적으로 키를 정의할 수 있는 구조를 만들기 위해 Foundation의 `AttributedString` 설계를 참고했습니다. * 이는 개별 서비스가 자신의 도메인에 맞는 설계를 독립적으로 확장할 수 있게 하여, 거대해진 프로젝트 규모에 대응할 수 있는 유연한 기반을 마련하는 계기가 되었습니다.

LLM 및 @generateMock (새 탭에서 열림)

에어비앤비는 LLM과 제품 컨텍스트를 결합한 `@generateMock` 지시어를 도입하여, 수동으로 작성하던 GraphQL 모의 데이터 생성 과정을 자동화하고 혁신했습니다. 이 시스템은 단순한 랜덤 값 생성을 넘어 쿼리 정의, 스키마 주석, 그리고 디자인 목업 이미지까지 컨텍스트로 활용해 실제 서비스 환경과 매우 흡사한 타입 안정적(Type-safe) 데이터를 생성합니다. 이를 통해 개발자는 백엔드 구현을 기다리지 않고도 고품질의 데모와 테스트를 수행할 수 있으며, 쿼리 변경에 따른 모킹 데이터의 관리 부담을 획기적으로 줄였습니다. ### 기존 모킹 방식의 한계와 도전 과제 * **수동 작업의 비효율성:** 수백 줄에 달하는 GraphQL 쿼리에 대응하는 JSON 데이터를 직접 작성하고 수정하는 과정은 매우 번거롭고 실수에 취약합니다. * **병렬 개발의 병목:** 서버 스키마가 확정된 후에도 실제 API가 구현될 때까지 클라이언트 개발자는 UI 테스트를 진행하기 어려워 임시방편(하드코딩, 로컬 프록시 등)에 의존하게 됩니다. * **데이터의 동기화 문제:** 쿼리나 스키마가 진화함에 따라 수동으로 작성된 모의 데이터는 점차 실제 프로덕션 환경과 괴리가 생기며, 이는 테스트 신뢰도 저하로 이어집니다. ### @generateMock 지시어를 통한 선언적 모킹 * **지시어 기반 워크플로우:** 개발자는 `.graphql` 파일의 연산, 프래그먼트, 또는 특정 필드에 `@generateMock` 지시어를 추가하는 것만으로 모의 데이터를 정의할 수 있습니다. * **주요 파라미터 활용:** * `id`: 여러 버전의 모의 데이터를 생성할 때 식별자로 사용하며, 생성된 헬퍼 함수의 이름에 반영됩니다. * `hints`: "파리, 교토로 가는 여행 일정을 포함해달라"와 같이 LLM에게 구체적인 데이터 생성을 지시하는 자연어 가이드를 제공합니다. * `designURL`: 디자인 도구(Figma 등)의 URL을 입력하면 LLM이 실제 디자인 화면의 텍스트와 레이아웃에 부합하는 데이터를 생성합니다. * **로컬 개발 도구 통합:** 에어비앤비의 코드 생성 도구인 'Niobe'와 결합되어, 코드 생성 시 JSON 데이터와 이를 로딩하는 소스 코드(TypeScript, Swift, Kotlin)가 자동으로 빌드 아티팩트에 포함됩니다. ### LLM을 활용한 컨텍스트 중심의 데이터 생성 * **스키마 최적화 주입:** 전체 스키마를 LLM에 전달하는 대신, 해당 쿼리와 연관된 타입 및 인라인 문서 주석만을 추출하여 컨텍스트 윈도우 내에서 효율적으로 처리합니다. * **디자인 시각 정보 반영:** 내부 API를 통해 `designURL`의 스냅샷 이미지를 생성하고 이를 LLM에 전달함으로써, 실제 UI 디자인에 명시된 이름, 주소 등의 콘텐츠와 일치하는 현실적인 데이터를 얻습니다. * **수동 수정 및 보존:** 생성된 JSON 데이터는 개발자가 직접 수정할 수 있으며, 이후 다시 코드를 생성하더라도 Niobe는 사용자가 직접 수정한 내용을 지우지 않고 보존하는 지능적인 병합 기능을 제공합니다. 이러한 접근 방식은 단순히 더 나은 가짜 데이터를 만드는 것을 넘어, 프론트엔드와 백엔드 간의 의존성을 분리하고 개발 생산성을 극대화하는 데 목적이 있습니다. 대규모 GraphQL 환경을 운영하는 조직이라면 스키마 메타데이터와 LLM을 결합하여 테스트 자동화 수준을 한 단계 높이는 이 모델을 참고할 가치가 있습니다.