Slide 1

Slide 1 text

實戰GRAPHQL BY MARK

Slide 2

Slide 2 text

為你⾃自已學 GRAPHQL

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

ABOUT ME • Web Developer. • 追求穩定,⾼高效能,開發維護成本低,開發速度快的Web開發⽅方法。 • 在軟體開發的規畫和執⾏行行期,持續不斷的追求漸進式進化。

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

WHY GRAPHQL?

Slide 10

Slide 10 text

如果你是BACKEND
 你說要導入GRAPHQL • You are doing your own. • 你正在搞你⾃自已。 • 不過只有剛開始不舒服,之後就會覺得... • API不⽤用⼀一直改來來改去 很爽的啦

Slide 11

Slide 11 text

如果你是FRONTEND
 你可能在想:
 「什什麼?我⼜又要學新的東⻄西?」 • 這個⼈人得了了便便宜還賣乖 • (其實前端很⾎血汗的)

Slide 12

Slide 12 text

如果你是UI/UX的⼈人
 你是奇材 • 我以為你是畫wireframe兼設計的⼈人⽽而已。 • 你知道的太多了了 • 過來來寫程式吧

Slide 13

Slide 13 text

會覺得GRAPHGL是好東⻄西的⼈人 需要⼀一顆考慮 前後端如何做資料交換的⼤大腦 ⽽而且這個⼈人有需要 把前後端分別做出來來

Slide 14

Slide 14 text

COMPONENT BASED

Slide 15

Slide 15 text

• Single page include more and more components.

Slide 16

Slide 16 text

• Restful api doesn’t care client. • But Graphql do.

Slide 17

Slide 17 text

RESTFUL API TO COMPONENT BASED API

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

RESTFUL API TO COMPONENT BASED API

Slide 20

Slide 20 text

RESTFUL API TO COMPONENT BASED API

Slide 21

Slide 21 text

RESTFUL API TO COMPONENT BASED API

Slide 22

Slide 22 text

http://www.igoogleportal.com/

Slide 23

Slide 23 text

• 3. UI Driven Development

Slide 24

Slide 24 text

UI DRIVEN DEVELOPMENT

Slide 25

Slide 25 text

UI DRIVEN DEVELOPMENT • 確定了了Wireframe
 —>定義DB schema
 —>處理理ORM
 —>考慮Data Source • 決定如何query的語法 • 剩下的就是如何做資料的
 CREATE
 UPDATE
 DELETE

Slide 26

Slide 26 text

理理想的狀狀態

Slide 27

Slide 27 text

• 我們有⼀一份結構清楚的Wireframe • 以致於: • Frontend developer可以識別出⾴頁⾯面上的Component加以切分 • Backend developer給出的
 GraphQL API對每個Componet都可以組出對應的Component

Slide 28

Slide 28 text

====================

Slide 29

Slide 29 text

下⾯面的投影片
 是OPEN SOURCE 
 的CODE


Slide 30

Slide 30 text

FRONTEND的⼈人有個概念念就好

Slide 31

Slide 31 text

因為BACKEND的部分佔了了八成

Slide 32

Slide 32 text

有興趣的朋友在會後 都可以繼續討論
 看看有沒有更更好的做法

Slide 33

Slide 33 text

• 4.Query Arguments

Slide 34

Slide 34 text

(KNEX EXAMPLE) 動態QUERY參參數 • where query • sort • whereIn • don’t use OR

Slide 35

Slide 35 text

DON’T USE THAT

Slide 36

Slide 36 text

• 只使⽤用必要的屬性做為參參數 • use GET for HTTP caches.

Slide 37

Slide 37 text

• 5.Handle your N+1 query.

Slide 38

Slide 38 text

# 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 } } }

Slide 39

Slide 39 text

# 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 }

Slide 40

Slide 40 text

# 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 }) } } }

Slide 41

Slide 41 text

# 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

Slide 42

Slide 42 text

# 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 })

Slide 43

Slide 43 text

https://github.com/motephyr/dataloader-helper/

Slide 44

Slide 44 text

• 6. Apollo&Subscription.

Slide 45

Slide 45 text

• Subscription只在需要Realtime時使⽤用 • Need Websocket Support. • 由Page做查詢,傳props⾄至Component.

Slide 46

Slide 46 text

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); } } } },

Slide 47

Slide 47 text

subscription CandidateMutated { candidateMutated { mutation node{ ...candidateFields calendars { ...calendarFields } company{ ...companyFields } company_department_id company_department { ...companyDepartmentFields } interviewers { ...interviewerFields } information { basic } exams { ...examFields } } } }

Slide 48

Slide 48 text

https://github.com/motephyr/gql-subscription-helper

Slide 49

Slide 49 text

//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 } ) } }

Slide 50

Slide 50 text

• 7. About Modal and Route Change

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

• React router&Vue router: Nested routes • 將需要保留留狀狀態的⾴頁⾯面做成有網址的路路由,⽅方便便使⽤用者散播資訊。 • 在⾴頁⾯面內瀏覽時,保留留Parent Page的狀狀態,並使⽤用subscription. • 在不同的⾴頁⾯面切換時,以cache-and-network為主

Slide 53

Slide 53 text

• 8. Authorization: router, resolver, directive

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

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}

Slide 56

Slide 56 text

posts: getUser(async (_, { q }, { currentUser }) => { const result = await Post.find(currentUser.id) return result })

Slide 57

Slide 57 text

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) } }; } }

Slide 58

Slide 58 text

REFERENCE https://graphql.org/ https://medium.com/naresh-bhatia/graphql-concepts-i-wish-someone-explained-to-me-a-year- ago-514d5b3c0eab https://codersera.com/blog/nestjs-typeorm-graphql-dataloader-tutorial-with-typescript/ https://medium.com/paypal-engineering/graphql-resolvers-best-practices-cd36fdbcef55 https://medium.com/the-guild/authentication-and-authorization-in-graphql-and-how-graphql-modules-can- help-fadc1ee5b0c2

Slide 59

Slide 59 text

Q&A

Slide 60

Slide 60 text

PROBLEM THAT WAITING TO BE SOLVED • Query Just for this Component? 
 
 Should it be stored in a global state for use or save?