Hacker News new | ask | show | jobs
by bogwog 808 days ago
So instead of managing state on the client, you manage state on the client and the server? That doesn't seem like an improvement, even if it saves you from having to build yet another API.
9 comments

It's never that simple. In web applications there's always these types of states:

    * States that the client needs to keep track of
    * States that the server needs to keep track of
Then on top of those there's two more kinds of states that overlap but they're not quite the same thing:

    * States that only need to exist in memory (i.e. transient)
    * States that need to persist between sessions
There's a seemingly infinite number of ways to manage these things and because "there's no correct way to do anything in JavaScript" you either use a framework's chosen way to deal with them or you do it on an ad-hoc basis (aka "chaos" haha).

In the last sophisticated SPA I wrote I had it perform a sync whenever the client loaded the page. Every local state or asset had a datetime-based hash associated with it and if it didn't match what was on the server the server would send down an updated version (of whatever that thing was; whether it be simple variables, a huge JSON object, or whole images/audio blobs).

Whenever the client did something that required a change in state on the server it would send an update of that state over a WebSocket (99% of the app was WebSocket stuff). I didn't use any sort of sophisticated framework or pattern: If I was writing the code and thought, "the server needs to keep track of this" I'd have it send a message to the server with the new state and it would be up to the server whether or not that state should be synchronized on page load.

IMHO, that's about as simple a mechanism as you can get for managing this sort of thing. WebSockets are a godsend for managing state.

Interesting. What kind of app was it? How did you decide whether the server needed to keep track it? I’m assuming that was predetermined? What about new requirements, which would need to be tracked by the server? Could the app deal with this dynamically without code changes?
It was Gate One: https://github.com/liftoff/GateOne

(Note: Company no longer exists so it is unmaintained, fork at will =)

It's been so long since I've looked at the code but I did write excellent documentation, most of which is generated from docstrings. Example:

https://github.com/liftoff/GateOne/blob/master/gateone/core/...

You can read the documentation here:

https://liftoff.github.io/GateOne/

If you look at the bookmarks plugin you can see an example of how I performed this style of client<->server synchronization using an Update Sequence Number (USN):

https://github.com/liftoff/GateOne/blob/6ae1d01f7fe21e2703bd...

After the web client connects to the websocket all the plugin's `init()` methods are called and the bookmarks plugin's javascript file calls `userLoginSync()`:

https://github.com/liftoff/GateOne/blob/6ae1d01f7fe21e2703bd...

It sends the current USN (which is retrieved from `localStorage`) to the server (the routing is quite sophisticated... Just know that the message ends up going to the correct function =) and if it's different from the USN on the server the server will send an updated list of bookmarks to the client at which point the client will take care of that via its own handler:

https://github.com/liftoff/GateOne/blob/6ae1d01f7fe21e2703bd...

