3P by GN⁺ | ★ favorite | 댓글 1개
  • Increase는 API 리소스가 사용자의 제품 이해를 좌우한다고 보고, 결제 네트워크의 복잡성을 숨기기보다 드러내는 No Abstractions 원칙을 채택함
  • Stripe식 추상화는 빠른 통합에 강하지만, Increase 사용자는 payment network 지식을 바탕으로 직접 연결과 깊은 통합을 원함
  • API는 Nacha specification 같은 기저 네트워크 용어를 그대로 쓰고, ACH transfer의 진행 과정을 불변 하위 객체로 모델링함
  • 사용자가 할 수 있는 액션이 크게 다르면 ach_transferinbound_ach_transfer처럼 리소스를 분리해 처음에는 장황해도 장기적으로 예측 가능성을 높임
  • 추상화 수준은 통합 개발자의 도메인 경험과 투입 의지에 맞춰 정해야 하며, 낮은 추상화를 택했다면 이후에도 그 원칙을 유지해야 함

API 리소스는 사용자의 멘탈 모델을 만든다

  • API resource는 API의 명사이며, 이름과 모델을 정하는 일은 API 설계에서 가장 어렵고 중요한 부분에 속함
  • 어떤 리소스를 노출하느냐가 사용자가 제품의 동작 방식과 가능한 작업을 이해하는 멘탈 모델을 구성함
  • Increase는 이 판단을 돕기 위해 “No Abstractions”라는 설계 원칙을 사용함
  • Stripe식 추상화와 Increase의 차이

    • Stripe는 복잡한 결제 도메인을 사용자가 쉽게 다룰 수 있는 API로 추출하는 추상화에 강점이 있음
    • 여러 결제 네트워크를 PaymentIntent라는 API resource로 모델링하고, Visa와 Mastercard의 chargeback reason code 차이를 하나의 enum으로 합쳐 사용자가 두 네트워크를 따로 고려하지 않아도 되게 함
    • Stripe 사용자 상당수는 결제 자체가 아닌 제품을 만드는 초기 스타트업이며, 신용카드 세부 사항을 깊이 알기보다 빠르게 통합한 뒤 본래 제품 개발로 돌아가려 함
    • Increase 사용자는 payment network에 대한 기존 지식이 깊고, 금융 기술을 계속 다루며, 직접 네트워크 연결과 깊은 통합을 위해 Increase를 사용함
    • 이들은 FedACH window가 언제 닫히고 transfer가 언제 도착하는지 정확히 알고 싶어 하며, ACH transfer의 Standard Entry Class code가 달라지면 return timing도 달라질 수 있음을 이해함
    • ACH transfer와 wire transfer를 하나의 API resource로 묶어 기저 네트워크 복잡성을 숨기면, Increase 사용자에게는 단순화가 아니라 불편함이 됨

No Abstractions가 API에 드러나는 방식

  • 실제 네트워크 용어 사용

    • Increase는 API resource와 attribute 이름을 새로 만들기보다 기저 네트워크의 어휘를 사용하는 편임
    • ACH transfer를 API로 만들 때 노출하는 parameter는 Nacha specification의 field 이름을 따름
  • 불변 리소스와 lifecycle object

    • 리소스도 실제 세계의 이벤트나 메시지에 맞춰 모델링하며, 이 접근은 더 많은 API resource를 불변으로 만들게 됨
    • ACH transfer lifecycle에서 보낼 수 있는 네트워크 메시지들의 묶음처럼, 불변 리소스들을 state machine 형태의 lifecycle object 아래에 그룹화함
    • ach_transfer object는 시간이 지나며 바뀌는 status field와, lifecycle 진행에 따라 생성되는 여러 불변 sub-object를 가짐
    • ach_transferstatuspending_approval이고 approval, submission, acknowledgementnull일 수 있음
    • FedACH에 제출된 뒤에는 statussubmitted가 되며, approval, submission, acknowledgement가 각각 승인·제출·확인 시점의 불변 정보로 채워짐
    • submission에는 trace_numbersubmitted_at 같은 값이 포함됨
  • 사용 사례별 리소스 분리

    • 같은 API resource라도 instance별로 가능한 액션 집합이 크게 다르면 Increase는 이를 여러 리소스로 나누는 편임
    • originated ACH transfer와 received ACH transfer에서 가능한 액션은 사실상 정반대라서, ach_transferinbound_ach_transfer로 분리함
    • 이 방식은 API 문서 왼쪽에 많은 리소스가 보일 만큼 처음에는 더 장황하고 위압적으로 보일 수 있음
    • 대신 장기적으로는 리소스와 액션의 관계가 더 예측 가능해짐

