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 full-size slide

  2. ALEXANDER
    SAENKO

    View full-size slide

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

    View full-size slide

  4. WHEN TO USE?

    View full-size slide

  5. {
    "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 full-size slide

  6. {
    "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 full-size 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_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 full-size slide

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

    View full-size slide

  9. 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 full-size slide

  10. 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 full-size slide

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

    View full-size slide

  12. 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 full-size slide

  13. {
    "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 full-size slide

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

    View full-size slide

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

    View full-size slide

  16. 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 full-size slide

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

    View full-size slide

  18. 0 0+1
    JAVASCRIPT

    View full-size slide

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

    View full-size slide

  20. 0+1 1
    WRAP REST API

    View full-size slide

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

    View full-size slide

  22. 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 full-size slide

  23. 1+1 1
    DATA LOADERS

    View full-size slide

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

    View full-size slide

  25. 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 full-size slide

  26. 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 full-size slide

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

    View full-size slide

  28. ...

    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 full-size slide

  29. 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 full-size slide

  30. 2 1+1
    AUTHORIZATION

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  34. 2 2+1
    SWIFT QUERY

    View full-size slide

  35. 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 full-size slide

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

    View full-size slide

  37. 2 3+1
    FILE UPLOAD

    View full-size slide

  38. 2+1 4
    ONBOARDING

    View full-size slide

  39. 3+1 4
    MAINTENANCE

    View full-size slide

  40. http://appreviewtimes.com/

    View full-size slide

  41. 4 4
    FINAL RESULTS

    View full-size slide

  42. LESSONS
    LEARNED

    View full-size slide

  43. TYPESCRIPT
    FROM THE
    BEGINNING

    View full-size slide

  44. EXPERIMENTAL
    SANDBOX

    View full-size slide

  45. YES,
    I WILL USE IT
    AGAIN

    View full-size slide