[HN Gopher] No Abstractions: our API design principle
       ___________________________________________________________________
        
       No Abstractions: our API design principle
        
       Author : jackflintermann
       Score  : 150 points
       Date   : 2024-04-25 19:15 UTC (3 hours ago)
        
 (HTM) web link (increase.com)
 (TXT) w3m dump (increase.com)
        
       | jackflintermann wrote:
       | Author here - this has been a useful mindset for us internally
       | but I'm curious if it resonates externally. I'd love your
       | feedback!
        
       | spandrew wrote:
       | 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 wrote:
         | 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
        
         | esafak wrote:
         | How Stripe builds APIs and Teams:
         | https://www.youtube.com/watch?v=IEe-5VOv0Js
        
       | chowells wrote:
       | 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 wrote:
         | 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.
        
           | exe34 wrote:
           | I thought I understood everything you said, but isn't Stripe
           | a middleperson here?
           | 
           | > without a middleman.
        
             | koreth1 wrote:
             | 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 wrote:
               | Makes sense thanks!
        
             | OJFord wrote:
             | Yes, GP's point is 'good luck to you doing that yourself,
             | without a middleman [such as Stripe]'.
        
           | jackflintermann wrote:
           | 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 wrote:
             | 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 wrote:
               | 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
        
         | arrowsmith wrote:
         | 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.
        
         | conroy wrote:
         | 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 wrote:
           | The article says "no abstractions", but HTTP is often exactly
           | that: an abstraction over lower-level protocols.
        
             | jackflintermann wrote:
             | 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.
        
             | chaos_emergent wrote:
             | 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 wrote:
         | 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.
        
       | summerlight wrote:
       | 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.
        
       | kikimora wrote:
       | 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 wrote:
         | How is the leakage noticeable?
        
       | l5870uoo9y wrote:
       | > 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 wrote:
         | 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!
        
       | lolpanda wrote:
       | 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 wrote:
         | I've always seen currencies multiplied by 100 to remove the
         | need for floating point.
        
           | nijave wrote:
           | Yeah, this seems like a common pattern. Not sure about
           | currency with arbitrary place values though (like Bitcoin)
        
             | deathanatos wrote:
             | 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 integer1. The value 1 is 1 sat, or 0.00000001 BTC.
             | 
             | 1(where you need to; otherwise, I'd use some fixed point
             | type if my language supports it)
        
           | akavi wrote:
           | That's not quite a sufficient rule. Eg, 1 Bahraini Dinar is
           | 1_000 Bahraini Fils.
        
         | freedomben wrote:
         | 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.
        
         | benhoyt wrote:
         | 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 wrote:
           | 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.
        
         | endofreach wrote:
         | 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 wrote:
           | 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...
        
         | cratermoon wrote:
         | neither. Use rational or some other better type.
        
         | tadfisher wrote:
         | 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.
        
           | koreth1 wrote:
           | By way of example: when I worked on payment code in Java, we
           | accepted numeric JSON values in our API but they were
           | deserialized into `BigDecimal` fields in our payload classes,
           | not `float` or `double`.
        
       | andrewstuart wrote:
       | I hate abstractions. Program the thing as it is intended.
       | 
       | Why do programmers always need a library between them and the
       | API?
        
         | Jtsummers wrote:
         | > 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.
        
       | cratermoon wrote:
       | 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 wrote:
         | > 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 wrote:
           | 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?
        
         | jackflintermann wrote:
         | 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!
        
       | cpeterso wrote:
       | This 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-l...
        
       | Karellen wrote:
       | > 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 wrote:
         | Git 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-
         | Po...
        
       | lwhi wrote:
       | 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?
        
       ___________________________________________________________________
       (page generated 2024-04-25 23:00 UTC)