9

Take advantage of Turbo Streams in event handlers

 2 years ago
source link: https://blog.arkency.com/take-advantage-of-turbo-streams-in-event-handlers/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Take advantage of Turbo Streams in event handlersHi, weʼre arkency 👋

It’s been a time since Rails 7 came with Turbo and its Turbo Streams.

At first, I was a bit skeptical because of the idea of broadcasting view updates as a sort of Active Record callbacks. Sorry, I’m simply not buying the idea of mixing WebSockets calls into a data model. However, rejecting the concept of Turbo::Broadcastable concern, I see Turbo Stream as a great tool and I’m sure there is a proper place for it in the Rails app architecture.

This is more or less what our typical architecture looks like.

sync.png

Read models are loaded and presented on the UI. A user issues a command which is passed to the domain layer. This usually culminates in one or more domain events being published. These events are persisted and then handled synchronously or asynchronously by event handlers which update the read models. With the next page load, the user sees the updated read models. The circle is closed.

With Turbo Streams and just one more event handler, we can invoke asynchronous direct updates from the backend to the UI and significantly improve user experience.

async.png

Let’s see how we do it based on the ecommerce, our demo application.

<table>
  <thead>
    <tr>
      <td>Number</td>
      <td>Customer</td>
      <td>State</td>
    </tr>
  </thead>

  <tbody>
  <% @orders.each do |order| %>
    <%= turbo_stream_from "orders_order_#{order.uid}" %>
    <tr>
      <td><%= order.number %></td>
      <td><%= order.customer %></td>
      <td id="<%= "orders_order_#{order.uid}_state" %>"><%= order.state %></td>
    </tr>
  <% end %>
  </tbody>
</table>

class Configuration
  def call(event_store)
    @event_store = event_store

    # ... handlers building read models omitted

    subscribe(
      ->(event) { broadcast_order_state_change(event.data.fetch(:order_id), 'Submitted') },
      [Ordering::OrderSubmitted]
    )
    subscribe(
      ->(event) { broadcast_order_state_change(event.data.fetch(:order_id), "Paid") },
      [Ordering::OrderConfirmed]
    )
    subscribe(
      ->(event) { broadcast_order_state_change(event.data.fetch(:order_id), "Cancelled") },
      [Ordering::OrderCancelled]
    )
  end

  private

  def subscribe(handler, events)
    @event_store.subscribe(handler, to: events)
  end

  def broadcast_order_state_change(order_id, new_state)
    Turbo::StreamsChannel.broadcast_update_later_to(
      "orders_order_#{order_id}",
      target: "orders_order_#{order_id}_state",
      html: new_state)
  end
end

Boom! Any time we catch an OrderSubmitted, OrderConfirmed, or OrderCancelled event, we invoke broadcasting an update. Every subscribed client receives a Turbo Streams message and updates the specific order state. Page reload is not required.


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK