- Increaseは、APIリソースがユーザーのプロダクト理解を左右すると考え、決済ネットワークの複雑さを隠すのではなく露出させる No Abstractions 原則を採用している
- Stripe式の抽象化は素早い統合に強い一方、Increaseのユーザーは payment networkの知識を前提に、直接接続と深い統合を求めている
- APIはNacha specificationのような基盤ネットワークの用語をそのまま使い、ACH transferの進行過程を不変の下位オブジェクトとしてモデル化する
- ユーザーが実行できるアクションが大きく異なる場合は、
ach_transferとinbound_ach_transferのようにリソースを分離し、最初は冗長でも長期的な予測可能性を高める
- 抽象化レベルは、統合を行う開発者のドメイン経験と投下意欲に合わせて決めるべきであり、低い抽象化を選んだなら、その後もその原則を維持する必要がある
APIリソースはユーザーのメンタルモデルを作る
- API resourceはAPIの名詞であり、名前とモデルを決めることはAPI設計の中でも最も難しく重要な部分に属する
- どのリソースを公開するかが、ユーザーがプロダクトの動作方式や可能な操作を理解するためのメンタルモデルを構成する
- Increaseはこの判断を助けるために「No Abstractions」という設計原則を使っている
-
Stripe式の抽象化とIncreaseの違い
- Stripeは、複雑な決済ドメインをユーザーが扱いやすいAPIへと抽出する抽象化に強みがある
- 複数の決済ネットワークを
PaymentIntentというAPI resourceとしてモデル化し、VisaとMastercardのchargeback reason codeの違いを1つのenumに統合することで、ユーザーが2つのネットワークを別々に考えなくて済むようにしている
- Stripeユーザーの多くは決済そのものではないプロダクトを作る初期スタートアップであり、クレジットカードの細部を深く知るよりも、素早く統合して本来のプロダクト開発に戻りたいと考えている
- Increaseのユーザーはpayment networkに関する既存知識が深く、金融技術を継続的に扱い、ネットワークへの直接接続と深い統合のためにIncreaseを使っている
- 彼らはFedACH windowがいつ閉まり、transferがいつ到着するのかを正確に知りたがり、ACH transferのStandard Entry Class codeが変わればreturn timingも変わり得ることを理解している
- ACH transferとwire transferを1つの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_transferはstatusがpending_approvalで、approval、submission、acknowledgementがnullであり得る
- FedACHに提出された後は
statusがsubmittedになり、approval、submission、acknowledgementにはそれぞれ承認・提出・確認時点の不変情報が入る
submissionにはtrace_numberやsubmitted_atのような値が含まれる
-
ユースケース別にリソースを分離する
- 同じAPI resourceでも、instanceごとに可能なアクション集合が大きく異なる場合、Increaseはそれを複数のリソースに分ける傾向がある
- originated ACH transferとreceived ACH transferで可能なアクションは事実上正反対であるため、
ach_transferとinbound_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を作るなら、その方向にコミットし、抽象化を追加したくなる誘惑に耐えなければならない
1件のコメント
Hacker Newsの意見
常に両方を提供することもできる
きめ細かな制御が可能だが深い専門知識を必要とする低レベルAPIを提供し、その上に、よくあるユースケースをいくつかの単純な操作へマッピングする高レベルAPIを作ればよい。どうせ一部の顧客は、こうした高レベル層を中途半端に自前実装しているかもしれない
2つの層をきれいに分離すれば、低レベルAPIに抽象化を入れたり、高レベルAPIに傷や特殊ケースを追加したりするよう求める圧力が減る。顧客がそれを望むなら、すでに別のAPIに存在しているからだ
顧客が一方の層からもう一方の層へ移行する方法を学べる資料まで提供すれば、さらによい。決済ネットワークの内部構造をまだ深く理解してはいないが、その方向へ成長したい顧客も引きつけられる
今日 Web File System API を使ったが、文字列1つをファイルに書き込むのに関数呼び出しが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が「Pythonらしい」APIを追求するやり方にも似ている
ツールの内部実装の観点に慣れてしまうと、人々が実際にどう使うのかを忘れるのはあまりに簡単だ
branchやcheckoutのような高レベルの「porcelain」コマンドがあり、commit-treeやupdate-refのような低レベルの「plumbing」コマンドがある
https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
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 も同様に見え、顧客が何を必要としているかに同じように鋭く集中することで、優れたプロダクト設計指針を作ったように思える。励みになる。
個人的には後者が起きるときのほうが好ましいが、そこには美学的な判断も含まれる。
これは、ドメイン駆動設計におけるユビキタス言語の設計パターンに似ている。実装で、ドメイン専門家が使う現実世界の用語をそのまま使わせるやり方だ。
https://thedomaindrivendesign.io/developing-the-ubiquitous-l...
この記事は私には、一種の恥の回避反応のように読める。人は病的なほど「自分が間違っていた」あるいは「私たちが間違っていた」と言うのを嫌がるので、皿の上の野菜を食べたように見せかけるためにあちこち動かす子どものように、メタファーを押し回すことになる。
Hoare のチューリング賞講演に出てくる「明白な欠陥はない」という言葉も思い出す。
これは、ドメイン駆動設計のユビキタス言語という概念をよく示す例だ。
ドメイン専門家が理解する言葉を使うべきだ。ユーザーが NACHA ファイルを知っているなら、別の用語を使った瞬間に、頭の中で対応付けを維持しなければならなくなる。
逆に Stripe の場合、ユーザーはドメイン専門家ではないので、理解可能でありつつ不要な詳細を隠す抽象化を作ることに価値がある。ユーザーに言葉を教えなければならないなら、できるだけシンプルにすべきだ。
POSIX のような抽象化がなければ、アプリケーションは対応するファイルシステムごとにアダプターを書かなければならなかっただろう。
興味深い。
この概念のタイトルは誤解を招く。ここでいう「抽象化なし」は、文字どおり抽象化がないという意味ではなく、「この特定の抽象化の集合は使い、別の抽象化は使わない」という意味だ。彼らが説明した特定の部分集合は議論に値するが、当然ながら抽象化の集合である。
たとえば「ACH 振込を API にするとき、公開するパラメーター名を Nacha 仕様のフィールド名にちなんで付ける」と述べているが、仕様そのものが抽象化だ。
「ネットワーク用語を使うように、リソースを実際の出来事、たとえば実行されたアクションや送信されたメッセージに合わせてモデル化しようとしている。その結果、より多くの API リソースが不変になり、状態機械の『ライフサイクルオブジェクト』の下に束ねられる」と述べているが、この意味での不変性と「ライフサイクルオブジェクト」も抽象化だ。
「特定の API リソースで、ユーザーが各インスタンスに対して取れるアクションの集合が大きく異なる場合、複数のリソースに分ける傾向がある」というのも、また別の抽象化だ。Stripe API とは異なるレベルで分けているだけである。
結局これは設計判断と抽象化の集合であって、「抽象化なし」という原則ではない。最も重要な判断は、できるだけ一般化を少なくすることのように見え、一般化も抽象化の一種だ。おそらく「より少ない一般化」のほうが、より正確なタイトルだっただろう。
「Increase上で作るユーザー別の月額料金はユースケースによって異なる」という部分を見た
いま RAG対応AIテキスト-to-SQLエンドポイントに公開APIアクセスを追加しているのだが、最大の問題は価格設定。だいたいどのくらいの価格帯を指しているのか、分かる人はいるだろうか? 価格にはOpenAIのトークン、あるいはユーザーに自分のOpenAIトークンを入れてもらう方式、データベース使用量、そして今後はキャッシュやレート制限の設定まで反映する必要がある
例えばGongは多くの組織に年間10万ドル以上を請求していると理解しているが、ストレージ、CPU、その他の運用費を考慮しても、コストがコンピューティング費用に近いはずがない。おそらく少なくとも数倍以上の開きがあるはずだ。だが営業チームは売上に非常に直接的に結びつくため、Gongのようなツールとして購入できるレバレッジは即座に明確な価値を持つ
[1]: コストプラス方式の価格設定を避けるべきだという原則の例外は、コモディティを売る場合だ。しかしあなたはその状況ではない!