return to table of content

No Abstractions: our API design principle

Karellen
38 replies
1d20h

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.

yen223
12 replies
1d19h

It 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.

Pxtl
8 replies
1d19h

It doesn't double the security surface area if the abstracted API goes through the low-level API. The outer one is just chrome, and so the risks of screwing something up there is far lower.

Unless you're using a trash language where even simple wrappers could buffer underrun or something.

lmz
7 replies
1d18h

Isn't there the issue of modifying the state enough through the low-level API such that it breaks the assumptions of the high-level one?

eru
3 replies
1d13h

Just say that you don't support mixing both APIs.

lazyasciiart
1 replies
1d10h

Now you’re telling users to check the source code of any tools they use with your stuff? “We recommend using ToolOne only on content you do not also manage with ToolTwo”

eru
0 replies
10h4m

See how libfuse handles it with their low level and high level APIs.

actionfromafar
0 replies
1d12h

That’ll fix it. ;-)

Pxtl
1 replies
1d4h

Yes. But that's the high-level API's problem. That's a problem with any abstraction really. "What if there's something in the thing we're abstracting that doesn't fit the abstraction" isn't really a problem with the "two API" approach, it's a problem with abstraction.

The high-level API needs to handle that case, if nothing better than having internal assertions that throw if it hits a case it's not designed to accommodate.

(also I'm annoyed with myself that I wrote buffer underrun in my first post instead of buffer overflow and now it's too late to edit).

cylemons
0 replies
15h8m

You did make me search what a buffer underrun was and I think it was a good read

withinboredom
0 replies
1d11h

The high level API shouldn’t care about state. In other words, your “readers” should merely aggregate state and your “writers” should only care about subsets of state.

Think about file permissions in Linux. Running ls just shows you gross file perms (current user, group, and global) but you can also grant access to other individual users, or even make a file immutable that still shows up as writable to ls. The high level api doesn’t know or care about the low level state except where it is relevant.

campbel
2 replies
1d18h

Unlikely to double. The low level API exposes all capabilities, the high level API exposes a subset of those capabilities under a smaller surface. The high level API will not be as large as the low level.

blowski
0 replies
1d11h

This reminds me of the Kubernetes API.

IggleSniggle
0 replies
1d6h

This reminds me of the git API.

jampekka
8 replies
1d18h

This. There should be a low level API to be able to do rarer more complicated cases, and a higher level simple API for common cases built on the lower-level API.

Just today I was working with the Web File System API, and e.g. just writing a string to a file requires seven function calls, most async. And this doesn't even handle errors. And has to be done in a worker, setting up of which is similar faff in itself. Similar horrors can be seen in e.g. IndexedDB, WebRTC and even plain old vanilla DOM manipulation. And things like Vulkan and DirectX and ffmpeg are even way worse.

The complexity can be largely justified to be able to handle all sorts of exotic cases, but vast majority of cases aren't these exotic ones.

API design should start first by sketching out how using the API looks for common cases, and those should be as simple as possible. E.g. the fetch API does this quite well. XMLHttpRequest definitely did not.

https://developer.mozilla.org/en-US/docs/Web/API/FileSystemS...

Edit: I've thought many a time that there should be some unified "porcelain" API for all the Web APIs. It would wrap all the (awesome) features in one coherent "standard library" wrapper, supporting at least the most common use cases. Modern browsers are very powerful and capable but a lot of this power and capability is largely unknown or underused because each API tends to have quite an idiosyncratic design and they are needlessly hard to learn and/or use.

Something like what jQuery did for DOM (but with less magic and no extra bells and whistles). E.g. node.js has somewhat coherent, but a bit outdated APIs (e.g. Promise support is spotty and inconsistent). Something like how Python strives for "pythonic" APIs (with varying success).

metalspoon
3 replies
1d18h

Tbf Vulkan is not intended for an endprogrammer. It is a deliberately low level standardization to allow directly control GPU hardware. The high-level approach (OpenGL) failed. The endprogrammer is supposed to use a third party middleware, not Vulkan itself.

