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

實戰GraphQL.pdf

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for motephyr motephyr
August 18, 2019
320

 實戰GraphQL.pdf

Avatar for motephyr

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?