GrammAcc
Should there be a client-server interaction-adapter API in Elixir?
I was reading through this topic: Nex - A minimalist web framework for indie hackers and startups and I love minimalist web stuff and the “traditional” page-based web of yore, but I also have never liked opinionated frameworks. I always like the pitch, but every time I use one, it makes me sad when I try to write code the way I want and it doesn’t work. I have always stuck with Flask/Quart in Python because it gives all of the difficult-to-implement functionality of a web app, but it doesn’t try to build the legos ahead of time. I’m not limited by however the framework wants the pieces to fit together. It takes care of the hard parts, and I get to just do what I enjoy which is design code that is fun for me personally to work with.
That’s what I love about Phoenix as well. It felt heavy and opinionated at first with the generated code, but after learning it more, I realized I could structure things however I wanted by straying from the defaults.
This message in the linked topic brings up an interesting idea related to all that Nex - A minimalist web framework for indie hackers and startups - #28 by thiagomajesk LiveView as a generic client-server interaction engine.
I’m not sure how feasible it would be to decouple LiveView from Phoenix, but I normally build LiveView-like websockets in my Python apps, and I think it’s feasible to have something that achieves the same things as LiveView as a standalone library.
Carrying on with that idea and my love for frameworks that provide the tools to build the legos myself instead of providing the legos out of the box, I think generalizing the idea of a client-server interaction library might be an interesting thing to explore.
My thinking is that the modern web has been mostly dominated by SPA frameworks, but there’s also been a lot of work going on with the Hypermedia stuff to try to reduce the bloat of the web somewhat. All of these things are basically just different models of client-server interaction. FE-first reactive frameworks use a request-response model for creating a more stateful user experience. Realtime frameworks like LiveView try to accomplish a similar experience with a stateful connection and the ability to push from the server instead of having everything driven by the client.
I think there must be an abstraction hiding here somewhere. Maybe rather than thinking about things in terms of SPA/realtime/hypermedia, we could find a way to abstract pushing/pulling in a bi-directional model. Then we can use that same abstraction on both the client and the server and it doesn’t matter how the actual connection is implemented. This has the advantage that it also works for server-server interactions in a service architecture.
Assuming something like Plug could be implemented for this client-server push/pull interaction abstraction, then the choice of LiveView or HTMX or whatever would be a user decision and not something the framework author needs to decide up front. It would also simplify fallback/failover scenarios when things like websockets or SSE fail in realtime apps. Making it easier to implement both stateful connections with websockets or HTTP2/3 and also stateless connections with the same interface would be super helpful both from a productivity perspective (single mental model for building different kinds of apps) and from a quality perspective (fallback/failover for different networking implementations).
Thoughts?
Most Liked
rhcarvalho
Phoenix.Socket is one such abstraction. It doesn’t unify with regular HTTP requests (the Plug abstraction), but it is nice that it is not exclusively tied to a WebSocket-based implementation.
Phoenix ships with a LongPoll transport, and I can imagine one could write an alternative transport using POST+SSE, for example.
As @derek-zhou wrote, unifying HTTP and WS forces us to pick the minimum common denominator, and settle for the worse of both worlds.
- HTTP gives us caching, easier debugging, statelessness.
- WS is a persistent connection, less handshakes, harder debugging, stateful.
An abstraction over that would need to be stateless, not support caching/response headers/cookies, …
This difference between the two technologies is not only the cause of different pipelines in Phoenix (plugs vs on_mount), but also has a very deliberate use for certain scenarios that require one over the other, e.g. login/logout in LiveView requires a regular HTTP request to set cookies.
derek-zhou
HTTP request/response and websocket streaming are very different things in both semantics and targeted applications; the former is more like UDP and the latter is more like TCP. I am afraid a grand unifying scheme will not exploit each to their full potential. An optimized architecture should be built from the ground up for either a connection or a connection-less networking API. I can cite 2 examples where crossing the 2 did not work out: HTTP/2 server push and Phoenix Socket over long poll.
GrammAcc
Disclaimer: I hate React and I wish people would stop trying to force it into every shaped hole they can find. ![]()
I don’t think trying to create an abstraction over LiveView, HTMX, React, etc is a good idea. I was thinking more along the lines of the communication semantics.
If we could merge the apis for handling a route for HTTP request-response cycle and a stateful persistent connection (e.g. websockets) into a single uniform api, then the programmer wouldn’t have to choose a framework that’s good at realtime stuff to have stateful interactivity or a framework that’s optimized for incremental stateless updates for component-based DOM-patching, etc.
I’m not sure how feasible it is ofc since if it was a simple problem, it would have been solved a long time ago, but now that we have our choice of well-supported protocols for use in browser-based and service-oriented applications, it would be worth investigating a possible abstraction over all of them.
Not just a library that makes working with each different protocol easier we’ve got those already.
My initial thoughts were that separating out sending and receiving data into separate functions (either declarative or imperative doesn’t matter) would be a good first step.
For example, we can model a bi-directional websocket connection by implementing two handlers:
def push(%{updated: val} = state), do: {val, state}
def pull(%{updated: val} = msg, state), do: {:reply, Map.put(state, :updated, val)}
def pull(msg, state), do: {:noreply, state}
This same api would work for stateless HTTP request-response communication as well.
The implementation could raise or return a 404 or something else if returning {:noreply, state} from a pull/2 running over an HTTP1 connection.
I actually want this kind of api in a web framework regardless of its broader usefulness because I think it makes it complex connections more composable. In Python I use the Quart web framework for websocket stuff, and it has a single @websocket decorator for creating a websocket “route”, and the code inside that handler runs both sending and receiving messages, so I have to manually separate sending and receiving to make them run concurrently. Separating them out ends up making the code a lot simpler and easier to test as well though, so I think it’s a good pattern to enforce if possible.
P.S. I really think send/1 and recv/2 are better names for the above functions, but that would clash with Elixir’s process utilities, so ![]()
Last Post!
GrammAcc
Very interesting. I assumed that a navigate event would reconnect the websocket, but I must have misread or simply missed that detail in the docs. Thanks for the correction!
I guess that makes sense though since nested LiveViews have an on_mount/3 as well.
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