f1shy
1 replies
1d8h

So OpenGL is a total failure! I learned something today… What should I use?

wizzwizz4
0 replies
1d6h

I like WebGPU: it's slightly higher-level than OpenGL, but the shader language is better. (For the actual web, use WebGL: like Wasm, WebGPU isn't actually suitable for use in websites.)

withinboredom
0 replies
1d11h

Someone has to write that middleware…

cpeterso
1 replies
1d12h

My favorite quote about abstraction is from Edsger Dijkstra:

”The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.”

If only the “A” in API stood for “abstraction”. For many APIs, it probably stands for “accreted”. :)

MaxBarraclough
0 replies
6h11m

That's a great quote. This topic turned up a couple of weeks ago in another thread. [0] The excellent talk Constraints Liberate, Liberties Constrain, by Runar Bjarnason, gets at the same point, and even uses this same Dijkstra quote. [1]

[0] https://news.ycombinator.com/item?id=40021696

[1] https://youtu.be/GqmsQeSzMdw?t=874 (this takes you right to the Dijkstra quote)

zellyn
1 replies
1d15h

Problems can sneak in when you use the low-level API to do something to an object that can't be cleanly represented in the higher-level API. You need some kind of escape hatch, like a list of links to or ids of low-level details or a blob (Map<String,arbitrary-JSON> of miscellaneous data that can hold the low-level additions.

Hopefully the top-level important concepts like "amount_due" will still reflect the correct numbers!

withinboredom
0 replies
1d11h

Those problems usually present themselves by people overthinking the high level api and trying to be smart.

As an example, you can use chattr to make a file in Linux immutable. ls still shows that you have permission to write to the file, even though it will fail.

When people try to overthink the api and have it determine if you really can write to a file, people will try using the high level api first (chmod) and it won’t work because it has nothing to do with permissions.

KISS is really needed for high level APIs.

mbork_pl
0 replies
1d10h

Came 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.

eterm
0 replies
22h28m

That's a fantastic blog post, worthy of it's own HN submission.

Groxx
1 replies
1d17h

I'm particularly fond of this pattern when you can implement the high level API that you want outside the library, which ensures that your low level API is sufficiently flexible and means you're dogfooding your own stuff as a user. It's far too easy to get used to the internal side of a tool you're building, and forget how people actually use it.

HelloNurse
0 replies
1d5h

It is also important to guarantee that the two API designs are coherent and interoperable, and this kind of strict layering is the best strategy to avoid mistakes.

sameoldtune
0 replies
3h46m

“Make the easy things easy and the hard things possible”

lwhi
0 replies
1d11h

I like this idea a lot.

One level of API for implementation model.

And second level for mental model.

jimbob45
0 replies
22h28m

That sounds like the exact philosophy the Vulkan devs took versus OpenGL.

geophph
0 replies
13h40m

matplotlib seems to have implemented this approach

eru
0 replies
1d13h

Compare also exokernels, and how they delegate abstraction to libraries, not the OS.

devjab
0 replies
1d11h

I’m genuinely curious as to what an abstraction-rich api would look like and why it would be useful.

I’ve mainly worked in enterprise organisations or in startups transitioning into enterprise which is sort of where my expertise lies. I’ve never seen an API that wasn’t similar to the examples in this case.

I mean… I have… but they wouldn’t be labelled as high-abstraction api’s. If they needed a label it would be terrible APIs. Like sending table headers, column types in another full length array with a line for each column, and then the actual data/content in a third array. Even sending the html style that was used in what ever frontend, so that some data is represented as “some data” and other is represented as [“some data”, [“text-aligned”, “center”…],… . Yes, that is an actual example. Anyway I’ve never seen a high abstraction api and I feel like I’m missing out.

andenacitelli
0 replies
1d15h

Another example of this is AWS CDK. There are a few “levels” of constructs - high level ones that are simpler and apply “presets” that are enough for 80% of users, but the core low level ones still exist if you have a nonstandard use case.

chowells
21 replies
1d22h

If 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
10 replies
1d21h

Based 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.

jackflintermann
4 replies
1d21h

Yes, 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) but suffice to say it doesn't have a REST API.

