12 ms·
No Abstractions: our API design principle
- jackflintermann 2y agoAuthor here - this has been a useful mindset for us internally but I'm curious if it resonates externally. I'd love your feedback!
- spandrew 2y agoLove the article. If you love Stripe (and as a designer and tech entrepreneur I do – Stripe's simplicity and front-end skill is incredible) you might look at them and copy their ability to simplify and deliver polished experiences. But the real mastery of Stripe is that they know their customers — and the simplicity they crave. By this article is sounds like Increase does as well and has forged a similar laser-focus on what their customers need to build terrific design guidelines for making products. Inspiring to see.
- rtpg 2y agoYeah I do think you can see in Stripes API places where there are differing tensions between “let’s make this potentially universal” and “let’s accept that this stuff is going to probably only apply for one payment method in one market”. Personally I appreciate when the latter happens, but there’s an aesthetic decision there
- esafak 2y agoHow Stripe builds APIs and Teams: https://www.youtube.com/watch?v=IEe-5VOv0Js https://www.youtube.com/watch?v=IEe-5VOv0Js
- chowells 2y agoIf there's no abstraction, what's your value-add? I don't care enough to read your marketing BS to see where you claim to be special, but... If your API is doing the exact same things as an underlying service is doing, you're just a middleman extracting rents. You might find it more valuable to state your position as "carefully scoped abstractions" to make it clear what value you add.
- koreth1 2y agoBased on my previous experience on payment systems, there's a surprising amount of value in not having to maintain direct business relationships with the underlying payment providers. It is much, much easier to work with a company like Stripe than to work directly with Visa and MasterCard and the ACH network, and heaven help you if you're a small company that needs to do automated cross-border payments to a wide range of countries without a middleman. You'll probably also get much better support from a tech-focused company when an API starts freaking out.
- exe34 2y agoI thought I understood everything you said, but isn't Stripe a middleperson here? > without a middleman.
- koreth1 2y agoRight, Stripe is a middleman and part of the value they're giving you is that you don't have to work directly with the underlying payment companies. If you had to support the same range of payment options without a middleman, you'd need to have business relationships with a bunch of payment companies, which would be a lot more difficult and time-consuming. Hope that's clearer!
- exe34 2y agoMakes sense thanks!
- OJFord 2y agoYes, GP's point is 'good luck to you doing that yourself, without a middleman [such as Stripe]'.
- jackflintermann 2y agoYes, exactly, the important thing to us is that our users don't need to build an additional mental model between us and the networks we sit atop. If you know the network, we want you to be able to intuit how our API works. There's a very real difference (arguably the fundamental value-add of our company) in the transport layer, though. The actual mechanics of integrating with, say, FedACH, are a bit long to get into here (we get into it a bit here if it's of interest: https://increase.com/documentation/fedach https://increase.com/documentation/fedach) but suffice to say it doesn't have a REST API.
- summerlight 2y agoI like the part that explains why Increase choose a different approach. Contexts matter a lot when you design something fundamental, but people usually don't appreciate this enough.
- kikimora 2y agoI think this is better than Stripe’s abstract everything approach even for people who are not into payments. Stripe has built a very leaky abstraction.
- adelineJoOs 2y agoHow is the leakage noticeable?
- kikimora 2y agoI’m not saying Stripe API is bad. But there are limits to how much differences you can hide behind a generic API while keeping it consistent. Off the top of my head I can think of a few cases I would qualify as a leaky abstraction. To start with - there is a payment method abstraction and there is SetupIntent that works with it. Normal use case is tokenizing a CC. But for ACH it does something different if ever works. Same setup intent would work with debit cards, but not in Brazil because of local regulations. I don’t remember if you get a decent error code when attempt to tokenize a Brazilian debit card. Customers making cards payments can initiate a dispute which would cost you 15 usd + payment amount if they win. This cannot happen with some other payment methods. It became important when you implement Stripe connect because you might want to set different fees for different payment methods to account for cost of disputes. The leaky abstraction part here is as soon as you start creating certain type of payment intents you also have to subscribe to Stripe webhooks for disputes. To save on refund fees you may want to authorize payments (confirm payment intent) and capture them after a period of time. During that window you can cancel the payment and pay only authorization fee instead of paying full refund fee. This strategy works only for payment methods supporting authorization and capture semantics and having favorable commission structure. Max amount of time between confirm and capture depends on the payment method as well. Not specific to Stripe Terminals but still. Tapping a card gives you an anonymized payment method while dipping the same card reveals some cardholder data. This is beyond Stripe control, but puzzling at first because at the API level you deal generic PaymentMethod object. With Stripe connect what happens after the payment is defined in terms of abstract transfers between Stripe accounts. In some regions transfers works across countries while not in the others. One example is Canada-USA vs Brazil and rest of the world. From one end you have abstract transfers API to move money between Stripe accounts. From another you have to implement a number of workarounds to make transfers work in all interesting scenarios because of regional and currency conversion considerations. For example in some cases you do transfers while in other you do payment intents. What I’m trying to say here is you have to know specifics of payment methods, underlying technologies and regions you work with. By looking at high-level API you may think it is easy to support many payment methods when in fact many of them would require very specific code.
- l5870uoo9y 2y ago> Monthly fees for users building on Increase vary by use case. I am currently adding public API access to AI-powered text-to-SQL endpoint with RAG support and the my biggest issue is the pricing. Anybody have a ballpark figure what we could be talking about here? Pricing must account for OpenAI tokens (or perhaps letting them add their own OpenAI token), database usage and likely caching/rate limiting setup down the line.
- chaos_emergent 2y agoFoundationally, pricing should be based on value, not cost[1] so you should think about what the value is to your customer and go from there. Ex: I know that Gong costs a ton of organizations over 100k/year, and there's no way that, accounting for storage, CPUs, and all the other OpEx, that the cost comes anywhere close to the cost of compute - it's likely at least an order of magnitude greater. But because sales teams bring in so much revenue so directly, any leverage that they can buy in the form of a tool like Gong is immediately and obviously valuable. [1]: the exception to avoiding cost-plus pricing is if you're selling a commodity. But you're not in that boat!
- lolpanda 2y agofor any APIs related to money, should the currency be in strings as opposed to in floats? This will prevent accidental float arithmetic in the code. I always find it tricky to work with currency in javascript.
- trevor-e 2y agoI've always seen currencies multiplied by 100 to remove the need for floating point.
- nijave 2y agoYeah, this seems like a common pattern. Not sure about currency with arbitrary place values though (like Bitcoin)
- deathanatos 2y agoI'm not sure what you mean by "arbitrary place values" with Bitcoin; if you are implying it's infinitely divisible, it isn't. You'd do the same trick with Bitcoin: represent it as an integer¹. The value 1 is 1 sat, or 0.00000001 BTC. ¹(where you need to; otherwise, I'd use some fixed point type if my language supports it)
- akavi 2y agoThat's not quite a sufficient rule. Eg, 1 Bahraini Dinar is 1_000 Bahraini Fils.
- cateye 2y agoSome currencies use more than 2 decimal places. For instance, the currencies of Algeria, Bahrain, Iraq, Jordan, Kuwait, Libya, and Tunisia are subdivided into 3 decimals.
- kadoban 2y agoIf you use a higher constant, 10000 or 1000000 or something, you give yourself a good amount of more fleixibility.
- 2y ago
- andrewstuart 2y agoI hate abstractions. Program the thing as it is intended. Why do programmers always need a library between them and the API?
- Jtsummers 2y ago> Why do programmers always need a library between them and the API? You do know that libraries present an API, right? Very few people program on Linux or other OSes without using libc or the OS/distribution equivalent, and for good reason. Those libraries provide a degree of compatibility across hardware systems and operating systems (and even the same OS but different versions). Your question is about as sensible as asking "Why do programmers always need a programming language between them and the machine code?" Because it improves portability, reusability, reasonability, and on and on. Though, since you hate abstractions, maybe you do only program in machine code.
- koreth1 2y agoI kind of hate the fact that the term "API" has lost its generality in the minds of a huge number of practitioners, and people now assume it refers to a set of network (usually HTTP) request and response formats. It's great that we have a succinct word to describe programmatic interfaces built on top of HTTP. It's not great that there's no longer a universally-understood word for the original more general meaning even though, as this thread demonstrates, the original meaning is still as relevant as ever.
- compootr 2y agoI think context has to be taken into account people here are referring to some financial service on the internet, whose API is invoked over http An article about some library might be viewed differently, i.e "X's API is better than Z's"...etc
- cratermoon 2y agoNo Abstractions here really means "just use terms from the underlying system", which is a good naming principle in general. Problems inevitably arise over time when there's multiple underlying systems and they have different names for the same thing, or, arguably worse, use both use a name but for different things. In this example, what if the underlying payment providers have different models? Also, what if the Federal Reserve, deprecates Input Message Accountability Data and switches to a new thing? Maybe things are a lot simpler in the payment industry than they are in transportation or networking protocol. If I built a packet-switching product based on X.25 and later wanted to also support tcp/ip, what's the right abstraction?
- advisedwang 2y ago> No Abstractions here really means "just use terms from the underlying system" The article clearly says it also means "no unifying similar objects", which enables the naming decision.
- cratermoon 2y agoHow does that work if, for example, the example given of "Visa and Mastercard have subtly different reason codes for why a chargeback can be initiated, but Stripe combines those codes into a single enum so that their users don’t need to consider the two networks separately.". Unfortunately, the article doesn't explain how Increase handles that overlap. Presumably, as the article states, their customers are the sort that do care about Visa reason codes vs Mastercard reason codes, so what's the design of a "no abstraction" API in that case?
- travisjungroth 2y agoI’m just reasoning from my limited experience and the article. Stripe unifies those reasons codes. Increase doesn’t. It might be that the Chargeback has the processor and chargeback code as attributes. So rather than have a universal “goods and services not received”, it’s a 13.1 for Visa, a 4855 for MasterCard and a F30 for Amex. This matters when the boundaries are different. For example, they all split up the categories of fraud differently.
- freguencey 2y ago[dead]
- cpeterso 2y agoThis is similar to Domain-Driven Domain's "Ubiquitous Language" design pattern, making your implementation use the same real-world terminology used domain experts. https://thedomaindrivendesign.io/developing-the-ubiquitous-language/ https://thedomaindrivendesign.io/developing-the-ubiquitous-l...
- hinkley 2y agoI was introduced to this concept a good while before DDD came along, when someone opined that if the nouns and verbs in your code don't match the problem domain that's an impedance mismatch and it's going to get you into trouble some day. It really reads like a shame response to me. People are so pathologically allergic to saying "I was wrong" or "we were wrong" that they end up pushing their metaphors around like a kid trying to rearrange their vegetables on their plate to make it look like they ate some of them. It's also smacks of the "No defects are obvious" comment in Hoare's Turing Award speech.
- Karellen 2y ago> If you’re building an abstraction-heavy API, be prepared to think hard before adding new features. If you’re building an abstraction-light API, commit to it and resist the temptation to add abstractions when it comes along. You could always do both. Provide a low-level abstraction-light API that allows fine control but requires deep expertise, and write a higher-level abstraction-rich API on top of it that maps to fewer simple operations for the most common use cases - which some of your clients might be implementing their own half-baked versions of anyway. If you maintain a clean separation between the two, having both in place might mean there is less pressure to add abstractions to the low-level API, or to add warts and special-cases to the high-level API. If a client wants one of those things, it already exists - in the other API. Bonus points for providing materials to help your clients learn how to move from one to the other. You can attract clients who do not yet have deep knowledge of payment network internals, but are looking to improve in that direction.
- paulddraper 2y agoGit is an example of this. [1] There are high-level "porcelain" commands like branch and checkout. And then there are low-level "plumbing" commands like commit-tree and update-ref. [1] https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
- mbork_pl 2y agoCame here to say that. Also, to some extent, Emacs. There are thousands of functions (actually, a bit less than 10k in stock Emacs without packages, and over 46k in my Emacs) performing various low-level tasks, and much fewer commands (~3k in stock Emacs, almost 12k in my config), i.e., interactive functions, often higher-level, designed for the user.
- deleted 2y ago[deleted]
- yen223 2y agoIt does double your API surface area, so that's the tradeoff you'll have to consider. It can be the correct decision in a lot of cases.
- lwhi 2y agoSo they say parts of the API structure are based 1-1 on externally controlled specifications. What happens if those specifications evolve or change? New API?
- Supermancho 2y agohttps://docs.stripe.com/api/versioning https://docs.stripe.com/api/versioning A versioned API. Which means more APIs to maintain, until they are removed.
- lwhi 2y agoMore complexity is bad imo.
- Supermancho 2y agoI agree.
- bvrmn 2y agoAlso it greatly helps to not overuse nouns and try to forcefully model verbs with resource entities.
- west0n 2y agoIf we didn't have abstractions like POSIX, applications would need to write an adaptor for every supported file system.
- shironandonon_ 2y agoTLDR: we are special and created our own standard.
- ngrilly 2y agoThey are saying the opposite: we follow existing standards as much as possible.
- dheera 2y agoJava engineers need to see this. Goddamn Proxies, Factories, and Beans. Fragments, Surfaces, Runnables.
- theptip 2y agoThis is a great example of the concept “ubiquitous language” from Domain Driven Design. Use language that your domain experts understand. If your users know about NACHA files, using other terms would mean they need to keep a mapping in their head. On the other hand, in Stripe’s case, their users are not domain experts and so it is valuable to craft an abstraction that is understandable yet hides unnecessary detail. If you have to teach your users a language, make it as simple as possible.
- Nevermark 2y agoOr to put it another way, they are domain experts in the kinds of transactions they want to perform, not how transactions are implemented in the financial system.
- tegling 2y agoOne tricky thing to model neatly in payment APIs is that payment schemes indicate the roles of payer and payee in payment returns in different ways. E.g. for one particular scheme the payer and payee may be kept in the same position as in the initial payment (creditor of payment return is actually the one sending funds) whereas in another one they are switched (creditor is the one receiving funds in the return). I'd be curious to see how they are handling this case as it can be a real head-scratcher.
- danecjensen 2y agoStrong work Jack!
- RobotToaster 2y agoNo abstractions? So their API lets me control the individual registers on their CPU then?
- dkjaudyeqooe 2y agoNo, sorry, registers are an abstraction. The API only lets you set voltages on individual wires.
- layer8 2y agoDo you also have to provide the clock signal for processing? Might become expensive in terms of API calls.
- dkjaudyeqooe 2y agoI had to think about it for a sec, but clock signals are indeed an abstraction so you have to provide them.
- the_af 2y agoInteresting. The title of the concept is misleading, "No Abstractions" here doesn't literally mean "no abstractions" but instead "we use this specific set of abstractions, and not others". And the specific subset they describe is worth discussing! But it's of course a set of abstractions. E.g. > For example, the parameters we expose when making an ACH transfer via our API are named after fields in the Nacha specification A specification is an abstraction. > Similar to how we use network nomenclature, we try to model our resources after real-world events like an action taken or a message sent. This results in more of our API resources being immutable [...] and group them together under a state machine “lifecycle object”. Immutability (in this sense) and "lifecycle objects" are abstractions. > If, for a given API resource, the set of actions a user can take on different instances of the resource varies a lot, we tend to split it into multiple resources. Another abstraction, just splitting at a different level than the Stripe API. This is a set of design decisions and abstractions. Definitely not a "no abstractions" principle. I would say the most important decision they seem to have made is to generalize as little as possible -- and generalization is indeed a kind of abstraction. Maybe "Fewer Generalizations" would have been a more accurate title?
- Splizard 2y agoI think the point the author is trying to express here, is that it can be a useful in API representation, to couple the design with other representations designed by other parties. I don't think "No Abstractions" is a good framing for this, although I would have to admit I dislike use of the term abstraction, as it implies there is a hierarchy of representations.
- MaxBarraclough 2y ago> Visa and Mastercard have subtly different reason codes for why a chargeback can be initiated, but Stripe combines those codes into a single enum so that their users don’t need to consider the two networks separately. This is indirection, not abstraction. Abstraction raises the semantic level.
- AtNightWeCode 2y agoBut the entire API is an abstraction... So the benefits are: Audits Network/infra Time to market Single API Less code Risk (sometimes) The downside: 1. Slow or missing propagation of underlying features. 2. Hidden business logic. 3. Risk of changes in pricing models and so on. 4. Single point of failure. By method, let's talk about the downsides: 1. Not the biggest risk here. But for some reason features that are new or will save you a lot of money does not propagate as fast other things. 2. Many services like payment gateways are expected to hide some aspects of the underlying services. What does this hide? 3. The big risk with something like this used to be vendor lock-in. Today it is almost always acquisitions. Is this really a product? Will it be merged and sold together with something that I don't want? 4. Obvious Overall I think these types of services are the most useless. Abstractions that are not simplifications should mostly be avoided. I also think one needs to be extra careful if this only sits between you and other services. That is not a product in general.