Dimitry Salahutdinov

Optimistic UI and Live Uupdates with Logux & Ruby

wroc_love.rb 2019

00:00:14.810 Hi everyone! Let me start with a short introduction describing the main point of my talk. Here is the spinner—a common placeholder that indicates loading. Adding more spinners becomes increasingly frustrating for users. However, while spinners have become a standard in the industry, they still make users feel upset.
00:00:35.040 Hello again! My name is Dmitry, and I am a back-end developer from the middle of Russia. I am a family man with three kids, doing some open-source work illegally after midnight when my children are asleep. I work for a company that provides social content automation services, helping businesses with content workflow scheduling and analytics.
00:00:47.250 This project started as an initiative by some passionate developers working for evil merchants, and I want to emphasize that it has grown into something significant. So, let's dive into the term 'optimistic UI' and explore why it matters.
00:01:11.940 Imagine a classic form submission process. When a user fills in all the fields and clicks the submit button, the user interface freezes as it awaits an AJAX request. This makes the user unable to continue working on other tasks. However, statistics indicate that around 98% of AJAX requests succeed. What if we changed this process to make it more optimistic? We could modify the user interface immediately, changing the submit button’s state to 'success' right after the click. This way, from the user's perspective, there is no waiting, no staring at a disabled button, and no irritating spinner.
00:01:43.440 This approach is termed 'optimistic UI' and is based on the assumption that most requests will be successfully processed by the server. To illustrate this, let me share a story about three users at a pub. The first user drinks until he is tired and leaves after consuming a drink. The second user pays upfront for his drinks and leaves in a similar state, while the third user, the 'optimistic’ one, goes straight to the bar knowing how pubs operate.
00:02:06.840 This third user’s experience exemplifies the optimistic UI. Now, let’s discuss why optimistic UI is important. Time can be assessed from both objective and psychological perspectives. Objective time measurement operates with a stopwatch, while psychological perception relates to the feelings we experience while spending time on activities. For example, reading books satisfies curiosity and makes readers happy, while biking strengthens our bodies and promotes relaxation.
00:02:30.150 However, waiting on a user interface doesn't make anyone happy. Users are accustomed to paying for positive experiences and certainly would not pay to feel frustrated. Thus, I firmly believe that implementing optimistic UI is a key factor for the success and profitability of our projects.
00:02:57.840 According to Google’s performance model aimed at helping developers ensure quality user experiences, a delay of over 100 milliseconds in user interface response can lead to a loss of focus. No seller would want their customers to lose concentration just before making a purchase. Picture if Twitter's user interface adopted a pessimistic approach that displayed a spinner every time a user wanted to like a tweet. This would be an extremely detrimental user experience.
00:03:36.180 So, let’s eliminate user frustration by adopting optimistic UI. While the idea seems easy in theory, challenges arise when implementing it. I will review some common issues faced when attempting to create optimistic UI, particularly during local development, where everything appears flawless—packets are delivered without loss or delay. But, once the application moves to production, it encounters real network conditions, which can often be surprising.
00:04:02.280 Errors can occur, packets can be lost, and requests might be processed in inverted order, leading to network errors. Handling such errors effectively is critical when working with optimistic UI because the changes have already been applied to the user interface before the request is sent.
00:04:30.120 Being offline is one aspect of this challenge that many people misunderstand. They often think of being offline as a long-time absence from civilization, yet we experience offline periods daily, often for short durations during the day. Another significant issue arises when handling server errors. For instance, all changes may be successfully applied to the user interface, but for some reason, the server responds with an error, necessitating a resolution.
00:05:04.740 Merging conflicts is the last major challenge. When multiple users attempt to edit the same data simultaneously, those updates can conflict with one another. Many of these issues have individual solutions, but as of now, there is no comprehensive solution to address all these challenges for modern web applications apart from the classic 'reload' button.
00:05:36.270 While many solutions can be sourced from computer science, they have not yet been fully implemented for modern web applications. One idea is 'event sourcing', which changes the classical model of persistent object state and saves the sequence of state changes in events. This allows reconstructing the actual object state by replaying all events.
00:05:51.510 Tomorrow, we will have a deeper dive into event sourcing with Anton. Another key concept is to regard modern web applications as distributed systems, where servers and clients interact by synchronizing messages. Components of this distributed system must have characteristics such as the absence of a global clock and support for concurrency.
00:06:22.350 Based on these ideas, the Logux framework was created, invented by Andrey Staltsev. Its goal is to introduce a new method of communication between server and client.
00:06:47.040 When we refer to frameworks, we typically imply a set of ideas and concepts that provide instrumentation for building software in a specific way. Frameworks are also meant to reduce complexity, similar to how Ruby on Rails simplifies web application development, React targets efficient component rendering, and Redux assists with state management.
00:07:06.300 The Logux framework's aim is more than just code. First, it proposes a complex solution to utilize WebSockets, which facilitate real-time data transfer, enabling servers and clients to exchange messages as long as the connection remains open. Additionally, Logux supports live updates intrinsically for building collaborative tools.
00:07:40.200 Another idea behind Logux is to specify an open protocol for server-client communication. This flexibility allows Logux components to be implemented using various technologies, not limited to Node.js. Logux provides a standalone server and a client-side JavaScript package. The server handles WebSocket connections, internal store events, and coordinates synchronization and broadcasting among clients.
00:08:01.560 On the other hand, the Logux client wraps Redux with a compatible API to manage data synchronization in the background. Therefore, there is no longer a need to manually request and handle responses, as Logux clients manage transport transparently.
00:08:30.750 Logux is designed to integrate seamlessly with existing front-end infrastructures, relying on the familiarity of the Redux API. It also includes a set of standard UI widgets to visually represent network state in a user-friendly manner.
00:08:42.360 The client tracks the WebSocket connection through periodic pings and accumulates events during network downtime, making attempts to resend them when the connection is restored. This feature means that Logux supports offline modes out of the box.
00:09:12.720 The challenge of maintaining order in a distributed system—where a global universal clock is not feasible—is addressed historically by some solutions that calculate the time shift based on the difference between server and client time, adjusted for packet round-trip time.
00:09:38.850 This approach allows clients to convert their local time to server time before synchronization. Although the solution may not be universally applicable and might produce transport errors, in practical terms related to user interfaces, the errors are not critical.
00:10:04.190 For example, time discrepancies can occur where the same machine shows different local times at various moments. Therefore, simply timestamping events does not guarantee accuracy. Instead, Logux utilizes timestamps combined with incrementing counters for event ordering.
00:10:26.066 Logux can implement automatic conflict merging. The simplest strategy utilized is last-write-wins, where previous changes may not apply immediately but will synchronize later without disrupting overall performance.
00:10:47.856 Handling server errors is critical for maintaining an optimistic UI. For these purposes, Logux uses a special 'undo' event. If an error occurs, it sends an undo event back to the client, which then removes the original event from the Redux store and recalculates the frontend state.
00:11:12.720 Now, I’ve discussed these concepts primarily in the context of Node.js. But what about integration with Ruby, particularly existing Ruby backends? Logux includes a special component called 'proxy' that forwards incoming events over HTTP, allowing any backend to process events and implement business logic.
00:11:51.540 This proxy also exposes an endpoint for backends to send synchronized events back to the client, essentially functioning as a two-way HTTP gateway. Logux treats the backend as a conventional client communicating over HTTP while receiving events without filtering.
00:12:15.220 We initiated the integration of Logux with an existing Rails application, resulting in the development of the 'logux-rails' Ruby gem. This gem is easily configurable, requiring only the Logux proxy backend URL to be set and then mounted on the route.
00:12:54.630 This gem provides abstractions around the foundational Logux protocol. Items and actions encapsulate Logux messages with types and additional parameters similar to Rails action params. The meta field contains technical metadata describing the action, including time identification and scope.
00:13:38.610 The Ruby gem also supports syndication using credentials that facilitate round-trip responses from the backend, ensuring smooth interactions. Following the convention over configuration principle, we match event types to corresponding action types and classes.
00:14:10.020 Logux retains channel support, enabling clients to receive notifications regarding specific events. When a client subscribes to a channel, we initialize it with the actual state. This can easily be accomplished by overriding the initial data method within the Logux channel class.
00:14:58.800 The initial state may vary; for instance, if we were implementing a chart with live updates, the initial state for any thread could reflect the most recent messages for that thread. Additionally, the Ruby gem supports sending updates back to clients easily using the 'add' method within the Logux singleton class.
00:15:32.940 When an event is triggered, an HTTP request is sent to the Logux proxy, which then broadcasts the event to the relevant client scopes. With a Rails dependency, we provide helper methods to implement last-write-wins strategies for model updates, tracking the last update timestamps effectively.
00:16:20.130 Furthermore, we are ready to send undo events in instances of server validation errors, allowing the Logux server to identify which client to synchronize events based on the provided metadata.”},{