Async web responses in Rails - discussion - inspired by Rails Enqueue API

The Problem

I wonder how many people (I’m one of them) started with basic Rails
applications serving HTML, JSON, or both, and eventually ran into a
point
where certain parts of the application became too slow, or were
re-factored
to consume some 3rd party services, and ended up not being able to
synchronously serve the response in a timely manner, requiring switching
to
asynchronous responses.

The Pattern

At an abstract level, the behaviour is as follows:

  1. Get request (could be either HTML or JSON)
  2. Initiate some kind of async job (“job” here is interpreted widely,
    could be delayed/enqueue job or some other paradigm, point is, it’s
    asynchronous to the request and has no guaranteed completion time)
  3. Respond to requestor with 202 Accepted status, or some other
    status
    signifying "we accepted your request but do not have a response yet)
  4. Include in response the URL client should check to visit response.
    Note that this isn’t necessarily always mapping nicely to CRUD in
    sense of
    returning standard RESTful id. For example, nature of the job could
    be
    something like a very complex search/report query, of the form
    /items/123?conditions=…, but we can’t just tell client to visit
    /items/123 for their result, because different clients doing this
    search
    may request same resource but with different filter conditions.
  5. Client will poll the URL returned at last step, which will either
    return “check back later” status if response is not done, or the
    actual
    response if it’s finished (or, alternatively, 3rd URL to visit the
    finished
    response once it’s complete, which client will then visit to get
    their
    actual data).
  6. If response jobs need to be stored on server side, need some
    mechanism to eventually clear them out.

The Rails Way

You may look at above and say “well, you have a custom requirement, so
write yourself a custom solution, Rails can’t read your mind”. And you
might be right. But, on the other hand:

  1. Over many projects I’ve been on, this has been a very common
    requirement. For many applications which scale beyond a certain point
    both
    load-wise and 3rd-party-integration-wise, response times are often
    not
    guaranteed because of dependencies you have no direct control over,
    and we
    can’t just hang the request until the job is done.
  2. I don’t know from the beginning when, and for which resources,
    I’ll
    need async request/response handling. I want to be able to Just Code
    stuff
    using the basic simple Rails as I need it, and switch to async
    processing
    later for needed endpoints only, as my application evolves. I want to
    be
    able to do this with minimal changes on both API and internal
    implementation. For example, if my regular controller uses
    current_user
    (from session), current_account (from request), and other such
    variables, I
    want to be able to continue using them in async controllers and not
    have to
    re-write the whole controller/view after switching to async.

I was inspired to start this discussion by the latest Enqueue work added
in
Rails 4.2. After many years and many competing async job processors
(delayed_job, sidekiq, resque, etc.) Rails decided it made sense for
them
to provide a wrapper API so that code can be written in a consistent way
and the implementation be relatively easy to change with no external
impact. Just as importantly, it now becomes possible to write code that
is
synchronous yet uses the Enqueue API (using the “inline” adapter), and
later pick and choose which parts should become async based on the
application evolution.

The Enqueue API makes the backend job processing easier to make async,
but
the controller-level request-response handling is still a sore point:

  1. The URL pattern for async responses is different from standard
    REST,
    making migrations from sync to async requests painful and existing
    APIs
    changed.
  2. Rendering a RESTful response (either HTML or JSON) synchronously
    is
    trivial in normal Rails controllers; rendering it async is not. Even
    seemingly simple things like rendering an existing model/view is not
    easy
    without the familiar controller context. There are some gems that try
    to
    encapsulate it by constructing a custom controller and stubbing or
    caching,
    some examples Google found. Unfortunately, doing this is tedious work
    and
    makes it difficult to use existing session or request-based helpers
    like
    current_user or other methods from controller or application helpers.
    A lot
    of session/request caching and method re-definitions are needed.

    Rails 3: rendering views outside a controller | AlphaHydrae

    ruby - Rails, How to render a view/partial in a model - Stack Overflow
  3. This just doesn’t seem a “Rails Way” to solve my problem, it makes
    me
    feel like I’m fighting against the MVC/REST instead of leveraging it.
    :frowning:

The Vision

What do you think of being able to do something like:

class ItemsController < ApplicationController::Base
respond_to_async :show

def show

end
end

Or

class ItemsController < ApplicationController::Base

def show
render ‘show’, async: true
end
end

This would provide the ground work (e.g. REST/URL structure) to handle
requests in a way which would be possible to make asynchronous if and
when
needed. Similar to Enqueue, there could be an “inline” pattern that
behaves
synchronously, but allows smooth transition to true async later, e.g.
“redirect” would provide a response URL for client to visit.

Similar to the “enqueue” philosophy, the main purpose of the async
request/response API would not to actually force a specific
implementation,
but to provide a wrapper API that the actual implementation can fit in.
Users can either write their custom async implementation or use a 3rd
party
gem, but any such implementation should conform to the expectations set
by
the API.

The above snippets are just hypothetical examples of what such an API
might be like, I’m open to totally different ideas to solve this
problem
too.

The Discussion

Have you previously worked with implementing SOA or other requests which
cannot be responded to immediately with final result because the job is
too
slow or distributed? I’d love to hear your opinion about this! Some
factors
to consider would be:

  1. What was the response type of your application? HTML or JSON? Did
    it
    support normal forms, front-end JS frameworks, mobile APIs, etc?
  2. How did you handle such a problem? Was it similar to above or did
    you
    have a drastically different way?
  3. Did you design your application to have asynchronous responses
    from
    day 1, or did you start with a basic Rails application and had to
    make all
    or parts of it respond asynchronously later? What was the migration
    like?
  4. Did you ever think that Rails could provide a more consistent
    standard and easier migration path from sync to async responses?

Or perhaps you didn’t have to build such systems? Perhaps you think they
don’t even make sense and are not The Rails Way and don’t belong in
Rails?
I’d love to hear from you too - if you think Rails helping to solve this
isn’t the right approach, then what might the right solution be/look
like?

All feedback welcome!

Thanks for your reply, Jason! It’ll take me some time to digest and
review
your whole post.

One thing to note, however, is that I was thinking of a solution
agnostic
to client side. In our Rails environment, we have many types of clients,
following very different paradigms:

  • Regular browser clients consuming Rails-rendered views
  • Client-side framework (we use Backbone.js)
  • Versioned APIs used by iPhone and Android mobile devices
  • Versioned APIs used by desktop clients, which are offline for
    majority
    of time
  • Intra-app service APIs (data from one application consumed by
    another
    Rails application via private API but still using Rails controllers).
    In
    fact we have several types of those as well, some request/response
    based
    using Rails Controllers, others event-based with subscriber-consumer
    pattern using RabbitMQ. But while the push-based, consumer/subscriber
    pattern is great for certain types of events (e.g. propagate an
    updated
    record across distributed systems), I definitely don’t think its
    suitable
    for all types of events/APIs in our system.

Perhaps a long-term solution for the web is indeed to move to an async
streaming model, but for many clients which are not always online (e.g.
mobile, desktop) this doesn’t seem like a viable option. And in sense of
intra-app services, it would still be a very big leap requiring major
re-architecting of the whole communication protocol.

Another important point for me is that, given the size of existing Rails
deployments and their consumers, there is a demand for evolutionary
rather
than revolutionary design, something one can “grow into” from a
successful
regular web app that started small but later outgrew itself. In the case
of
Rails, this would be the next evolutionary step from a basic MVC
(whether
HTML or JSON).

Eugene

Eugene,

I think your raise some very interesting points (and a bit of a can of
worms). I cannot speak for anyone else, but I can say I think about the
heart of the questions you ask. I do think there is some perspective
missing here, and that is the perspective of the front end application
that is at play. Specifically, I don’t think Rails can really answer
your question well without thinking about what Javascript apps will look
like in 2, 5, and 10 years from now.