koreth1
1 replies
1d21h

That's an excellent point too. Some payment systems have abysmal technology. The product I worked on was focused on international payments and in a couple cases, the "API" was literally, "Upload a CSV file via FTP, and at some later point, another CSV file might appear on the FTP server with some of the payment results, but if it doesn't, call us because we probably just forgot to upload it."

nijave
0 replies
1d20h

Batch jobs and (S)FTP. In a bit of a weird twist, back when I worked at Chase, they were innovating on the ancient technology but it was things like "better batch job management/orchestration" and SFTP proxy to route between different servers and centralize key management

wodenokoto
0 replies
1d12h

But it must have _some sort_ of API. Since your rest API is modelled on their API it made me really curious about how you communicate with those networks.

jrochkind1
0 replies
1d17h

I'm curious to learn more about what your customers look like, what sorts of businesses and activities they are in. Where stripe's customers are working on products unrelated to payments, yours are working on products related to payments? I'm having trouble conceiving of examples.

exe34
3 replies
1d21h

I thought I understood everything you said, but isn't Stripe a middleperson here?

without a middleman.
koreth1
1 replies
1d21h

Right, 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
0 replies
1d21h

Makes sense thanks!

OJFord
0 replies
1d21h

Yes, GP's point is 'good luck to you doing that yourself, without a middleman [such as Stripe]'.

fendy3002
0 replies
1d14h

Idk how this one works, but credit card processors need license. If you can use credit card service without requiring that license then it'll be the best additional value.

conroy
6 replies
1d21h

In this case, a HTTP API is the abstraction. Integrating with ACH and other payment rails requires a lengthy integration process. Sometime you have to send binary files using FTP!

jiggawatts
5 replies
1d21h

The article says “no abstractions”, but HTTP is often exactly that: an abstraction over lower-level protocols.

jackflintermann
2 replies
1d20h

I guess the phrase "no abstractions" is specifically valuable to us when designing our REST API resources - our whole stack is certainly an abstraction of sorts, but we don't want to add yet another abstraction in that specific layer.

gr4vityWall
1 replies
1d4h

Just wanted to say that I appreciated the article :) Using well-designed APIs is great, and seeing people putting a lot of thought on it, with the intent of improving dev experience, is very refreshing. I've dealt a lot of technically impressive Free Software projects that didn't focus on this as much, and as a result, using their libraries was harder.

jackflintermann
0 replies
22h22m

Thank you!

gr4vityWall
0 replies
1d4h

My interpretation is that they meant domain-level abstractions. So, their API endpoints won't try to hide details about the underlying payment methods through abstractions, because that works best for those users.

The API being implemented with JSON over HTTP isn't related to the domain of processing payments, so I don't see it as a contradiction to the article's title.

chaos_emergent
0 replies
1d20h

perhaps what you're thinking of is "equal entropy abstractions" - HTTP is just a way of standardizing logic, but the complexity of the shape and behavior of the API remains.

contravariant
0 replies
1d20h

There is abstraction there's just not an additional layer of abstractions on top of it.

Which to be honest is quite good, there's lots of things you can solve with an additional layer of abstraction but not having too many layers of abstraction. It's also rare to be able to identify an abstraction that correctly cuts things off at the right layer.

arrowsmith
0 replies
1d21h

Presumably the value comes from providing a single unified platform that means you don't have to integrate with every underlying service separately.

I know nothing about the lower-level details of payment networks but the mere fact that this company exists and has customers would suggest that there's a value-add.

alex_lav
0 replies
1d13h

If there's no abstraction, what's your value-add?

But then

I don't care enough to read

Hmmmmmm.

lolpanda
20 replies
1d21h

for 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
5 replies
1d21h

