Upgrade to Pro — share decks privately, control downloads, hide ads and more …

實戰GraphQL.pdf

motephyr
August 18, 2019
280

 實戰GraphQL.pdf

motephyr

August 18, 2019
Tweet

Transcript

  1. INDEX • 1. Introduction • 2. Single page include more

    and more components:
 Restful API to Component based API • 3. UI Driven Development • —————————————————dividing line——————————————— • 4. Query Arguments • 5. Handle your N+1 query: Dataloader helper. • 6. Apollo&Subscription • 7. About Modal and Route Change • 8. Authorization: router, resolver, directive
  2. 如果你是BACKEND
 你說要導入GRAPHQL • You are doing your own. • 你正在搞你⾃自已。

    • 不過只有剛開始不舒服,之後就會覺得... • API不⽤用⼀一直改來來改去 很爽的啦
  3. UI DRIVEN DEVELOPMENT • 確定了了Wireframe
 —>定義DB schema
 —>處理理ORM
 —>考慮Data Source

    • 決定如何query的語法 • 剩下的就是如何做資料的
 CREATE
 UPDATE
 DELETE
  4. # Frontend: query query Users{ users(q: JSON){ id username user_data{

    # one to one id address } posts { # one to many id title description } groups { # many to many group_name } } } query Posts{ posts(q: JSON){ id title description user { # belongs to id username } } }
  5. # Backend: type type Query { users(q: JSON): [User] posts(q:

    JSON): [Post] } type User { id: Int username: String user_data: UserData posts: [Post] groups: [Group] } type UserData { id: Int address: String } type Post { id: Int title: String description: String user: User } type Group { id: Int group_name: String }
  6. # Backend: resolver const resolvers = { Query: { users:

    async (_, { q }) => { const result = await BasicService.query(User, q) return result }, posts: async (_, { q }) => { const result = await BasicService.query(Post, q) return result }, }, User: { user_data(user, _, { loaders: { user_datas } }) { return user_datas.load({ key: 'user_id', value: user.id }) }, posts(user, _, { loaders: { posts } }) { return posts.load({ key: 'user_id', value: user.id, many: true }) }, groups(user, _, { loaders: { user_groups, groups } }) { let temp = await user_groups.load({ key: 'user_id', value: user.id, many: true }) return temp.map(x => groups.load({ key: 'id', value: x.group_id })) } }, Post: { user(post, _, { loaders: { users } }) { return users.load({ key: 'id', value: post.user_id }) } } }
  7. # Backend: dataloader const Database = use('Database') const {DataLoaderHelper} =

    require('dataloader-helper') const Loader = new DataLoaderHelper(async function (model, ref_id, keys) { // console.log(`*** loading all '${model}' with ids [${keys}] from database`) const data = await Database .raw(`select * from ${model} where ${ref_id} = ANY(?)`, [keys]) return data.rows }) /** * Define your loaders here. * Each key should be a valid * instance of DataLoader. */ const loaders = () => ({ users: Loader.create('users'), user_datas: Loader.create('user_datas'), posts: Loader.create('posts'), user_groups: Loader.create('user_groups'), groups: Loader.create('groups') }) module.exports = loaders
  8. # Backend: schema const { makeExecutableSchema } = require('graphql-tools') const

    fs = require('fs') const typeDefs = fs.readFileSync(__dirname + ‘/type.graphql', { encoding: 'utf8' }) const resolvers = require('./resolvers.js') module.exports = makeExecutableSchema({ typeDefs, resolvers: resolvers })
  9. apollo: { candidates: { // gql query query: queries.Candidates, //

    Static parameters variables() { let query = this.$route.query.query ? this.$route.query.query : this.set_init_query_order({ order: { id: -1 } }); return { q: JSON.parse(query) }; }, subscribeToMore: { document: queries.CandidateMutated, updateQuery: (prev, { subscriptionData }) => { const data = subscriptionData.data; if (!data) return prev; return GQLSubscriptionHelper.updateQuery(prev, data.candidateMutated); } } }, calendars: { // gql query query: queries.Calendars, subscribeToMore: { document: queries.CalendarMutated, updateQuery: (prev, { subscriptionData }) => { const data = subscriptionData.data; if (!data) return prev; return GQLSubscriptionHelper.updateQuery(prev, data.calendarMutated); } } } },
  10. subscription CandidateMutated { candidateMutated { mutation node{ ...candidateFields calendars {

    ...calendarFields } company{ ...companyFields } company_department_id company_department { ...companyDepartmentFields } interviewers { ...interviewerFields } information { basic } exams { ...examFields } } } }
  11. //Server side //singleton const pubsub = use('My/GraphPubSub') //publish this.addHook('afterCreate', async

    (candidate) => { pubsub.publish(CANDIDATE_MUTATED, { candidateMutated: { mutation: "CREATED", node: candidate.toJSON() }, company_id: candidate.company_id }); }) this.addHook('afterUpdate', async (candidate) => { pubsub.publish(CANDIDATE_MUTATED, { candidateMutated: { mutation: "UPDATED", node: candidate.toJSON() }, company_id: candidate.company_id }); }) this.addHook('afterDelete', async (candidate) => { pubsub.publish(CANDIDATE_MUTATED, { candidateMutated: { mutation: "DELETED", node: candidate.toJSON() }, company_id: candidate.company_id }); }) //subscribe Subscription: { candidateMutated: { subscribe: withFilter( () => pubsub.asyncIterator("CANDIDATE_MUTATED"), (payload, variables, context) => { return context.currentUser.company_id === payload.company_id } ) } }
  12. const getUser = next => async (root, args, context, info)

    => { try { context.currentUser = await context.auth.getUser() if (!context.currentUser) { throw new Error(`Unauthenticated!`); } } catch (e) { throw new Error(e); } return next(root, args, context, info); }; module.exports = {getUser}
  13. posts: getUser(async (_, { q }, { currentUser }) =>

    { const result = await Post.find(currentUser.id) return result })
  14. class AuthDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field) { const { resolve

    = defaultFieldResolver } = field; field.resolve = async function ( source, args, context, info, ) { context.currentUser = await context.auth.getUser() if(context.currentUser.role !== 'ADMIN'){ return null }else{ return resolve.call(this, source, args, context, info) } }; } }
  15. Q&A

  16. PROBLEM THAT WAITING TO BE SOLVED • Query Just for

    this Component? 
 
 Should it be stored in a global state for use or save?