저는 사내에서 Toss POS와 Toss Front의 외부 연동 SDK를 개발합니다.
SDK를 통한 데이터 연동으로 손쉽게 Toss POS와 Toss Front에 내가 원하는 플러그인 앱을 개발해 연동할 수 있습니다. (플러그인 앱 템플릿 코드)
외부 연동사의 개발로써 Toss POS, Toss Front의 그로스를 이뤄내기 위해서는 사용 경험이 좋은 SDK, 플러그인 기능이 필수적입니다. 3rd-party의 연동 개발을 외부 연동사의 개발로 이루어낼 수 있습니다.
그 과정에서 SDK를 개발하고 유지하며 느낀 “SDK 인터페이스 유지보수”에 대한 3가지 기본적인 기준을 적어보려 합니다.
(해당 글은 <Everything I know about good API design> 글에 큰 영감을 받았습니다. SDK 인터페이스 운영은 API 개발 운영과 큰 유사성을 띄는 것을 알게 되었습니다.)
목차
1️⃣ 서론
2️⃣ 1. Semantic Versioning(SemVer)
3️⃣ 2. WE DO NOT BREAK USERSPACE
4️⃣ 3. 불변성에 가까운 설계의 정확성 확보
5️⃣ 마무리
Toss POS와 Toss Front의 데이터를 연동할 수 있는 SDK를 개발하고, 유지보수를 이어가며, 다양한 연동사들에 대한 문의와 이슈 보고를 받습니다.
기민하게 이슈에 대응하고 더 다양한 기능을 제공하고자 하는 과정에서, 다양한 크고작은 어려움을 겪게 되었습니다.
예를 들어, SDK 독립적인 버저닝에 실패하여 신규 피처 개발을 두 브랜치에 따로 개발을 한다거나,
우선순위에 없는 개발 내용을 연동사의 강경 요청에 의해 어쩔 수 없게 지원해드리기도 하였습니다.
이런저런 상황을 겪으며, “그래도 ‘이것’을 꼭 지켜야 SDK가 중심을 잡고, SDK가 중심을 잡아야 프로덕트가 안정적이겠구나” 깨달은 부분들이 있습니다.
이에 대해 기본적인 3가지만 추려 정리해보고자 합니다.
major / minor / patch … 시멘틱한 버전 관리(Semantic Versioning; SemVer)는 필수입니다. 그에 따른 Change Log 또한 필수입니다. 특히, Change Log는 곧 신뢰의 원천입니다.
버전 관리는 SDK를 사용하여 개발하는 연동사에게 버그 패치 및 안정성 향상을 위한 버전 업그레이드를 안내드리는 것에 필수적입니다. 만약 한 가지 버전으로만 이루어진 SDK라면, 단일 버전 SDK의 강한 결합으로 인해, 변경사항을 배포하는 순간 모든 운영중인 플러그인 프로덕트에 영향이 갑니다. 모든 인터페이스를 v1, v2, … 새롭게 추가해야 할 것입니다. 그리고 그 인터페이스를 사용해달라고 곧바로 요청드려야 문제 상황이 해결됩니다.
A 연동사의 요청사항 반영본
B 연동사의 이슈 레이징에 대한 수정본
위와 같은 다양한 작업들이 버전 관리가 안 된 채로 혼합된다면, 문의가 빗발칠 것입니다. “이건 왜 이렇게 바뀌었나요?”
예상할 수 없는 SDK 버전은, 즉 신뢰가 떨어지고 사용할 수 없는 SDK 버전이 되어 즉시 이탈할 것입니다. 개발자들은 안정적인 버전의 SDK를 사용하길 원하기 때문입니다.
내부 안정성을 위한 리팩터링
버전 관리를 진행하고, 내부 코드의 큰 수정사항이 있는 상황을 가정해보겠습니다. 이것이 인터페이스 Breaking Change가 필수적일지라도, 당장 major 버전을 올리고 Change Log를 자세히 작성하여 제공할 수 있습니다.
그와 대불어 마이그레이션 코드 및 AI를 위한 프롬프트까지 제공한다면, 연동사들이 순응하여 버전 업그레이드를 할 확률이 늘어납니다. 이는 곧 레거시 버전의 하위호환성 코드를 제거하는 데에 도움이 됩니다.
SDK 인터페이스를 변경해야 할 때는 어떻게 할까요? 단지 데이터를 가져오는 Getter 함수에, 응답값 필드 하나를 추가하는 것은 전혀 문제가 되지 않습니다.
하지만 필드를 제거하거나 변경해야 할 때 큰 문제가 생깁니다. 연동사 입장에서 내 코드를 “필수적으로” 수정해야 하는 Breaking Changes가 생기는 것이죠.
예를 들어 내가 order.priceValue를 사용하고 있는데, 버전을 올렸을 때 해당 코드가 문제가 생긴다면 매우 당황스럽습니다. 다행히 Change Log를 보니 order.priceValue → order.price.value 마이그레이션 가이드를 발견하고 이를 수정할 수 있습니다. 여기에 QA 일정이 필수적인 조직이라면, 버전 업그레이드를 차라리 안해버리는 선택을 할 수도 있습니다.
그 코드를 사용하는 개발자들이 이를 버그로 인지하여 문의할 것이고, 극단적으로는 그들의 소프트웨어를 망쳤다고 보상을 요구할 수도 있습니다.
여기서 원칙은, Linus Torvalds의 유명한 슬로건인 ”WE DO NOT BREAK USERSPACE”와 비슷합니다. SDK의 유지보수자로서, 저희는 하위 소비자에게 해를 끼치지 않도록 하는 “의무”가 있습니다. 단순히 약간 어색하다는 이유만으로 인터페이스를 변경해서는 안 됩니다.
고려해볼 수단으로는 v2 SDK(Interface Versioning)를 제공하는 것이 있습니다. (조금은 책임 전가의 시선으로 보일 수 있습니다.)
인터페이스 변경 없이 차라리 v2를 제공한다면, 아래와 같이 상대적으로 부드러운 인터페이스 스위칭(Graceful Deprecation)이 가능합니다.
SDK의 fn 대신 fn-v2 제공 및 공지
(연동사) fn-v2 인터페이스로 SDK version up
모든 연동사가 fn-v2 인터페이스로 마이그레이션 한다면, 기존 fn 인터페이스를 위한 하위호환성 코드 제거
버전 관리는 최선의 경우 필요악지만, 그것 역시 악입니다.
연동사들의 코드 마이그레이션의 여부로 레거시 코드를 청산할 수 있는지 여부가 달라집니다. 연동사들은 이를 위해 버전업을 강행할 이유는 없습니다. 이에 대한 시선 차이가 생길 수 있는데, 이 상황은 유지보수자로서는 최악입니다.
만약 이미 여러 개의 버전을 관리하는 SDK 함수가 있다면, 새 버전을 추가할 때마다 n가지의 하위호환성(Backward Compatibility)을 고려해야 합니다. 그리고 n의 경우의 수가 늘어날수록 테스트와 디버깅에 인지과부화가 오기 쉽습니다. 최대한 지양하는 것이 좋습니다.
SDK 개발은 서버개발자의 API 인터페이스와 같이, 한 번 제공되면 되돌리기가 쉽지 않습니다.
따라서, 개발의 속도보다 결정의 정확성이 더욱이 중요한 영역입니다. 즉, 불변성(Immutability)에 가까운 설계를 지향해야 합니다.
외부의 압박에 시달려 더 빠르게 SDK 기능을 제공하는 것은 지양해야 합니다. 인터페이스가 더 오래 유지될 “정답”을 찾아 고민하는 시간이 더 중요합니다. 이를 위해서는, 비즈니스 모델의 불변적인 핵심을 f/u하고, SDK에 반영하는 것이 필요합니다.
이를 위해서는 ‘나’와 ‘팀의 코드 정책’ 대한 확신을 가지는 것이 선행되어야 한다고 느낍니다. 그러기 위해서는 더 깊게 고민하는 것이 필요할 것 같습니다.
또한, 오래가는 SDK 인터페이스를 구상하기 위해 다양한 레퍼런스와 더 좋은 코드를 많이 체화하는 학습이 필요합니다. 개인적인 노력이 꾸준하게 필요할 것 같습니다.
3가지를 정리해보면 아래와 같습니다.
Semantic Versioning(SemVer)
WE DO NOT BREAK USERSPACE
불변성에 가까운 설계의 정확성 확보
Toss POS와 Toss Front 제품에 SDK를 통한 플러그인 앱을 지원한지 긴 시간이 지나지 않았습니다. 따라서, 인사이트는 유지보수자(저)의 고통과 비례하며 계속해서 쌓아질 예정입니다. 그에 따라 더 Good Design이 된 SDK를 제공드릴 것이고, 그에 따라 더 플러그인 앱 활성화가 활발해지기를 바랍니다.
감사합니다.