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.

CocoaHeads Ukraine

October 06, 2018
Tweet

More Decks by CocoaHeads Ukraine

Other Decks in Programming

Transcript

  1. GRAPHQL MIDDLEWARE
    WRITE AND USE

    View Slide

  2. ALEXANDER
    SAENKO

    View Slide

  3. AGENDA

    View Slide

  4. GRAPHQL
    ▸ Data query and manipulation language
    ▸ Open Source
    ▸ Created by Facebook
    ▸ 10k stars on GitHub
    ▸ Always* backward-compatible

    View Slide

  5. WHEN TO USE?

    View Slide

  6. View Slide

  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"
    }
    ]
    }

    View Slide

  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"
    }
    ]
    }

    View Slide

  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"
    }
    ]
    }

    View Slide

  10. {
    "id": 118,
    "status": 0,
    "name": "The text you need",
    "updated_date": 1498741329,
    "tasks_count": 2
    }

    View Slide

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

    View Slide

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

    View Slide

  13. type TableModel {
    id: Int!
    status: Boolean!
    name: String!
    updatedDate: Date!
    tasksCount: Int!
    }

    View Slide

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

    View Slide

  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
    }

    View Slide

  16. {
    "id": 118,
    "status": 0,
    "is_completed": false,
    "name": "The text you need",
    "updated_date": 1498741329,
    "tasks_count": 2
    }

    View Slide

  17. type TableModel {
    id: Int!
    status: Boolean! @deprecated
    isCompleted: Boolean!
    name: String!
    updatedDate: Date!
    tasksCount: Int!
    }

    View Slide

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

    View Slide

  19. WHEN TO USE
    ▸ No breaking changes in API
    ▸ Client-driven API
    ▸ Minimum network pressure

    View Slide

  20. PROS & CONS

    View Slide

  21. 0 0+1
    JAVASCRIPT

    View Slide

  22. View Slide

  23. View Slide

  24. +
    https://code.visualstudio.com

    View Slide

  25. 0+1 1
    WRAP REST API

    View Slide

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

    View Slide

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

    View Slide

  28. 1+1 1
    DATA LOADERS

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  32. return {
    schema,
    rootValue: {
    id: ‘viewer’,
    loaders: createLoaders(fetchFn),
    },
    };

    View Slide

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

    ...

    View Slide

  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

    View Slide

  35. 2 1+1
    AUTHORIZATION

    View Slide

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

    View Slide

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

    View Slide

  38. View Slide

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

    View Slide

  40. View Slide

  41. View Slide

  42. View Slide

  43. 2 2+1
    SWIFT QUERY

    View Slide

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

    View Slide

  45. static func query(id: String) -> String {
    return """
    query {
    case(id: "\(id)") {
    id
    code
    status
    created {
    date
    }
    updated
    patient {
    name
    }
    treatmentPath {
    name
    }
    }
    }
    """
    }

    View Slide

  46. 2 3+1
    FILE UPLOAD

    View Slide

  47. View Slide

  48. View Slide

  49. 2+1 4
    ONBOARDING

    View Slide

  50. View Slide

  51. View Slide

  52. View Slide

  53. 3+1 4
    MAINTENANCE

    View Slide

  54. http://appreviewtimes.com/

    View Slide

  55. View Slide

  56. View Slide

  57. 4 4
    FINAL RESULTS

    View Slide

  58. LESSONS
    LEARNED

    View Slide

  59. TYPESCRIPT
    FROM THE
    BEGINNING

    View Slide

  60. IDEMPOTENCE

    View Slide

  61. EXPERIMENTAL
    SANDBOX

    View Slide

  62. YES,
    I WILL USE IT
    AGAIN

    View Slide

  63. THANK YOU!

    View Slide