원칙은 작은 설계 결정을 줄인다

  • 복잡한 API를 여러 해에 걸쳐 설계하다 보면 작은 결정이 계속 발생하며, 초기에 세운 기반 원칙이 이런 결정의 인지 부하를 줄임
  • wire transfer를 Federal Reserve로 보낼 때 필요한 Input Message Accountability Data는 해당 transfer의 전역 고유 ID 역할을 함
  • 추상화가 많은 API라면 엔지니어가 이를 더 “사용자 친화적”으로 trace_number, reference_number, id 중 무엇이라 부를지 고민할 수 있음
  • Increase에서는 field 이름을 input_message_accountability_data로 정하고 넘어감
  • 사용자가 이 field를 처음 볼 때 즉시 알아보기 쉬운 이름은 아닐 수 있지만, 기저 시스템과 어떻게 매핑되는지 바로 이해하는 데 도움이 됨

추상화 수준을 정할 때의 기준

  • No Abstractions는 모든 API에 맞는 원칙이 아님
  • 적절한 추상화 수준은 통합 개발자의 도메인 경험, 제품 영역에 대한 이해, 통합에 투입할 에너지에 따라 달라짐
  • 추상화가 많은 API를 만들면 새 기능을 추가하기 전에 깊이 고민해야 함
  • 추상화가 적은 API를 만들면 그 방향에 커밋하고, 추상화를 추가하려는 유혹을 견뎌야 함

댓글과 토론

