Hacker News new | ask | show | jobs
by cratermoon 785 days ago
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?

3 comments

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!

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

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

> 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/

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