[HN Gopher] No Abstractions: our API design principle
       ___________________________________________________________________
        
       No Abstractions: our API design principle
        
       Author : jackflintermann
       Score  : 312 points
       Date   : 2024-04-25 19:15 UTC (1 days 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
        
             | jrochkind1 wrote:
             | 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.
        
             | wodenokoto wrote:
             | 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.
        
           | fendy3002 wrote:
           | 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.
        
         | 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.
        
               | gr4vityWall wrote:
               | 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 wrote:
               | Thank you!
        
             | 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.
        
             | gr4vityWall wrote:
             | 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.
        
         | 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.
        
         | alex_lav wrote:
         | > If there's no abstraction, what's your value-add?
         | 
         | But then
         | 
         | > I don't care enough to read
         | 
         | Hmmmmmm.
        
       | 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?
        
           | kikimora wrote:
           | 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.
        
       | 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.
        
           | cateye wrote:
           | 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.
        
           | kadoban wrote:
           | If you use a higher constant, 10000 or 1000000 or something,
           | you give yourself a good amount of more fleixibility.
        
         | 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.
        
             | krainboltgreene wrote:
             | Funny, because that's exactly what Stripe does
             | (https://docs.stripe.com/billing/subscriptions/usage-
             | based/pr...)
        
         | 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 request payloads but they
           | were deserialized into "BigDecimal" fields in our payload
           | classes, not "float" or "double".
        
           | lolpanda wrote:
           | Good point. it's not a problem with JSON. It's just that most
           | of the JSON libraries by default parse numbers into floats.
        
           | int_19h wrote:
           | Regardless of what the libraries _should_ be doing, there is
           | the reality of what they _are_ doing.
        
           | meekaaku wrote:
           | Yes, but say in javascript if you do a JSON.parse(), it will
           | give you a double float right?
        
         | GeneralMayhem wrote:
         | 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/...
        
       | 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.
        
           | koreth1 wrote:
           | 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 wrote:
             | 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
        
       | 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?
        
             | travisjungroth wrote:
             | 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.
        
         | 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!
        
         | Terr_ wrote:
         | > 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 wrote:
           | > 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.
        
       | 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...
        
         | hinkley wrote:
         | 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.
        
       | 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...
        
           | mbork_pl wrote:
           | 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.
        
         | yen223 wrote:
         | 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 wrote:
           | 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 wrote:
             | 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 wrote:
               | Just say that you don't support mixing both APIs.
        
               | actionfromafar wrote:
               | That'll fix it. ;-)
        
               | lazyasciiart wrote:
               | 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"
        
               | withinboredom wrote:
               | 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.
        
               | Pxtl wrote:
               | 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).
        
           | campbel wrote:
           | 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 wrote:
             | This reminds me of the Kubernetes API.
        
             | IggleSniggle wrote:
             | This reminds me of the git API.
        
         | jampekka wrote:
         | 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 wrote:
           | 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.
        
             | withinboredom wrote:
             | Someone has to write that middleware...
        
             | f1shy wrote:
             | So OpenGL is a total failure! I learned something today...
             | What should I use?
        
               | wizzwizz4 wrote:
               | 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.)
        
           | dumbo-octopus wrote:
           | It's 4 async calls, and it can be done entirely in the main
           | thread so long as you use the async API.
           | https://developer.mozilla.org/en-
           | US/docs/Web/API/FileSystemW...
        
             | jampekka wrote:
             | For OPFS it's still 6 calls. And the async API doesn't
             | support flushing writes.
             | 
             | https://developer.mozilla.org/en-
             | US/docs/Web/API/File_System...
        
           | cpeterso wrote:
           | 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". :)
        
         | InvisibleUp wrote:
         | .NET also does this a lot. Here's a recent devblog post looking
         | at file I/O: https://devblogs.microsoft.com/dotnet/the-
         | convenience-of-sys...
        
           | eterm wrote:
           | That's a fantastic blog post, worthy of it's own HN
           | submission.
        
         | Groxx wrote:
         | 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 wrote:
           | 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.
        
         | andenacitelli wrote:
         | 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.
        
         | zellyn wrote:
         | 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 wrote:
           | 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.
        
         | eru wrote:
         | Compare also exokernels, and how they delegate abstraction to
         | libraries, not the OS.
        
         | lwhi wrote:
         | I like this idea a lot.
         | 
         | One level of API for implementation model.
         | 
         | And second level for mental model.
        
         | devjab wrote:
         | 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.
        
         | jimbob45 wrote:
         | That sounds like the exact philosophy the Vulkan devs took
         | versus OpenGL.
        
       | 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?
        
         | Supermancho wrote:
         | https://docs.stripe.com/api/versioning
         | 
         | A versioned API. Which means more APIs to maintain, until they
         | are removed.
        
       | bvrmn wrote:
       | Also it greatly helps to not overuse nouns and try to forcefully
       | model verbs with resource entities.
        
       | west0n wrote:
       | If we didn't have abstractions like POSIX, applications would
       | need to write an adaptor for every supported file system.
        
       | shironandonon_ wrote:
       | TLDR: we are special and created our own standard.
        
         | ngrilly wrote:
         | They are saying the opposite: we follow existing standards as
         | much as possible.
        
       | dheera wrote:
       | Java engineers need to see this. Goddamn Proxies, Factories, and
       | Beans. Fragments, Surfaces, Runnables.
        
       | theptip wrote:
       | 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 wrote:
         | 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.
        
       | tegling wrote:
       | 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.
        
       | danecjensen wrote:
       | Strong work Jack!
        
       | RobotToaster wrote:
       | No abstractions? So their API lets me control the individual
       | registers on their CPU then?
        
         | dkjaudyeqooe wrote:
         | No, sorry, registers are an abstraction.
         | 
         | The API only lets you set voltages on individual wires.
        
       | the_af wrote:
       | 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?
        
       ___________________________________________________________________
       (page generated 2024-04-26 23:02 UTC)