This message was imported from the Ruby/Rails Modularity Slack server. Find more info in the import thread.
Message originally sent by slack user U7213XMGS3H
Hello! I have a question about organizing GraphQL code into packs. In most graphql-ruby implementations I’ve seen there’s a coupling of GraphQL types and ActiveRecord models. This poses a problem because ideally while modularizing our code into packs, we would make model classes private. GraphQL by it’s nature needs to describe relationships between models that are private in various packs.
Let’s imagine we had 2 packs, customers
and orders
.
We might imagine a GraphQL type in the orders pack
# packs/orders/app/public/graphql/types/order_type.rb
class Types::OrderType < GraphQL::Schema::Object
field :some_field, String
field :customer, Types::Customer
end
In a unmodularized monolith, I would assume there would be a belongs_to
relationship between the Order
and Customer
models. This would mean that when graphql-ruby
called order.customer
it would run a database query. (We’d probably want some batching in place, but perhaps that’s out of scope for this conversation.)
If my models are private in two separate packs, how can I achieve this? Do I:
• Make graphql-ruby
represent plain ruby objects and not models? Have public APIs from the packs return these objects? I like this route, but it seems extremely hard to achieve incrementally because I already have hundreds of types defined that relate to models
• Make models public? This feels quite wrong, and one of the reasons I’m so interested in Packwerk is that I want ActiveRecord to be used only in private
• Put each model (or sets of models) in their own packs that are public, but is only imported into certain places. For example, a GraphQL batch loader pack could import all models. Feels kind of harebrained, but it could work.
Hey <@U7213XMGS3H>! I’m definitely no expert in graphql
, so I’m hoping some folks with more expertise than me can chime in.
At Gusto, we’ve definitely found something similar to you – there is a tradeoff between what GraphQL wants and what we want for our modularized app.
• graphql-ruby
wants ActiveRecord, our other ideals push us to not expose AR as a public interface
• graphql
has cycles as a feature – packwerk rejects cycles as a feature
• graphql
exposes its own “interface” to systems that may or may not use the existing (or not) interface of a system
The most important bit I want to mention is that this tradeoff is definitely present still as we’re figuring out the best way to reconcile these technologies, and I’d encourage any team to set up their graphql/packwerk stack in a way that creates the minimum friction and maximum value for engineers.
That being said, at Gusto, here are some things we do to help mitigate this friction:
• Although we originally put each domain’s graphql stuff in the pack for that domain, we reversed this decision and decided to merge graphql concerns in their own pack. This has a couple of nice advantages:
◦ The issue of cycles is all but eliminated, since the graphql pack can have cycles within itself, but should still not have cycles with other packs.
◦ Generally speaking, it encourages the graphql pack to use the public interface of other packs. There are ways for graphql to call plain old Ruby APIs instead of AR APIs, which we try to encourage. This also pushes us to take GraphQL concerns (e.g. pagination) and find a way to bake them into our plain ruby APIs, which increases reusability of those ruby APIs. That being said – folks skip this and use private implementation anyways, which is fine too! This can be done incrementally too (you don’t need to migrate everything at once).
• Another option is to add graphql paths to the exclude
key of packwerk.yml
. That way you allow packwerk to add value to non-graphql code and you let graphql do its thing unencumbered.
Over time I Hope graphql-ruby
can find ways to be as convenient for POROs as ARs, e.g. by expecting an interface to data rather than a specific implementation, but that’ll be a community effort where we feel the most pain.
Message originally sent by slack user U7213XMGS3H
Thanks @AlexEvanczuk, this is a great answer. It’s good to hear I’m not missing some obvious pattern that would perfectly reconcile these issues.
Our problem is that we have hundreds of current GraphQL types that are mostly tied 1:1 to AR models. We want to move these AR models into packs privately. Many GraphQL fields map to model methods or relations. I’m struggling to imagine how we can do this incrementally.
Another solution I started working up (but ultimately abandoned) was to use an in process graphql gateway. I got this idea by asking myself “how would we handle graphql if we were splitting up the code into different services.” The issue with this strategy is that there’s only one gem I can find for schema stitching, and it’s pretty new and doesn’t have a lot of adoption. I tried to get it to work with our large schema and it failed in inscrutable ways. I thought about spinning up an out-of-process gateway and hitting multiple GraphQL controllers (1 for each pack) but that seemed kind of crazy.
Given that Shopify has a large GraphQL API, I’m curious if they’ve developed any patterns… any Shopify devs reading this?
Message originally sent by slack user U7213XMGS3H
Another thought I had was to make a public API exclusively for graphql that returned AR objects, but then enforce that with a rubocop rule.