If you examine the bookmarks.js from top to bottom (it's not THAT long) you should get the gist of where and how various states are stored. That plugin mostly deals with stuff in `localStorage` but if you poke around in Gate One you'll see vastly more sophisticated state synchronization and state update routines at work.

Eventually, some form of this paradigm is going to win.

In practice, applications need state on both the client and the server. The server needs the authoritative state information (since the client is untrusted), but the client needs to be able to re-render in response to user interaction without a round-trip.

Some things just end up being a better experience fully client-side. Don't go all in on it - just do it when it makes sense.

Another thing I like about this is the ability to be able to use Svelte as a templating language rather than Heex.

It is just a new generation rediscovering ColdFusion, Web Forms, JSF, PHP, Spring, Rails...
Sounds pretty reductive and dismissive. Have you actually used LiveView and compared the DX? I've used a a few of the technologies you listed, and LiveView is a different animal.
Could you elaborate? I don't see many similarities between those and LiveView.

The difference between traditional technologies that render HTML server-side and LiveView, is a persistent connection to the server which allows it to re-render templates in response to server-side events and patch client-side HTML without writing any Javascript.

Mostly based on WebSockets and Server Push.

As one example of such approaches, .NET has SignalR since 2013.

And WebForms could use designer tooling since 2001, and then there was ASP.NET AJAX Control Toolkit.

Not quite, I'd say that SignalR is comparable to Phoenix Channels. It's a communication protocol that can use different transport protocols, Websockets being one of them.

LiveView builds on top of Phoenix Sockets. When the page is loaded for the first time, it starts a lightweight server-side process on the BEAM VM. This long-lived process renders the HTML template (which can consist of multiple SPA-style components) and keeps all of the relevant "props" (called assigns) in-memory.

Every time a new event is received, either client-side (e.g. button click) or server-side (typically via a PubSub subscription), that long-running process will update its assigns (props) to reflect the new state. The framework then intelligently re-renders parts of the HTML template which depend on those modified assigns, and sends a minimal HTML diff to the client.

All of this can be done without writing any custom JS. Basic client-side events are usually set up with special HTML attributes like `phx-click=my_custom_event` and automatically wired up by LiveView JS bundle to be received by that long-running process.

But it doesn't have the BEAM VM. The runtime matters a lot, and LiveView wouldn't work as well if it wasn't running under the BEAM.
This isn't particularly convincing, as the pattern -- loosely: keep a websocket open and keep the formerly clientside app state serverside while passing events and pushing diffs -- seems to appear successfully outside of the BEAM vm. For example, blazor serverside (.net) and laravel livewire (php).

I haven't checked but I'm sure there will be a python one too

You are not convinced because you don't know the details. But I am not paid to advocate or to even try to convince. You've been informed now -- from here on it's on you as to whether to remain biased or to expand your horizons.
Tomato, Tomato, it has another VM.
It's your right to delude yourself that the runtime does not matter. That's not a discussion I am willing to have though, especially when exactly 100% of my 22+ years of programming experience have demonstrated, time and again, that the runtime inevitably ends up making a lot of difference (sometimes all the difference even).

Or, when you don't have a runtime -- as is the case with Rust, kinda sorta I mean because technically `tokio` can be classified as a runtime -- then you rely on a stricter compiler.

Both strategies work pretty well.

I am not shitting on C# / .NET, they are solid as hell. But some things the BEAM VM just does better and everyone who worked with old-school VMs (in my case the JVM) and the BEAM VM can tell you that.

But again, you do you, think what you will. ¯\_(ツ)_/¯

This saying really doesn't work in writing, I just read ..err.. 'tomato, tomato'
What does php spring and rails have to do with this? I'm confused haha
Keeping sessions on both sides.
This is no different than any other optimization performed in any other software ecosystem. There is no reason to do this unless you have to. And the reason you would have to is because of performance and user experience.

When your web application requires the following:

- Large amount of user interaction (requires client side JavaScript)

- Large amount of experimentation (bundle is not static, changes per request)

You are going to want to split up the logic on the server and client to reduce the amount of JavaScript you're sending to the client. Otherwise you have a <N>MB JavaScript bundle of all the permutations you're application could encounter. This may be fine for something like a web version of Photoshop where the user understands that an initial load time is required. But for something like Stripe, Gmail, etc. where the user expects the application to load instantly you want to reduce the initial latency of the page.

You can move everything to the server, but like GitHub experienced you then encounter a problem where user interaction on the page suffers because user actions which are expected to be instant, instead require a round trip to the server (speed of light and server distribution then becomes an issue).

You can lazy load bundles with something like async import, but then you run into the waterfall problem, where the lazy loaded JavaScript then needs to make a request to fetch it's own data, and you're left making two requests for everything.

If you encounter all these problems then you end up reaching for solution which make it easier to manage the complex problem of sharing logic/state between the client and the server.

Are you comparing with PWAs? Or what’s the baseline? Because with PWAs you have both too. State is accessed from the client with requests and you have to manage caching and stale state as well, to ensure performance and not drifting out of sync across components, no? Last I checked out a modern “performant” graphql stack – it was horrifyingly complex and full of knobs.
You already manage two kinds of state when you are building a React app:

- State coming from the server as a result of user actions

- Local state which isn't intended to be shared to the server, usually UI stuff.

> build yet another API.

It appears that's one of their major problems. They're not "building" APIs. They're just slapping "one shots" onto the side of a router whenever the need arises. This speaks to a complete lack of a planning and design phase.

I guess if you want to build something without any plan whatsoever, this might be a way to "improve" that process, but there's a much simpler one that doesn't require your team to become polarized over a framework.

Yeah that is the crux of most modern three-tiered architectures (client<->server<->database) these days. We're working on simplifying this with a new concept: a full-stack database.

If you run an identical query engine on client and server and then sync data between them, a client can just write a normal query on the client and you get an optimistic response instantly from a local cache and then later a response from the authoritative server. Frankly, this is where most highly interactive apps (like Whatsapp, Linear, Figma, etc) end up anyway with bespoke solutions we're just making it general purpose.

That sounds a lot like CouchDB and PouchDB. I haven't used the latter, but CouchDB provides a standard REST API (with authentication) out of the box. You'll probably need to add a custom API for more complex stuff, integrating with other services, etc. But all the boring CRUD stuff is already built into the database.

But more stuff like this is always welcome!