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

When Node and APIs become BFFs

When Node and APIs become BFFs

API development is a common challenge for many teams and companies. In a world of micro-services, multiple backends and ever more demanding front-ends, building the right set of APIs becomes more important.

How can a single API support a diverse set of clients, different data access patterns, and multiple SLAs?

Backend for Frontend (BFF) is a pattern that has emerged to solve some of these challenges and help software engineers build better-focused APIs that can cater to different user experiences. This is achieved by actually building one backend for every frontend client.

In this talk, I discuss and present some of the experience and the challenges me and my team ran into while building API services for all frontends at IFTTT.

Ivayr Farah Netto

November 13, 2017
Tweet

More Decks by Ivayr Farah Netto

Other Decks in Programming

Transcript

  1. When Node and
    APIs become BFFs
    @nettofarah

    View Slide

  2. Netto Farah
    @nettofarah
    Eng. Manager at

    View Slide

  3. This is a story about

    View Slide


  4. monorail


    v0 -> inception

    View Slide

  5. but as we grew…

    View Slide

  6. IFTTT ❤
    • Millions of users
    • Billions of API calls every day
    • Website, iOS app, Android app

    View Slide

  7. that architecture
    became challenging

    View Slide


  8. monorail


    challenges with v0

    View Slide

  9. we knew we needed
    to make some changes

    View Slide

  10. v1 -> monorail + friends

    monorail


    Feeds
    Recommendations
    Geolocation

    View Slide

  11. View Slide

  12. What makes this
    approach so challenging?

    View Slide

  13. reality
    monorail


    Feeds
    Recommendations
    Geolocation





    View Slide

  14. repeated concerns

    View Slide

  15. We looked at what
    other people were doing…

    View Slide

  16. http://microservices.io/patterns/apigateway.html
    https://nginx.com/blog/microservices-api-gateways-
    part-1-why-an-api-gateway/

    View Slide

  17. Feed
    Recommendations
    Geolocation
    MonoRail



    C

    O
    O

    R

    D

    I

    N

    A

    T
    I
    O
    N
    T

    R

    A
    C

    I

    N
    G
    A
    U
    T
    H

    v2 -> API Gateway

    View Slide



  18. API Gateway

    View Slide

  19. but API Gateways
    have their limitations too

    View Slide

  20. different access patterns

    View Slide

  21. multiple use cases

    View Slide

  22. ambiguity

    View Slide

  23. solved with documentation
    or conventions

    View Slide

  24. [/,0,1..2] => ✅
    4

    View Slide

  25. 4
    [5] =>
    ask me later…

    View Slide

  26. backends for
    frontends

    View Slide

  27. BFFs

    View Slide

  28. Feed
    Recommendations
    Geolocation
    MonoRail



    v2 -> BFFs


    View Slide

  29. View Slide

  30. But there’s still a fair amount
    of repetition

    View Slide

  31. buffalo


    View Slide

  32. Feed
    Recommendations
    Geolocation
    MonoRail



    v2.1 -> Buffalo





    View Slide

  33. But this is
    JSKongress

    View Slide


  34. + = =

    View Slide

  35. simple concurrency
    primitives

    View Slide

  36. building reliable
    BFFs with node

    View Slide

  37. loading async
    resources

    View Slide

  38. // get some async resource
    get(url).then(result => {
    // render json
    return res.json(result)
    })

    View Slide

  39. const promises = Promise.all([
    fetch(feedURL),
    fetch(locationURL)
    ])
    promises.then(result => {
    return res.json({
    feed: result[0],
    location: result[1]
    })
    })

    View Slide

  40. Service

    latency and network
    volatility

    View Slide

  41. let’s take a look
    at our feed service

    View Slide

  42. 99pct ~ 1500ms
    avg ~ 200ms

    View Slide

  43. const promises = Promise.race([
    fetch(feedURL),
    fetch(feedURL),
    fetch(feedURL)
    fetch(feedURL)
    ])
    promises.then(feed => {
    return res.json(feed)
    })

    View Slide

  44. View Slide

  45. timeouts

    View Slide

  46. const pTimeout = require(‘p-timeout')
    // 99pct was ~1500ms

    pTimeout(
    get(feedURL),
    1600
    ).then(onSuccess, onError)
    function onError(err) {
    console.error(err)
    //=> [TimeoutError: Promise timed out]
    return res.error(err)
    }

    View Slide

  47. failures
    Service

    View Slide

  48. retries

    View Slide

  49. const retry = require(‘p-retry')
    // retry 2 times before giving up
    retry(loadFeed, {
    retries: 2
    })
    // everything is still a promise
    function loadFeed() {
    return get(feedURL).then(feedRes => {

    return feedRes.json()
    })
    }

    View Slide

  50. failures + latency
    Service


    View Slide

  51. timeouts + retries?

    View Slide

  52. 99pct ~ 1500ms
    avg ~ 200ms
    median ~ 150ms

    View Slide

  53. most responses are fast!

    ~ 200ms
    slow responses are actually
    pretty rare

    View Slide

  54. ~3 x 250ms ~= 750ms

    1 x 1500ms ~= 1500ms

    View Slide

  55. View Slide

  56. // avg response time is ~200ms
    const feedTimeout = 250
    pTimeout(
    loadFeed,
    feedTimeout
    )
    .then(onSuccess)
    .catch(onError)

    View Slide

  57. // avg response time is ~200ms
    const feedTimeout = 250
    const retries = 2
    pRetry(
    () => pTimeout(loadFeed,feedTimeout),
    { retries }
    )
    .then(onSuccess)
    .catch(onError)

    View Slide

  58. // avg response time is ~200ms

    const feedTimeout = 250
    const totalTimeout = 700
    const retries = 2
    pTimeout(
    pRetry(
    () => pTimeout(loadFeed, feedTimeout),
    { retries }
    ),
    totalTimeout
    )
    .then(onSuccess)
    .catch(onError)

    View Slide

  59. homework
    • conditional retries
    • exponential backoffs
    • circuit breakers

    View Slide

  60. using promises to build
    well behaved BFFs

    View Slide

  61. let’s take a look at our
    analytics service

    View Slide

  62. response time
    throughput

    View Slide

  63. // I have 100 queries to run
    const queries = […]
    const pLimit = require(‘p-limit’)
    // Only 10 promises at a time
    const only10 = pLimit(10)
    const promises = queries.map(q => {
    return only10(
    () => fetchAnalyticsQuery(q)
    )
    })
    Promise.all(promises).then(onSuccess)

    View Slide

  64. const sleep = require('then-sleep')
    // Or something a bit simpler
    while (condition) {
    await runAnalyticsQuery(...)
    await sleep(250)
    }

    View Slide

  65. other cool ideas
    • reducing bursts with jittering: then-sleep, p-defer
    • priority queues: p-queue
    • circuit breaking: opossum

    View Slide

  66. Promises (and async/await)
    are simple concurrency
    primitives
    with a relatively low barrier of entry

    View Slide

  67. but they also
    have limitations
    • cancellations
    • error handling can be tricky
    • stack traces aren’t perfect

    View Slide

  68. Even more stuff!
    • param validation: https://github.com/nettofarah/
    property-validator
    • testing: https://github.com/nettofarah/axios-vcr

    View Slide

  69. for a longer list of promise
    awesomeness
    https://github.com/sindresorhus/promise-fun

    View Slide

  70. for more sophisticated
    async programming

    primitives
    rx.js
    check out

    View Slide

  71. start with
    the simplest solution
    you can think of
    and then grow it from there

    View Slide

  72. @nettofarah
    [email protected]

    View Slide