Open MichaelrMentele opened 1 year ago
This is an unedited rough draft to start discussion. Detailed plan needs development.
We seriously need to change this for numerous reasons. This advice is at odds with guidance from the creators of GQL, Apollo, etc. Fair warning I will pick on this at the builder's conf.
This reddit post is good https://www.reddit.com/r/graphql/comments/eovr0h/business_logic_layer/
Checkout graphql.org https://graphql.org/learn/thinking-in-graphs/#business-logic-layer
There is comments on the Apollo blog as well.
Summary
Services are misnamed and overloaded
Let's call them resolvers and stop recommended people put biz logic inside them. It's an anti-pattern for multiple reasons happy to discuss at length.
Motivation
Code smell: information leakage
Calling resolvers services is confusing. At the end of the day, by default every export (by default) gets exported out of the services directory into the services field on
createGQLHandler
. GQL doesn't know about "services" it only knows how to resolve fields. Why are we leaking this concept?There a basic bugs like the fact that every export is sent into services
createGraphQLHandler
which can result in nasty name clash bugs :(Unnecessary Conceptual Coupling
TL;DR creates tech debt, doesn't scale
Services are a broad term that can refer to: (1) separately hosted "services", (2) packages that fulfill a service or handle a concern. Classically services are organized around domains or business logic. However, naming our resolvers services and then encouraging reusing them as a fundamental building block does not work at scale and leads to a host of issues.
Resolvers aka services map to your external API which should be as simple as possible. Your external API is not your internal API for a variety of reasons I hope are self-evident.
The apollo docs themselves recommend having a model and biz logic layer but in RW we mush that all together in our API or lead folks down that path by encouraging reusing services in other services and building packages around our services and calling them services.
This works fine when early on you have a stupidly simple API that mostly does CRUD which is the genesis of most apps. But there are good reasons there is distinct layers for models, domain logic, and api fulfillment in every major web framework including Rails, Django, etc.
Classic example is seeding data, you might have validation or side-effects like sending emails that should happen during a mutation that you do NOT want to happen AND you may have side-effects like creating a child relation that you DO want to happen. A service is clearly overloaded. What do I do when seeding data then? Copy what I need and not what I don't? Oh but wait, now I have an obscure dependency and we redesign the schema or move that side-effect from the API -- now it's no longer coupled for whatever reason but I've forgotten to update my seed script. Clearly, there is some effects that are tightly coupled to the model layer and other that are coupled to the business layer and other that are coupled to the API layer. The classic solution is -- have a model layer you can extend.
If you squint at GQL it really is an interface replacement and resolver isn't so different from a controller in classic MVC. Anyway, I've gone off the rails now. My point is if we are going to prescribe how biz logic is organized we should be more thoughtful about our layers.
Django has apps which vertically slice domain logic and bundle it with routing. Rails has classic MVC with mixins and "concerns". What does RW have?
Detailed proposal
Clarify Single Responsibilities
createGQLHandler
-- GQL should have no idea about how biz logic is organized, it should know how to resolve types and that is allWrite a Scalable Architecture Doc for Code Organization
Are you interested in working on this?