In no particular order, here are some responses:

  • The Async response pattern is absolutely a thing and I’ve used it a
    lot. IN particular, I use it anytime I know the response can’t be
    guaranteed to be delivered in under 250 ms. Connecting to a 3rd party
    API always meets this requirement-- since I have no control over the
    third party API. Every try to write a “Place Order” page? You’re gonna
    want to enqueue a job and process that in the background and update the
    UI independently (just as you explained).

  • In the future, more apps will be built using WebSockets technology-
    something you didn’t mention. You can write poller-- as Eugene
    described – to support legacy browsers, all new browsers should support
    WebSockets and we should be taking advantage of this great technology. I
    think WebSockets will basically eliminate the need for the Poller and
    you’ll have an asynchronous app where the server can push down changes
    to the client when the long-running jobs finish. This kind of
    architecture work superfast and can achieve massive scale. So yes, it’s
    a real problem and something I too think about a lot.

  • I think the need for fast endpoint response times comes from the way
    Heroku is architected. In a cluster environment (Rackspace, EngineYard),
    people do things like configure load balancers and they tolerate their
    3, 5, or 7 second response times. In Heroku, this basically creates a
    bottlenecked app for everyone. So I think the subtle part of what you’re
    saying should be repeated and highlighted:

    • The new distributed architecture of Heroku pushed for consistently
      high-performance (under 500ms) endpoints.
    • The Rails Way does not encourage performance out of the box
      (although it is achievable),
    • The reliance on the request-operation-response pattern does no
      support itself to a truly Async (and dyno- distributed) scalability
      model
    • Rails doesn’t offer much in the way of dealing with or supporting
      Async architecture, specifically in the Client-Server interaction side
      of things. (Although there are many emerging technologies to do this)

So to me this seems to be the heart of the conflict.

  • Might I be a little radical and propose that in the future Rails will
    be considered “customer” (I said customer, not consumer) of Backbone,
    Angular, and/or Ember. For example, when I worked (for the first time)
    with an Ember.JS app (specifically, ember-data) using a Rails JSON
    back-end that used ActiveModel Serializers earlier this year, I was
    simply blown away. I had an A-ha moment that I haven’t had in Rails in
    many, many years. I think a lot of your Async questions can take on new
    dimensions when thought of from that perspective.

Not only is this kind of architecture a great idea, but it is where the
rest of the non-Rails world is moving to as well and where the Rails Old
guard is stubbornly entranced in their ways. (We’re talking about a
framework that still officially endorses unobtrusive declarative-based
javascript, RJS and Turbolink as the way to make Ajax-based websites.)

That last point is really significant for me. I think Rails is a great
back-end technology, and I think were a lot of newcomers the platform
mess up by trying to think of it too much as a front-end technology.
Compared to what the kinds are doing over in Node, Ember, Angular, we’re
the old guard now.

Newcomers come into Rails and expect Rails to have the answer to address
today’s modern UX needs. While Rails exceeds at being a back-end
technology and an amazing tool for domain modeling, it just doesn’t meet
today’s UX needs. That’s why a lot in web community is moving away from
Rails and embracing newer Javascript frameworks. Although Rails is still
HUGE (by the numbers), I’ve spent all year talking to people in the New
York City Startup scene about technology and Rails is not what they are
talking about.

I definitely some of the ideas you have, although I disagree with
others.

You can reach me here or at [email protected] if you want to discuss
offline.

-Jason

On Oct 26, 2014, at 10:22 PM, Eugene G. [email protected]
wrote:

Include in response the URL client should check to visit response. Note that
this isn’t necessarily always mapping nicely to CRUD in sense of returning
standard RESTful id. For example, nature of the job could be something like a very
complex search/report query, of the form /items/123?conditions=…, but we can’t
just tell client to visit /items/123 for their result, because different clients
doing this search may request same resource but with different filter conditions.
The URL pattern for async responses is different from standard REST, making
migrations from sync to async requests painful and existing APIs changed.
respond_to_async :show

The above snippets are just hypothetical examples of what such an API might be
like, I’m open to totally different ideas to solve this problem too.
All feedback welcome!


You received this message because you are subscribed to the Google G. “Ruby
on Rails: Talk” group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/rubyonrails-talk/4c0f375b-5e8e-4e60-a51e-7978dee9d69d%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Jason Fleetwood-Boldt
[email protected]

All material (c) Jason Fleetwood-Boldt 2014. Public conversations may be
turned into blog posts (original poster information will be made
anonymous). Email [email protected] with questions/concerns about
this.