A payment service at a large e-commerce company calls
chargeCard(amount, token). The engineers who wrote that
call have no idea whether the underlying processor is Stripe, Braintree,
or an internal gateway. They do not know whether the charge goes over
REST or gRPC. They do not know whether there is a retry queue behind the
scenes. They know one thing: they pass an amount and a token, and they
get back a result. Three years later, the company switches processors.
The payment service code does not change. That is what a good
abstraction achieves.
Now consider the alternative. A different team wrote
chargeStripe(amount, stripeToken, idempotencyKey, retryCount).
When the processor changes, every caller changes. The implementation
detail — Stripe — leaked through the interface into every system that
depended on it. The abstraction failed.