Nice from a testing standpoint too, since you can trivially mock out the hardware.
That said, a lot of what’s actually hard about IO is the error/fault handling, imposing timeouts and backoffs and all that jazz. At a certain point I’d wonder if extracting this out to a separate interface might obscure the execution flow in some of these scenarios.
> That said, a lot of what’s actually hard about IO is the error/fault handling, imposing timeouts and backoffs and all that jazz.
Application-level timeout/backoff handling is always scary to me, because I don't know how to make robust tests for it. I wonder if you couldn't use the same I/O-less approach, and split the logic out into pure functions that take the time passed/error state/... as value arguments, instead of measuring the physical time using OS APIs. It's probably not something for reusable libraries, but it could still be a nice benefit to be able to unit test in detail.
Split the re-triable action into one function, make a wrapper function that re-tries if needed, and use a third function that makes the decision to re-try and how long to back off.
Then you can test the decision function trivially, the re-try function by mocking the action and decision, and the action function itself without back off interfering.
That’s what you suggested, just saying that I did that in a Python API client with the backoff library and the result is pretty neat.
I love the idea of it all being totally abstract but in my experience this stuff is usually tied in with application level behaviours too, so you could end up with a pretty messy API between the layers.
That said, a lot of what’s actually hard about IO is the error/fault handling, imposing timeouts and backoffs and all that jazz. At a certain point I’d wonder if extracting this out to a separate interface might obscure the execution flow in some of these scenarios.