Hacker News 의견들
  • 항상 둘 다 제공할 수도 있음
    세밀한 제어가 가능하지만 깊은 전문성이 필요한 저수준 API를 제공하고, 그 위에 흔한 사용 사례를 몇 가지 단순한 작업으로 매핑하는 고수준 API를 만들면 됨. 어차피 일부 고객은 이런 고수준 계층을 어설프게 직접 구현하고 있을 수도 있음
    두 계층을 깔끔히 분리하면 저수준 API에 추상화를 넣거나, 고수준 API에 흠집과 특수 사례를 추가하라는 압력이 줄어듦. 고객이 그런 걸 원하면 이미 다른 API에 존재하기 때문임
    고객이 한 계층에서 다른 계층으로 이동하는 법을 배울 수 있는 자료까지 제공하면 더 좋음. 결제망 내부 구조를 아직 깊이 알지는 못하지만 그 방향으로 성장하려는 고객도 끌어들일 수 있음

    • 드문 복잡한 경우를 처리할 수 있는 저수준 API와, 그 위에 만든 흔한 경우용 단순한 고수준 API가 있어야 함
      오늘 Web File System API를 쓰는데, 문자열 하나를 파일에 쓰는 데 함수 호출이 7번 필요했고 대부분 비동기였음. 에러 처리도 포함되지 않고, 워커에서 해야 하며 워커 설정 자체도 비슷하게 번거로움. IndexedDB, WebRTC, 평범한 DOM 조작에서도 비슷한 끔찍함을 볼 수 있고, Vulkan, DirectX, ffmpeg는 훨씬 더 심함
      온갖 특수한 경우를 처리하려면 복잡성이 어느 정도 정당화되지만, 대부분의 경우는 그런 특수 사례가 아님
      API 설계는 먼저 흔한 경우에 API 사용 코드가 어떻게 보이는지 스케치하는 것에서 시작해야 하고, 그 경우들은 가능한 한 단순해야 함. 예를 들어 fetch API는 꽤 잘했고, XMLHttpRequest는 전혀 그렇지 않았음
      https://developer.mozilla.org/en-US/docs/Web/API/FileSystemS...
      모든 Web API를 위한 통합된 편의 계층 API가 있으면 좋겠다고 여러 번 생각했음. 모든 강력한 기능을 일관된 “표준 라이브러리” 래퍼로 감싸고, 최소한 가장 흔한 사용 사례를 지원하는 식임. 현대 브라우저는 매우 강력하지만 각 API 설계가 제각각이고 불필요하게 배우거나 쓰기 어려워서 그 힘이 잘 알려지지 않거나 덜 쓰임
      DOM에 대해 jQuery가 했던 것과 비슷하지만, 마법은 줄이고 부가 기능도 덜한 형태가 좋겠음. node.js는 어느 정도 일관된 API가 있지만 조금 낡았고, 예를 들어 Promise 지원이 들쭉날쭉함. Python이 “파이썬다운” API를 추구하는 방식과도 비슷함
    • 원하는 고수준 API를 라이브러리 밖에서 구현할 수 있을 때 이 패턴이 특히 마음에 듦. 그러면 저수준 API가 충분히 유연한지 확인할 수 있고, 스스로도 사용자 입장에서 자기 API를 직접 쓰게
      도구의 내부 구현 관점에 익숙해지다 보면 사람들이 실제로 어떻게 쓰는지 잊기 너무 쉬움
    • Git이 이런 예임
      branch와 checkout 같은 고수준 “porcelain” 명령이 있고, commit-tree와 update-ref 같은 저수준 “plumbing” 명령이 있음
      https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
    • .NET도 이런 방식을 많이 씀. 최근 파일 입출력을 다룬 개발 블로그 글이 있음: https://devblogs.microsoft.com/dotnet/the-convenience-of-sys...
    • 그 대신 API 표면적이 두 배가 되므로 고려해야 할 절충점임. 많은 경우에는 올바른 결정이 될 수 있음
  • Increase가 왜 다른 접근을 택했는지 설명한 부분이 좋음. 기본적인 것을 설계할 때는 맥락이 매우 중요한데, 보통 사람들이 이를 충분히 인정하지 않음

  • 여기서 “추상화 없음”은 사실상 기반 시스템의 용어를 그대로 쓰라는 뜻이고, 일반적으로 좋은 이름 짓기 원칙임
    문제는 시간이 지나면서 기반 시스템이 여러 개가 되고, 같은 것에 서로 다른 이름을 붙이거나 더 나쁘게는 같은 이름을 서로 다른 것에 쓰기 시작할 때 필연적으로 생김. 이 예에서 기반 결제 제공자들의 모델이 다르면 어떻게 할까? 또 Federal Reserve가 Input Message Accountability Data를 폐기하고 새 개념으로 바꾸면 어떻게 할까?
    결제 업계는 운송이나 네트워크 프로토콜보다 훨씬 단순할 수도 있음. X.25 기반 패킷 교환 제품을 만들었다가 나중에 TCP/IP도 지원하려 한다면 올바른 추상화는 무엇일까?

    • 꼼꼼히 읽어줘서 고마움
      폐기 문제는 운 좋게도 기반 시스템이 크게 바뀌지 않아서 괜찮음. Input Message Accountability Data는 사라지지 않을 것임. 하지만 예를 들어 Visa뿐 아니라 Mastercard에서도 카드를 발급하기 시작하면 충돌을 겪게 될 것임
      몇 가지 추상화를 실험해 보기도 했고, 그 지점에서도 그럴 수 있음. 계속 지켜온 규칙 하나는 “기반 객체”는 추상화하지 않고, 편의를 위해 더 높은 수준의 조합을 도입하는 것임. 예를 들어 “Card Payment”라는 것은 실제로 존재하지 않음(https://increase.com/documentation/api#card-payments). 관련 카드 승인과 정산 메시지를 묶는 방식일 뿐임. 하지만 사용자에게 매우 유용하고 대사를 직접 하기는 쉽지 않아서 시도해 봤음. 다만 기반 네트워크 메시지, 즉 “기반 객체”와 모든 원래 필드도 API에서 접근 가능해야 한다고 봄
      아쉽게도 내가 작업한 공개 API는 100% 결제 분야라 다른 관점이 있었으면 좋겠음
    • 글에서는 “비슷한 객체를 통합하지 않는다”는 뜻도 명확히 말하고 있고, 그게 이름 짓기 결정을 가능하게 함
    • “기반 시스템의 용어를 그대로 쓴다”는 건 도메인 주도 설계와 조금 비슷하게 들림. 다만 이 경우의 “기반 시스템”은 실제 비즈니스 도메인이라기엔 구현 중심에 조금 치우쳤을 수 있음
      DDD에서는 보통 비즈니스 도메인이 이미 만든 이름과 개념 모델을 따름. 자신만의 “개선된” [0] 모델이나 용어를 도입하려 하면 마찰과 오해가 생기고, 통합 버그 가능성이 늘며, 수십 년 또는 수백 년 동안 검증된 전문 지식을 무시하게 됨
      [0] https://xkcd.com/793/
  • 글이 좋음
    Stripe를 좋아한다면, 나도 디자이너이자 기술 창업자로서 Stripe의 단순함과 프런트엔드 역량이 놀랍다고 보는데, 그들을 보며 단순화하고 완성도 높은 경험을 제공하는 능력을 따라 하려 할 수 있음
    하지만 Stripe의 진짜 숙련도는 고객을 잘 안다는 데 있음. 그리고 고객이 갈망하는 단순함도 잘 앎
    이 글로 보면 Increase도 마찬가지로 보이고, 고객이 무엇을 필요로 하는지에 비슷하게 날카롭게 집중해 훌륭한 제품 설계 지침을 만든 것 같음. 고무적임

    • Stripe가 API와 팀을 만드는 방식: https://www.youtube.com/watch?v=IEe-5VOv0Js
    • Stripe API에서도 “이걸 잠재적으로 보편적으로 만들자”와 “이건 아마 한 시장의 한 결제 수단에만 적용될 것이라고 받아들이자” 사이의 긴장이 보이는 지점들이 있음
      개인적으로는 후자가 일어날 때 더 마음에 들지만, 거기에는 미학적 결정도 들어감
  • 이는 도메인 주도 설계의 보편 언어 설계 패턴과 비슷함. 구현에서 도메인 전문가들이 쓰는 현실 세계의 용어를 그대로 쓰게 만드는 방식임
    https://thedomaindrivendesign.io/developing-the-ubiquitous-l...

    • DDD가 나오기 훨씬 전에도 비슷한 개념을 들었음. 코드의 명사와 동사가 문제 도메인과 맞지 않으면 임피던스 불일치이고 언젠가 문제를 일으킬 것이라는 얘기였음
      이 글은 내게 일종의 부끄러움 회피 반응처럼 읽힘. 사람들은 병적으로 “내가 틀렸다” 또는 “우리가 틀렸다”라고 말하기를 싫어해서, 접시에 놓인 채소를 먹은 것처럼 보이게 이리저리 옮기는 아이처럼 은유를 밀고 다니게 됨
      Hoare의 튜링상 연설에 나온 “명백한 결함은 없다”는 말도 떠오름
  • 이는 도메인 주도 설계의 보편 언어 개념을 잘 보여주는 예임
    도메인 전문가가 이해하는 언어를 써야 함. 사용자가 NACHA 파일을 알고 있다면 다른 용어를 쓰는 순간 머릿속에서 매핑을 유지해야 함
    반대로 Stripe의 경우 사용자가 도메인 전문가가 아니므로, 이해 가능하면서 불필요한 세부를 숨기는 추상화를 만드는 것이 가치 있음. 사용자에게 언어를 가르쳐야 한다면 최대한 단순하게 만들어야 함

    • 달리 말하면, 그들은 수행하고 싶은 거래 유형의 도메인 전문가이지 금융 시스템에서 거래가 어떻게 구현되는지의 전문가는 아님
  • POSIX 같은 추상화가 없었다면 애플리케이션은 지원하는 파일 시스템마다 어댑터를 작성해야 했을 것임

  • 흥미로움
    이 개념의 제목은 오해를 부름. 여기서 “추상화 없음”은 문자 그대로 추상화가 없다는 뜻이 아니라 “이 특정한 추상화 집합은 쓰고, 다른 추상화는 쓰지 않는다”는 뜻임. 그들이 설명한 특정 부분집합은 논의할 가치가 있지만, 당연히 추상화 집합임
    예를 들어 “ACH 이체를 API로 만들 때 노출하는 매개변수 이름을 Nacha 명세의 필드 이름을 따서 붙인다”고 했는데, 명세 자체가 추상화임
    “네트워크 용어를 쓰는 것처럼, 리소스를 실제 사건, 예컨대 수행된 행동이나 전송된 메시지에 맞춰 모델링하려 한다. 그 결과 더 많은 API 리소스가 불변이 되고, 상태 기계 ‘수명주기 객체’ 아래에 묶인다”고 했는데, 이 의미의 불변성과 “수명주기 객체”도 추상화임
    “특정 API 리소스에서 사용자가 각 인스턴스에 대해 취할 수 있는 행동 집합이 많이 달라지면 여러 리소스로 나누는 경향이 있다”는 것도 또 다른 추상화임. Stripe API와 다른 수준에서 나누는 것일 뿐임
    결국 이는 설계 결정과 추상화의 집합이지, “추상화 없음” 원칙은 아님. 가장 중요한 결정은 가능한 한 적게 일반화하는 것으로 보이고, 일반화도 추상화의 한 종류임. 아마 “더 적은 일반화”가 더 정확한 제목이었을 듯함

  • “Increase 위에서 만드는 사용자별 월 요금은 사용 사례에 따라 다르다”는 부분을 봤음
    지금 RAG 지원 AI 텍스트-to-SQL 엔드포인트에 공개 API 접근을 추가하고 있는데 가장 큰 문제가 가격 책정임. 대략 어느 정도 금액대를 말하는 건지 아는 사람이 있을까? 가격에는 OpenAI 토큰, 또는 사용자가 자기 OpenAI 토큰을 넣게 하는 방식, 데이터베이스 사용량, 그리고 앞으로 캐싱과 속도 제한 설정까지 반영해야 함

    • 근본적으로 가격은 비용이 아니라 가치를 기준으로 잡아야 하므로[1], 고객에게 어떤 가치가 있는지 생각하고 거기서 출발해야 함
      예를 들어 Gong은 많은 조직에 연 10만 달러 이상을 청구하는 것으로 아는데, 저장소, CPU, 기타 운영비를 고려해도 비용이 컴퓨팅 비용에 가까울 리 없음. 아마 최소 한 자릿수 배 이상 차이 날 것임. 하지만 영업팀은 매출을 매우 직접적으로 가져오기 때문에 Gong 같은 도구 형태로 살 수 있는 레버리지는 즉각적이고 명확하게 가치가 있음
      [1]: 원가 가산 가격 책정을 피해야 한다는 원칙의 예외는 범용재를 파는 경우임. 하지만 당신은 그런 상황이 아님!