Sttp client 4: improving the developer experience

Adrien Piquerez created a thorough and code-complete proposal on overhauling sttp client’s RequestT and SttpBackend types, by splitting them into multiple traits/classes, and thus reducing the number of type parameters. This is of course a binary-incompatible change, and would have to form the basis of sttp version 4.

Using that occasion, I’d like to gather wider feedback not only on the proposal, but also on your experiences with learning and using sttp client.

Starting with learning, some questions that would be helpful for us to understand how to improve the developer experience when using sttp:

  • what did you find most puzzling, when learning sttp?
  • when explaining sttp to co-workers, what was hardest for them to grasp?
  • which compiler messages have been misleading or not helpful?
  • do you use IDE’s type inference for sttp’s data structures, and if so, does it work well for you? did you understand why a specific type was inferred, and what the type means?
  • what was the largest road-block, that slowed you down when trying to make your first HTTP request using sttp?

Then, moving from learning to using sttp:

  • what’s are some of your daily “gotchas” that you have to keep in mind, when working with sttp?
  • is there any type of problem that surfaces again and again?
  • considering type safety: which area could be more type-safe, preventing some common bugs that need testing to detect?
  • how often do you define custom backend wrappers? do they work with a single, or multiple other backends / effect types / capabilities?
  • do you leverage sttp’s capability of customising any request (both partial & with the method/uri specified) using a single method?

Finally: if you have any idea on improving the API (even if it seems wild and improbable) - that’s the place to share :slight_smile:

Thank you!

1 Like
  • what did you find most puzzling, when learning sttp?

Websocket support, but this is (partially) gone from signatures now.

  • is there any type of problem that surfaces again and again?

response parsers (asJson). They are a bit complicated when you need to use anything more complicated. I cant give anything more precise at the moment but I remember fighting with them on couple occasions.

  • how often do you define custom backend wrappers? do they work with a single, or multiple other backends / effect types / capabilities?

We have some in many places, so relatively often. They typically need to work only with one underlying backend.

  • do you leverage sttp’s capability of customising any request (both partial & with the method/uri specified) using a single method?

you probably mean generic code that applies the same changes regardless of request completeness. If thats the case I can’t recall such code.

Finally: if you have any idea on improving the API (even if it seems wild and improbable) - that’s the place to share :slight_smile:

What I would love to have is ability to access raw response, regardless of what transformation where applied on top. Obviously this wont be possible for streams, but for most common case of strings/bytes being sent, this should be doable. We have an awkward asBoth() usage but we introduced some bugs when used not properly.

1 Like

if you have any idea on improving the API

Middlewares as first class Tapir concept.
Usecase: Distributed tracing middleware should be able to add the tracing headers to the docs.

Another thing I remember was challenging is to implement a monitoring http4s middleware that can figure out which endpoint is being hit so that it can increment the correct guage. It would be nice if Tapir Middleware would/could run after routing and has access to the endpoint object.

That’s a different project, but good suggestion as well - maybe you could move this to the tapir forum?

I’ll also change the title to explicitly say it’s about the client, my bad :slight_smile:

1 Like

Ditto - especially for dealing with broken APIs. e.g. reply with JSON on errors most of the time, but occasionally send back a raw string.

Ditto. We find it difficult to debug deserialization errors (especially with zio-json where error messages only show the offending next character).

what did you find most puzzling, when learning sttp?
The ZIO encoding with two separate error encodings. Most of the time absolve is fine, but new sttp users find this challenging.

considering type safety: which area could be more type-safe, preventing some common bugs that need testing to detect?

the uri interpolator is seductive, but can easily generate bad uri’s (e.g. substituting something besides strings)

Finally: if you have any idea on improving the API (even if it seems wild and improbable) - that’s the place to share :slight_smile:

  • We would like to see RequestMetadata included in ResponseException
1 Like

I just adopted our code to use 4.0.0-M1. I had to change just the imports and a few lines of code.

- private val backend: SttpBackend[Task, ZioStreams & WebSockets]
+ private val backend: WebSocketBackend[Task]
- def doConnect(uri: Uri, backend: SttpBackend[Task, ZioStreams & WebSockets], connection: Connection) 
+ def doConnect(uri: Uri, backend: WebSocketBackend[Task], connection: Connection)

All tests pass and also the benchmarks show no difference. So it looks very promising. Thx a lot.

1 Like

Hi!

I am coming a bit late but there is something I have not been able to do with sttp and maybe sttp 4 could be a chance to address it.

In Caliban Client, I have a function to transform a graphQL query into an sttp request: creating the request with the proper body and method, then parsing the response (using mapResponse) to return a nice object rather than a String. Then users are able to run this sttp request with the backend of their choice. This is perfect!

Now, I would like to do the same thing with WebSockets. I’d like to have another function that will transform my graphQL query into an sttp websocket request, and that function would take care of sending some “initialization” message(s), and parsing the received messages into a nice object to return a stream of these objects to the user.
I don’t think this is doable currently as Streams and Pipe do not expose anything. You have to choose a backend in order to do something, so the only way I can expose this function is to force the usage of a specific backend.

One interesting thing I saw is that in WebSocketStreamingTest there is a basic version of that since there is some generic code that requires a functionToPipe to be transformed into the proper backend Pipe.

Let me know if I missed something, or if this is something that could be solved.
Thanks!

Thanks! Definitely not too late, we’ve got to come back to sttp4 & API changes sometime soon :slight_smile:

If I understand correctly, this is somewhat partly implemented in tapir, where you can provide codecs for requests & responses (though no initialization messages, so these are simple functions LOW_LEVEL => HIGH_LEVEL, not Stream[LOW_LEVEL] => Stream[HIGH_LEVEL]. So on the sttp client level, I suppose what you’d want is to be able to map the defined response’s stream? Would that work?

Map the response would be nice, but also send some request.

Actually I have an example showing exactly what I want. This is the integration with Laminar/Laminext: caliban/package.scala at e1d2998a82378ec148714e09b586bee9c7023a15 · ghostdogpr/caliban · GitHub

Basically I send some message through the WebSocket and return a stream of converted responses. Then end-users can simply run that and consume it. I’d like to do the same thing with sttp but without choosing a backend.