I've always seen currencies multiplied by 100 to remove the need for floating point.

nijave
1 replies
1d21h

Yeah, this seems like a common pattern. Not sure about currency with arbitrary place values though (like Bitcoin)

deathanatos
0 replies
1d20h

I'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)

kadoban
0 replies
1d12h

If you use a higher constant, 10000 or 1000000 or something, you give yourself a good amount of more fleixibility.

cateye
0 replies
1d19h

Some 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.

akavi
0 replies
1d20h

That's not quite a sufficient rule. Eg, 1 Bahraini Dinar is 1_000 Bahraini Fils.

tadfisher
4 replies
1d20h

I will be the contrarian: JSON numbers are not floating point values, they are strings of characters matching the format "-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?". You can choose to parse them however you want, and parsing libraries should provide a mechanism to decode to an arbitrary-precision value.

meekaaku
0 replies
1d10h

Yes, but say in javascript if you do a JSON.parse(), it will give you a double float right?

lolpanda
0 replies
1d18h

Good point. it's not a problem with JSON. It's just that most of the JSON libraries by default parse numbers into floats.

koreth1
0 replies
1d19h

By way of example: when I worked on payment code in Java, we accepted numeric JSON values in request payloads but they were deserialized into "BigDecimal" fields in our payload classes, not "float" or "double".

int_19h
0 replies
1d10h

Regardless of what the libraries should be doing, there is the reality of what they are doing.

benhoyt
3 replies
1d21h

From their docs [1] it looks like they do everything using integers: the amounts are integers in the "minor unit" of currency, for example cents if the currency is dollars. So 1000 means $10.00. In languages like JavaScript where everything is a float64, you can still accurately represent integers up to 2^53, which would be $90 trillion.

[1] https://increase.com/documentation/api#transactions

crabmusket
2 replies
1d19h

This isn't sufficient to represent prices which often include fractional amounts of cents in non-retail scenarios. Think of AWS server prices per hour.

crabmusket
0 replies
16h34m

But those are decimal values, not integers. I didn't mean that using cents as a unit was insufficient, I meant that using integers was.

endofreach
1 replies
1d20h

You should never use floats for dinero. And it has nothing to do with JS, though i find it funny that you mention JS.

xxgreg
0 replies
1d20h

Don't use floats if you're trying to represent an exact value, i.e. someones bank account. But in financial modelling you're generally dealing in probabilistic "expected values", it's common and fine to use floats.

Having said that, half the world seems to run on Excel spreadsheets, which are full of money values, and Excel is basically all floats (with some funky precision logic to make it deterministic - would be curious to know more).

https://stackoverflow.com/questions/2815407/can-someone-conf...

freedomben
0 replies
1d21h

Yes, never use floats for currency. I typically use integers and for USD for example, measure in "cents" rather than dollar. I try to avoid the fallacy of appeal to authority, but this is what Stripe does. You can also use the Decimal type in javascript and convert to/from strings to cross API boundaries.

cratermoon
0 replies
1d20h

neither. Use rational or some other better type.

GeneralMayhem
0 replies
1d12h

Ideally integers, but at a large multiplier.

Google's money proto [1] has units and nanos. Any competent ad-tech system will use something similar: integer number of micro-dollars, nano-dollars, etc. You want a fair amount of precision, so just tracking whole cents isn't enough, but you want that precision to be (linearly) equally distributed across the value space so that you can make intuitive guarantees about how much error can accumulate.

[1] https://github.com/googleapis/googleapis/blob/master/google/...

cratermoon
6 replies
1d20h

No 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
2 replies
1d20h

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
1 replies
1d19h

How 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
0 replies
1d19h

I’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.

Terr_
1 replies
1d18h

No Abstractions here really means "just use terms from the underlying system"

Which sounds a bit like Domain Driven Design, although the "underlying system" in this case may be a bit too implementation-centered to be considered a real business domain.

