{"action":"created","issue":{"url":"https://api.github.com/repos/gocardless/coach/issues/61","repository_url":"https://api.github.com/repos/gocardless/coach","labels_url":"https://api.github.com/repos/gocardless/coach/issues/61/labels{/name}","comments_url":"https://api.github.com/repos/gocardless/coach/issues/61/comments","events_url":"https://api.github.com/repos/gocardless/coach/issues/61/events","html_url":"https://github.com/gocardless/coach/issues/61","id":380814458,"node_id":"MDU6SXNzdWUzODA4MTQ0NTg=","number":61,"title":"Parallel middleware","user":{"login":"jgwmaxwell","id":385833,"node_id":"MDQ6VXNlcjM4NTgzMw==","avatar_url":"https://avatars1.githubusercontent.com/u/385833?v=4","gravatar_id":"","url":"https://api.github.com/users/jgwmaxwell","html_url":"https://github.com/jgwmaxwell","followers_url":"https://api.github.com/users/jgwmaxwell/followers","following_url":"https://api.github.com/users/jgwmaxwell/following{/other_user}","gists_url":"https://api.github.com/users/jgwmaxwell/gists{/gist_id}","starred_url":"https://api.github.com/users/jgwmaxwell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgwmaxwell/subscriptions","organizations_url":"https://api.github.com/users/jgwmaxwell/orgs","repos_url":"https://api.github.com/users/jgwmaxwell/repos","events_url":"https://api.github.com/users/jgwmaxwell/events{/privacy}","received_events_url":"https://api.github.com/users/jgwmaxwell/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-11-14T17:47:09Z","updated_at":"2018-11-14T23:05:12Z","closed_at":"2018-11-14T23:05:12Z","author_association":"NONE","body":"So, this might be a slightly unusual use case here...\r\n\r\nOne flow that I'm investigating using this for fetches data from multiple backends before acting on some combination of that information. We currently do this asynchronously using concurrent ruby, which works nicely to avoid the latency of serial network requests.\r\n\r\nRather than writing a know-it-all middleware, I'd far rather have a composable flow, which would allow single-purpose middlewares to be called in parallel, each providing their own data to the downstream handler. Would you consider a PR that introduced some kind of `uses_parallel` or less monstrously named concept?\r\n\r\nThere would obviously be some issues around the concept of `next_middleware` in this case, and I could solve this without the extra class method by using a configurable `AsyncInjectionMiddleware` or the like, but thought I'd sound you out."},"comment":{"url":"https://api.github.com/repos/gocardless/coach/issues/comments/438852546","html_url":"https://github.com/gocardless/coach/issues/61#issuecomment-438852546","issue_url":"https://api.github.com/repos/gocardless/coach/issues/61","id":438852546,"node_id":"MDEyOklzc3VlQ29tbWVudDQzODg1MjU0Ng==","user":{"login":"jgwmaxwell","id":385833,"node_id":"MDQ6VXNlcjM4NTgzMw==","avatar_url":"https://avatars1.githubusercontent.com/u/385833?v=4","gravatar_id":"","url":"https://api.github.com/users/jgwmaxwell","html_url":"https://github.com/jgwmaxwell","followers_url":"https://api.github.com/users/jgwmaxwell/followers","following_url":"https://api.github.com/users/jgwmaxwell/following{/other_user}","gists_url":"https://api.github.com/users/jgwmaxwell/gists{/gist_id}","starred_url":"https://api.github.com/users/jgwmaxwell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgwmaxwell/subscriptions","organizations_url":"https://api.github.com/users/jgwmaxwell/orgs","repos_url":"https://api.github.com/users/jgwmaxwell/repos","events_url":"https://api.github.com/users/jgwmaxwell/events{/privacy}","received_events_url":"https://api.github.com/users/jgwmaxwell/received_events","type":"User","site_admin":false},"created_at":"2018-11-14T23:05:12Z","updated_at":"2018-11-14T23:05:12Z","author_association":"NONE","body":"I'm going to close this as I can't quite work out how the semantics actually would work, but the below is basically the solution I was thinking of for anyone who might read this and be interested:\r\n\r\n```ruby\r\nmodule BatchMiddlewareFactory\r\n  module_function\r\n\r\n  def build(props)\r\n    middleware = Class.new(Coach::Middleware)\r\n    middleware.provides *props.keys\r\n\r\n    middleware.send :define_method, :call do\r\n      promises = props.values.map { |callable| Concurrent::Promise.execute { callable.call(self) } }\r\n      results = Concurrent::Promise.zip(*promises).value\r\n\r\n      out = props.keys.each_with_index.each_with_object({}) do |(key, idx), hsh|\r\n        hsh[key] = results[idx]\r\n      end\r\n\r\n      provide(out)\r\n\r\n      next_middleware.call\r\n    end\r\n\r\n    middleware\r\n  end\r\nend\r\n```\r\n\r\nThis could then be used in the following way whilst maintaining the guarantees of the system\r\n\r\n```ruby\r\nclass SomeRoute < Coach::Middleware\r\n  uses BatchMiddlewareFactory.build({ \r\n    page: page_loader_callable,\r\n    user: user_loader_callable\r\n  })\r\n  requires :user\r\n\r\n  def call\r\n    [200, {'Content-Type' => 'application/json'}, [{ page: page, user: user }.to_json]]\r\n  end\r\nend\r\n```\r\n\r\nI don't think you can do this without removing the `next_middleware` concept, and the chance of something being reusable as a middleware and a loader-provider-thing is basically zero.\r\n\r\nAnyway, sorry for the noise!"}}
{"actor":{"display_login":"jgwmaxwell"}}