Originally posted in Rails Talk
(https://groups.google.com/forum/#!topic/rubyonrails-talk/DHqc9sQu0h8) but it
was suggested to me to post in Rails-Core, so apologies for the double post!
*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.
- http://www.jonb.org/2013/01/25/async-rails.html
-
http://alphahydrae.com/2013/06/rails-3-rendering-views-outside-a-controller/
-
http://stackoverflow.com/questions/6318959/rails-how-to-render-a-view-partial-in-a-model
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. :-(
*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!
--
You received this message because you are subscribed to the Google Groups "Ruby
on Rails: Core" 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].
Visit this group at http://groups.google.com/group/rubyonrails-core.
For more options, visit https://groups.google.com/d/optout.