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

Write and use GraphQL middleware

Write and use GraphQL middleware

Originally posted here: https://speakerdeck.com/alexandersaenko/write-and-use-graphql-middleware

Доклад включает в себя:
- Ретроспективу опыта написания GraphQL middleware (прокси-сервера между REST API и мобильным приложением) для iOS-проекта
- Анализ преимуществ и недостатков технологии
- Lessons learned использования GraphQL в production

This talk was made for CocoaHeads Kyiv #14 which took place Oct 6 2018.

Db84cf61fdada06b63f43f310b68b462?s=128

CocoaHeads Ukraine

October 06, 2018
Tweet

More Decks by CocoaHeads Ukraine

Other Decks in Programming

Transcript

  1. GRAPHQL MIDDLEWARE WRITE AND USE

  2. ALEXANDER SAENKO

  3. AGENDA

  4. GRAPHQL ▸ Data query and manipulation language ▸ Open Source

    ▸ Created by Facebook ▸ 10k stars on GitHub ▸ Always* backward-compatible
  5. WHEN TO USE?

  6. None
  7. { "id": 118, "status": 0, "name": "The text you need",

    "due_date": 1498861069, "created_date": 1498256269, "updated_date": 1498741329, "description": "Something you definitely don't need", "is_modified": 1, "modified_by": "Alexander Saenko", "tasks": [ { "id": 38, "name": "The first task", "description": "Something about the task"}, { "id": 41, "name": "The second task", "description": "Something about the second task" } ] }
  8. { "id": 118, "status": 0, "name": "The text you need",

    "due_date": 1498861069, "created_date": 1498256269, "updated_date": 1498741329, "description": "Something you definitely don't need", "is_modified": 1, "modified_by": "Alexander Saenko", "tasks": [ { "id": 38, "name": "The first task", "description": "Something about the task"}, { "id": 41, "name": "The second task", "description": "Something about the second task" } ] }
  9. { "id": 118, "status": 0, "name": "The text you need",

    "due_date": 1498861069, "created_date": 1498256269, "updated_date": 1498741329, "description": "Something you definitely don't need", "is_modified": 1, "modified_by": "Alexander Saenko", "tasks_count": 2 [ { "id": 38, "name": "The first task", "description": "Something about the task"}, { "id": 41, "name": "The second task", "description": "Something about the second task" } ] }
  10. { "id": 118, "status": 0, "name": "The text you need",

    "updated_date": 1498741329, "tasks_count": 2 }
  11. struct TableModel: Codable { let id: Int let status: Bool

    let name: String let updatedDate: Date let tasksCount: Int enum CodingKeys:String, CodingKey { case id case status case name case updatedDate = "updated_date" case tasksCount = "tasks_count" } }
  12. struct TableModel: Codable { let id: Int let status: Bool

    let name: String let updatedDate: Date let tasksCount: Int enum CodingKeys:String, CodingKey { case id case status case name case updatedDate = "updated_date" case tasksCount = "tasks_count" } }
  13. type TableModel { id: Int! status: Boolean! name: String! updatedDate:

    Date! tasksCount: Int! }
  14. export function TableModel(data) { return { id () { return

    data.id; }, status() { return data.status; }, name() { return data.name; }, updatedDate() { return new Date(data.updated_date * 1000); }, tasksCount() { return data.tasks.length; } } }
  15. { "id": 118, "status": 0, "name": "The text you need",

    "updatedDate": 1498741329, "tasksCount": 2 } struct TableModel: Codable { let id: Int let status: Bool let name: String let updatedDate: Date let tasksCount:Int }
  16. { "id": 118, "status": 0, "is_completed": false, "name": "The text

    you need", "updated_date": 1498741329, "tasks_count": 2 }
  17. type TableModel { id: Int! status: Boolean! @deprecated isCompleted: Boolean!

    name: String! updatedDate: Date! tasksCount: Int! }
  18. export function TableModel(data) { return { id () { return

    data.id; }, status() { return data.is_completed; }, isCompleted() { return data.is_completed; }, name() { return data.name; }, updatedDate() { return new Date(data.updated_date * 1000); }, tasksCount() { return data.tasks_count; } } }
  19. WHEN TO USE ▸ No breaking changes in API ▸

    Client-driven API ▸ Minimum network pressure
  20. PROS & CONS

  21. 0 0+1 JAVASCRIPT

  22. None
  23. None
  24. + https://code.visualstudio.com

  25. 0+1 1 WRAP REST API

  26. type Patient { id: String! code: String! name: String! group:

    String! }
  27. export function Patient(data) { return { __typeName: "Patient", id() {

    return data.id; }, code() { return data.id_code; }, name() { return data.first_name + ' ' + data.last_name; }, group() { return data.last_name ? data.last_name.trimLeft().charAt(0).toUpperCase() : data.first_name.trimLeft().charAt(0).toUpperCase(); } } }
  28. 1+1 1 DATA LOADERS

  29. BATCHING AND CACHING https://github.com/facebook/dataloader

  30. time: 0.724 URL: /api/user time: 0.71 URL: /api/vacancies time: 0.772

    URL: /api/user time: 1.116 URL: /api/invitations time: 1.125 URL: /api/counters/1 time: 1.126 URL: /api/friends time: 1.12 URL: /api/counters/1
  31. export default function createLoaders(fetchFn) { return { User: new DataLoader(async

    (keys) => { return Promise.all(keys.map(async () => { const response = await fetchFn(‘get’, `/api/user`); return await response.json(); })); }), Counters: new DataLoader((keys) => { return Promise.all(keys.map(async (id) => { const response = await fetchFn('get', `/api/counters/${id}`; return await response.json(); }); }) }
  32. return { schema, rootValue: { id: ‘viewer’, loaders: createLoaders(fetchFn), },

    };
  33. ...
 type: userType, resolve: async (rootValue) => { const {loaders}

    = rootValue; const user = await loaders.UserProfile.load(‘me’); const user1 = await loaders.UserProfile.load(‘me’); return user; }
 
 ... type: counterType, resolve: async (rootValue) => { const {loaders} = rootValue; const counter = await loaders.Counters.load(1); const counter1 = await loaders.Counters.load(1); return counter1; }
 ...
  34. time: 0.584 URL: /api/user time: 0.669 URL: /api/vacancies time: 0.982

    URL: /api/invitations time: 0.622 URL: /api/counters/1 time: 0.716 URL: /api/friends https://medium.com/@ven_korolev/make-applications-faster-with-a-dataloader-library-from-facebook-dec67d15e415
  35. 2 1+1 AUTHORIZATION

  36. return { schema: schema, context: { loaders: root.loaders(request.headers.authorization), token: request.headers.authorization

    }, graphiql: true }
  37. return { schema: schema, context: { loaders: root.loaders(request.headers.authorization), token: request.headers.authorization

    }, graphiql: true }
  38. None
  39. return { schema: schema, context: { loaders: root.loaders(request.headers.authorization), token: request.headers.authorization

    }, graphiql: true }
  40. None
  41. None
  42. None
  43. 2 2+1 SWIFT QUERY

  44. static let query = """ query { patients { id

    code name group } } """ struct Response: Decodable { let patients: [Patient] struct Patient: Decodable { let id: String let code: String let name: String let group: String } }
  45. static func query(id: String) -> String { return """ query

    { case(id: "\(id)") { id code status created { date } updated patient { name } treatmentPath { name } } } """ }
  46. 2 3+1 FILE UPLOAD

  47. None
  48. None
  49. 2+1 4 ONBOARDING

  50. None
  51. None
  52. None
  53. 3+1 4 MAINTENANCE

  54. http://appreviewtimes.com/

  55. None
  56. None
  57. 4 4 FINAL RESULTS

  58. LESSONS LEARNED

  59. TYPESCRIPT FROM THE BEGINNING

  60. IDEMPOTENCE

  61. EXPERIMENTAL SANDBOX

  62. YES, I WILL USE IT AGAIN

  63. THANK YOU!