To expand on that a bit: In DDD you generally defer to the names and conceptual-models the business-domain has already created. Trying to introduce your own "improved" [0] model or terms creates friction/miscommunications, adds opportunities for integration bugs, and ignores decades or even centuries of battle-tested specialized knowledge.

[0] https://xkcd.com/793/

cratermoon
0 replies
22h31m

the "underlying system" in this case may be a bit too implementation-centered to be considered a real business domain.

I tend to agree with this. The domain concepts would be things like charge-backs and the reasons for them. The details of the codes and categories are implementation-specific. Unless, as Increase seems to be implying, their domain is the payment networks and fintech and their customers care about them the same way a kernel programmer would care about the details of memory allocators or schedulers, while most application programmers just want them to exist and work in a consistent way.

jackflintermann
0 replies
1d20h

I appreciate the thorough read!

For deprecations we're lucky in that the underlying systems don't change very much (the Input Message Accountability Data isn't going anywhere). But we'll run into collisions when we, for example, start issuing cards on Mastercard as well as Visa.

We have experimented with a couple of, um, abstractions, and may do so there. One rule we've stuck to, and likely would as well, is to keep the "substrate objects" un-abstracted but to introduce higher-level compositions for convenience. For example, there is no such thing as a "Card Payment" (https://increase.com/documentation/api#card-payments) - it's just a way to cluster related card authorization and settlement messages. But it's extremely useful for users and nontrivial to do the reconciliation, so we tried it. But we think it's essential that the underlying network messages (the "substrate objects") are also accessible in the API, along with all the underlying fields etc.

Unfortunately 100% of the public APIs I have worked on are in payments. I wish I had another lens!

andrewstuart
3 replies
1d20h

I hate abstractions. Program the thing as it is intended.

Why do programmers always need a library between them and the API?

Jtsummers
2 replies
1d20h

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
1 replies
1d19h

I 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
0 replies
1d17h

I 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

RobotToaster
3 replies
1d8h

No abstractions? So their API lets me control the individual registers on their CPU then?

dkjaudyeqooe
2 replies
1d8h

No, sorry, registers are an abstraction.

The API only lets you set voltages on individual wires.

layer8
1 replies
19h22m

Do you also have to provide the clock signal for processing? Might become expensive in terms of API calls.

dkjaudyeqooe
0 replies
18h35m

I had to think about it for a sec, but clock signals are indeed an abstraction so you have to provide them.

spandrew
2 replies
1d22h

Love 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
0 replies
1d20h

Yeah 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

lwhi
2 replies
1d19h

So 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?

lwhi
0 replies
10h10m

More complexity is bad imo.

kikimora
2 replies
1d21h

I 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
1 replies
1d21h

How is the leakage noticeable?

kikimora
0 replies
1d17h

I’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.

theptip
1 replies
1d13h

This 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
0 replies
1d12h

Or 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.

shironandonon_
1 replies
1d16h

TLDR: we are special and created our own standard.

ngrilly
0 replies
1d11h

They are saying the opposite: we follow existing standards as much as possible.

l5870uoo9y
1 replies
1d21h

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
0 replies
1d20h

Foundationally, 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!

hinkley
0 replies
1d13h

I 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.

west0n
0 replies
1d17h

If we didn't have abstractions like POSIX, applications would need to write an adaptor for every supported file system.

the_af
0 replies
1d5h

Interesting.

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?

tegling
0 replies
1d12h

One 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.

summerlight
0 replies
1d22h

I 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.

jackflintermann
0 replies
1d22h

Author here - this has been a useful mindset for us internally but I'm curious if it resonates externally. I'd love your feedback!

dheera
0 replies
1d15h

Java engineers need to see this. Goddamn Proxies, Factories, and Beans. Fragments, Surfaces, Runnables.

danecjensen
0 replies
1d9h

Strong work Jack!

bvrmn
0 replies
1d18h

Also it greatly helps to not overuse nouns and try to forcefully model verbs with resource entities.

Splizard
0 replies
12h44m

I 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
0 replies
6h4m

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
0 replies
1h23m

But 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.