Phoenix Dev Blog – Streams
fly.io
fly.io
Thanks Chris and all the contributors for your hard work.
I would have little fear diving in at this point. We are not a small org and it was a big bet that has paid off.
There is no silver bullet, and sometimes doing a UI update on the client is still a better UX.
I can foresee a future where everyone goes down the LiveView rabbit hole, only to improperly blame it for causing too much UI update latency in some cases.
When it all goes well, it's very clean. If you can't make sure the server is evenly loaded amongst all your clients and close to them, it will probably feel quite poor in comparison to some client side update code.
LiveSvelte: https://github.com/woutdp/live_svelte/
So while you can replicate Live View everywhere, it's never going to be as convenient or expressive as the real thing.
On the project I am working on I have a Live View dashboard that's getting updated in real time from distributed async events coming from any server in the cluster with less than a dozen lines of code, and using nothing more fancy than standard Phoenix libraries (Phoenix.PubSub) and BEAM primitives (pg2, nodes, mailboxes, etc.). Porting Live View to Go, Python, Rust, Javascript is the easy part. You'll still miss the BEAM dearly.
as Erlang OTP uses a shared nothing architecture
each green thread has it own GC
communication is only via message passing
it is ok to fail over as the threads are easy to replicate as they are cheap
it is possible Golang via ergo as golang gets concurrency right, but doesnt have evolution systems and BEAM
Haskell via cloud haskell might replicate live view... but they are not the sensible defaults as in erlang/elixir/otp
Python never as their async concurrency parallelism story is ill-thought out w GIL limitations. Rust very very hard as their async concurrency design is extremely complex...
If you don't have a good async model, you're down to a thread/process per connection, which can get very expensive since you will have a thread for every user on your site.
You need something like Erlang's lightweight internal "processes", golang's coroutines, or maybe even Node.js evented I/O async model to handle this well.
No other popular language can pull this off.
It's not the same exact model and I wouldn't classify it as a replication of LiveView, it's more of an extension of the Turbolinks model from 2016 with a decent story for adding in frontend JS where needed.
I think it's comparable from a bottom line point of view of it being a solution that lets you send snippets of HTML over the wire (literally the tagline of Hotwire) to very quickly build nice feeling web apps with minimal to no Javascript.
Instead of going all-in with state and Websockets like LiveView, Hotwire sticks to a stateless HTTP model and sprinkles in Websockets only when necessary such as when broadcasting an event to any connected clients. I've used it in Flask before and it took a few minutes to get going. For the broadcasting bits server side, it works with Rails out of the box since this toolset was extracted from Hey and Basecamp. Other frameworks have their own ports tho.
I haven't touched rails in a long time but isn't that how it all used to be done way back when? I feel like my original rails apps were making requests and I was returning HTML partials to be (crudely) diffed into the DOM?
They weren't diff'd into the DOM client side. Optionally, there was SJR (Server-generated JavaScript Responses) from 10 years ago[0] that you could do to get similar behavior but now it's much easier and much more maintainable with Hotwire.
In your original Rails apps you could make a request, render a template that has 0 ore more partials and the HTTP response from Rails by default would be all of the HTML, from <html> to </html>. Your browser would parse all of that and produce the page.
Then Turbolinks came into play around 2016 and it's a 1 line of JS drop-in solution so the above still happens, except now instead of <html> to </html> being rendered by your browser, only the <body> to </body> gets rendered. This is a massive improvement for page load speeds because now all of your JS / CSS in your <head> doesn't get parsed on every page load. Even with caching this is a big win since your browser still needed to parse it. It intercepts your links and makes them ajax calls transparently. It worked for form submissions too but you had to do a little bit of work on the back-end to make this work (Rails did it by default). This works with any tech stack tho.
Then Hotwire came along:
- Now you can swap the whole body with Turbo Drive in the same way as Turbolinks did in the past except it also works for form submissions now too with no backend modifications. It's IMO the single biggest bang for your buck that you can do on a site to make it feel fast with no effort at all.
- You can also use Turbo Frames to only swap a portion of the page instead of the whole <body>. It's 1 new HTML tag, you just wrap your content into a frame tag and you're done. Everything "just works". Only that frame's HTML gets sent over the wire, the other content on the page remains untouched. For example if you had a main content area and a sidebar you could have the main content be in its own frame and as you click links and do things in that area the sidebar will never get re-rendered unless you purposely break out of the frame by setting a target on the link.
- Then there's Turbo Streams which works for non-GET requests by default. So if you posted a new comment on the bottom of a blog post you can tell the stream to append it to a div (or prepend, update, delete, etc.) so you end up only sending a tiny bit of HTML over the wire for that single comment's markup. Optionally you can also instruct that to be broadcast over a websockets channel. In Rails this is seamless, it's 1 extra line of code and now you have live updates broadcast to anyone connected to that channel. You can also use streams to update multiple areas of a page if you wanted to, such as append the comment and add a (1) notification to your nav bar or page's title.
You as an end user don't need to write much JS to do all of the above. Then there's the Stimulus part of Hotwire for when you do need to write JS for more client side interactivity. All of this works with any back-end tech stack.
[0]: https://signalvnoise.com/posts/3697-server-generated-javascr...
With contract work I get exposed to a number of different tech stacks too.
For example, A -> B -> C -> D is a linked list, but so is B -> C -> D, C -> D and so on, and each linked list is just a sub structure of another linked list.
This is interesting, because now we can confidently say that A -> B -> C -> D is a different list from B -> C -> D (even though one recursively contains the other one). Because we have that guarantee (along with the fact that a list CAN'T change), we don't have to define the same data twice, and we can reuse existing linked lists! This is called Structural sharing. Adding to this list as an extremely low memory cost.
If we go back to our html snippets that we are sending down the wire, you can see why all of this would have an advantage over how an imperative lang might represent strings under the hood.
1. Elixir strings are Erlang binaries (byte arrays), not charlists. Charlists are becoming rare nowadays even in Erlang because they're so memory-inefficient.
2. Comparing two lists for equality requires traversing them, you can't just look at the first node. If they were built independently they won't share structure.
3. HTML templating is (reasonably) efficient because of iodata, not charlists.
The great and often unmentioned pro of cons cells/linked lists is that they are a very simple form of persistent data structure, that make immutability and memory sharing very easy, so are an excellent primitive to build a garbage-collected immutable and shared-nothing language upon like Erlang, or Clojure.
It has ways to indicate "things are in flight" but for distant deployments you probably still need to write JS to manage some in-page elements for reasonable feeling UX.
This isn't hard, it has support for interop with plain JS or existing libraries and a somewhat limited built in system for basic show/hide/set/remove attributes (somehow lacks a "toggle_class" and "toggle_attribute" function though so you can write those yourself...). You can dispatch any custom JS event from it to cover .. anything you need.
If you're exclusively deploying to on-site/one-site/geographically-constrained systems, then you actually skip that issue...
You also have to re-think about how write code as Elixir is immutable, though I see that as a bonus.
Phoenix (and by extension LiveView) are actually a lot smaller than you might think coming from Rails or the like, mostly leveraging existing tools.
Elixir/Erlang/BEAM make building systems incredibly pleasurable. It ships with many tools to make and connect complicated things together, in a reliable way. Depending on what you make you may find it very liberating after working with Node.
Most JS UIs behave like this: open a list view, watch a spinner until data is fetched from the server. There is practically no difference between that and showing a spinner first and then the list.
So you need to pay some attention to applying-css/hooking-into-the-tooling (phx-click-loading) to show that a button was indeed clicked and is working on fetching data.
Some things such as flipping between tabs can feel quite bad if you click and the tab-button just puts a spinner there or the button just "fades a bit". Its much nicer to replace the content pane with some mock-blocks to show "yes, the tab swapped and its loading data" which is where you need JS to temporarily hide/show some things.
And once you start doing that you should really be measuring the avg rtt on the socket because showing the mock-content for 1ms then swapping the real content is also bad, so you need JS again to figure the best UX for that client.
Or you simply render all the tabs in one go and again, duck into JS to perform the local show/hide.
I don't remember the exact circumstances from when I last dabbled with LiveView, but I recall that using a combination of CSS delay (so that fast connections wouldn't see a useless spinner) and an opacity transition style (to reduce jarring elements popping up suddenly) produced a nice effect, all with plain CSS and a phx-loading attribute.
You do not need to write that JS though. LiveView comes with a few helpers to assist in client-only features.
I was looking for something else after years in React ecosystem and came across Phoenix LiveView but I wanted to write in Typescript.
(Note: Am author)
[1] - https://LiveViewJS.com
The main limitation is likely offline apps, LiveView requires a persistent connect to the server. I doubt this is something you'll encounter for your use case.
- Here is a thread on ElixirForum: https://elixirforum.com/t/phoenix-liveview-in-production-exa... - Here is a neat little aggregator: https://liveviewdemos.com/, and here is the code for it: https://github.com/leandrocp/phoenix_live_view_collection
In real world applications, it's always "polling", because you're not sure when server finished your request.
Or liveview is for toy application ?
Note: You seems mislead the point here, to build interactive apps, of course you can just use websockets. What's more liveview solve ? I guess liveview = html + websocket.
To me LiveView is not about interactive apps so much as it is a way to build applications, period. You can build in a way that may bring you more joy, performance, and dev speed than you could otherwise.
Interactivity is a side effect, and it's a great one. But I wouldn't recommend LiveView if someone only wanted interactivity. I'd recommend LiveView if someone wants a different way to build than the React+API status quo.
What about liveview ? That's the question. They're different.
To be clear I don't believe that, but that's analogous to what I hear when I see this.
LiveView has composition of components, so there's an immediate answer to your question. You've already answered another piece with built-in soft real-time.
In all honesty, if you have looked at LiveView and don't understand what it brings then I'm not sure if you looked sincerely.
React and similar technologies actually not just scrap your boilerplates, it's real abstraction (forget the DOM API) with state and componentation. It's not like you scrap your jquery boilerplate).
I see people mess with DOM Api in their application, which is not the right way, because it's mixing different abstractions together to create a mess.
It's a complete way to build complex apps. I've personally built fairly decent complexity SaaS (18mo of dev) completely on LiveView + some react when it made sense.
Saying it's just HTML pushed over WebSocket is like saying React is just virtual DOM with components. There's much more to it when you get into it.
Plus it's enjoyable. I had significantly more fun and development speed building on it than the typical React+API approach.
That said it's still situational and I don't blindly recommend it to everyone. I think Elixir has one of the best graphql implementations (Absinthe), so I still recommend React+GQL as well.
I found people who's stuck in DOM abstraction like React keeps reinvesting what platform is already capable of. Some abstract is so nasty e.g. forwardRef .. like really? Real Event bubbling is so much better, it visit every damn node you got the ref for free.
And it's hard to overstate, but to also properly explain, how important this is and how it feels like magic when you do it. Because it lets you tap into everything Elixr (and Erlang VM) have to offer on the backend without ever having to think of "how the hell do I bring all that to the client".
My use-cases are: a big chunk of data is being processed, and while its being processed regular updates are sent over PubSub. Most actions a user initiates are long-ish-running and their updates are also sent over PubSub. Things that other users are doing are sent over PubSub and possibly need to be reflected in the UI.
In "traditional" JS-on-the-client-whatever-on-the-server model you have a plethora of questions of how to get that to UI: which endpoints to query, which updates to send over the wire, the formats, the frequence, authorisation, auth...
With LiveView your "client" is an Elixir view living on the server which just updates the view based on those PubSubs (or timers, or Kafka streams, or user-initiated actions, or...) and LiveView takes care of upating the DOM/HTML in the browser.
"Build a real-time Twitter clone in 15 minutes with LiveView and Phoenix 1.5" will give a good overview of this: https://www.youtube.com/watch?v=MZvmYaFkNJI
[1] Built-in in Phoenix, https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html
Another case are dynamic elements based on external factors not based on user actions (like for example the message counter on a social network), those are pretty annoying without liveview because you need to build a whole frontend event system just to change a number.
liveview is also using a socket, whose existence springs to life via the "websockets" modernization where the client is a browser. and the traffic bidirectional over the socket again keeps the server side library aware of events, and the browser-side aware of UI alterations to display.