for our API - We need to write a type definition manually - [Backend] Similar classes to represent the same resource - UserDetail, UserLite, ... - [Backend] Messy logics to solve N+1 problem
UserDetailEntity < UserLiteEntity def as_json super.merge( email: @user.email ) end end class ActivityEntity class User < UserLite def as_json super.merge( avatar_url: @user.avatar_url ) end end def as_json { id: @activity.id, user: User.new(@activity.user).as_json } end end class UserLiteEntity def as_json { id: @user.id, first_name: @user.profile.first_name, last_name: @user.profile.last_name, } end end
UserLiteEntity def as_json { id: @user.id, first_name: @user.profile.first_name, last_name: @user.profile.last_name, } end end class UserDetailEntity < UserLiteEntity def as_json super.merge( email: @user.email ) end end class ActivityEntity class User < UserLite def as_json super.merge( avatar_url: @user.avatar_url ) end end def as_json { id: @activity.id, user: User.new(@activity.user).as_json } end end N + 1 N + 1
gem though, we can’t because of Mongo 18 class UserLiteEntity def as_json { id: @user.id, first_name: @user.profile.first_name, last_name: @user.profile.last_name, } end end users = User.all.includes(:profile) render json: users.map {|u| UserDetailEntity.new(u).as_json }.as_json Microservices also face this problem `includes(:profile)` executes SELECT * FROM profiles and sets to user.profile
provides typing out of the box - [Backend] Similar classes to represent the same resource - Declarative entity classes and lazy loading - [Backend] Messy logics to solve N+1 problem - Loader
class UserType < Types::BaseObject field :id, ID, null: false field :first_name, String, null: false field :last_name, String, null: false field :email, String, null: false field :avatar_url, String, null: false end end query Users { users { id, email } } class UserLiteEntity def as_json { id: @user.id, first_name: @user.profile.first_name, last_name: @user.profile.last_name, } end end class UserDetailEntity < UserLiteEntity def as_json super.merge( email: @user.email ) end end
field :first_name, String, null: false def first_name RecordLoader.for(Profile, 'user_id').load(object.id) end end end module Types class ActivityType < Types::BaseObject ... field :user, Types::User, null: false def user RecordLoader.for(User, 'id').load(object.user_id) end end end Parent knows how to load children
page for now - We have only one variation of query - Avoid using client library - We already have client data store, Redux - Use simple tool to generate TypeScript type definition - https://graphql-code-generator.com
CLI is very useful - API schema makes easier to work team members in parallel - Backend - Much structured than before - There’s some confusions about new things especially “Loader” - We need more types/queries to get more merits
partially - You may not need GraphQL client library - You don’t have to replace your existing client store - Have a discussion with your team to introduce new stuff