-
Notifications
You must be signed in to change notification settings - Fork 98
Description
Rails 6.0 has a new autoloading mode which delegates to the Zeitwerk gem:
The classic autoloader has been extremely useful, but had a number of issues that made autoloading a bit tricky and confusing at times. Zeitwerk was developed to address this, among other motivations.
When upgrading to Rails 6.x, it is highly encouraged to switch to zeitwerk mode because it is a better autoloader, classic mode is deprecated.
Rails 7 ends the transition period and does not include classic mode.
Our RPC service class implementation depends on a stub being generated from the protobuf definition, then being reopened in your application (i.e., monkey patching) to implement the service endpoints:
# app/services/foo/user_service.rb
require 'lib/foo/user.pb'
# Reopen the service class and provide the implementation for each defined RPC method.
module Foo
class UserService
# request -> Foo::UserRequest
# response -> Foo::UserResponse
def find
# request.email will be the unpacked string that was sent by the client request
users = users_by_email.map(&:to_proto)
respond_with(:users => users)
end
private
def users_by_email
User.find_by_email(request.email)
end
end
endThis pattern is incompatible with the Zeitwerk autoloader because files are loaded as classes/namespaces are defined by the generated Ruby protobuf definition. When Zeitwerk encounters the namespace/class while loading app/services (a sensible place to put service classes in a Rails app), the files are skipped:
Zeitwerk@rails.main: the namespace Foo already exists, descending into .../app/services/foo
Zeitwerk@rails.main: file .../app/services/foo/user_service.rb is ignored because Foo::UserService is already definedTo fix this, we need to reconsider how services are expected to be implemented.
One option would be to treat the generated service as a router that dispatches RPC requests to a separate application service class similar to the Rails router. The first cut would simply create the glue that allows this routing to take place based on the generated services (i.e., endpoint names and class methods would still need to be the same; no custom routing would be allowed). A future cut could make this more flexible, allowing requests to be routed as needed (i.e., conditionally based on a version scheme, etc.), but that may not be needed.
I would also anticipate this change being part of a major release in the event that backwards incompatible changes are required.