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

API Nightmares - Designing the Worst API Possible…So You Don't Have To

API Nightmares - Designing the Worst API Possible…So You Don't Have To

APIs have become ubiquitous in the modern day and age. From web services to clocks everything gets connected through APIs. This growth in interest for APIs has led to an increased focus on designing APIs in such a way that they are easy to consume, easy to maintain, and offer a pleasant developer experience. But what if you don't want your API to be easy and pleasant? This talk is a humorous take at designing a "nightmare API" that offers the worst possible developer experience. Take what you see in this talk - do the opposite - and your developers will thank you for it.

Marcel Neidinger

October 18, 2022
Tweet

More Decks by Marcel Neidinger

Other Decks in Programming

Transcript

  1. © 2022Cisco and/or its affiliates. All rightsreserved. Cisco Confidential.
    Marcel Neidinger
    Software Solutions Engineer, EMEA Systems Engineering
    18.10.2022
    Develop | Oct 18th
    API Nightmares
    Building the worst API possible – so you don’t have to

    View Slide

  2. @squ4rks

    View Slide

  3. Let’s make those endpoints
    hurt.

    View Slide

  4. A reasonable request and response
    GET /api/v1/users?country=Germany
    {
    "id": 1,
    "firstName": "Max",
    "lastName": "Musterman",
    "username": "c1sc0f4n",
    "address": {
    "street": "Mainstreet 42C"
    "city": "Berlin",
    "country": "Germany"
    },
    "phone": {
    "mobile": "+49 102 093920",
    "home": None
    }
    }

    View Slide

  5. A reasonable request and response
    {
    "id": 1,
    "firstName": "Max",
    "lastName": "Musterman",
    "username": "c1sc0f4n",
    "address": {
    "street": "Mainstreet 42C"
    "city": "Berlin",
    "country": "Germany"
    },
    "phone": {
    "mobile": "+49 102 093920",
    "home": None
    }
    }
    GET /api/v1/users?country=Germany

    View Slide

  6. Searching is listing and
    iterating. And it’s best
    done on the client.
    Lesson learned

    View Slide

  7. Your database (maybe)
    User
    id: INTEGER NOT NULL
    firstName: VARCHAR
    lastName: VARCHAR
    username: VARCHAR
    address: INTEGER[FK]
    phone: INTEGER[FK]
    Address
    id: INTEGER NOT NULL
    street: VARCHAR
    city: VARCHAR
    country: VARCHAR
    Phone
    id: INTEGER NOT NULL
    home: VARCHAR
    mobile: VARCHAR

    View Slide

  8. Less reasonable
    {
    "id": 1,
    "firstName": "Max",
    "lastName": "Musterman",
    "username": "c1sc0f4n",
    "address": 2,
    "phone": 3
    }
    GET /api/v1/phones
    GET /api/v1/addresses
    GET /api/v1/users

    View Slide

  9. Don’t give the interface
    what it needs. Give it your
    database layout.
    Lesson learned

    View Slide

  10. Less reasonable
    {
    "id": 1,
    "firstName": "Max",
    "LastName": "Musterman",
    "user_name": "c1sc0f4n",
    "address": 2,
    "phone": 3
    }
    GET /api/v1/phones
    GET /api/v1/addresses
    GET /api/v1/users

    View Slide

  11. Less reasonable
    {
    "id": 1,
    "firstName": "Max",
    "LastName": "Musterman",
    "user_name": "c1sc0f4n",
    "address": 2,
    "phone": 3,
    "enableUser": true,
    "enabledCalling": false
    }
    GET /api/v1/phones
    GET /api/v1/addresses
    GET /api/v1/users

    View Slide

  12. Less reasonable
    {
    "id": 1,
    "firstName": "Max",
    "LastName": "Musterman",
    "user_name": "c1sc0f4n",
    "address": 2,
    "phone": 3,
    "enableUser": true,
    "enabledCalling": false
    }
    GET /api/v1/phone_number
    GET /api/v1/addresses
    GET /api/v1/userProfile

    View Slide

  13. Inconsistency is the key
    to bad API design. Apply
    it to attributes, endpoints
    and everything inbetween.
    Lesson learned

    View Slide

  14. Less reasonable
    {
    "id": 1,
    […]
    "address": 2,
    "phone": 3,
    "links": [{
    "href": "/addresses/2",
    "rel": "address",
    "type": "GET"
    }]
    }
    GET /api/v1/phone_number
    GET /api/v1/addresses
    GET /api/v1/userProfile

    View Slide

  15. Less reasonable
    {
    "id": 1,
    […]
    "address": 2,
    "phone": 3,
    "links": [{
    "href": "/addresses/2",
    "rel": "address",
    "type": "GET"
    }]
    }
    GET /api/v1/phone_number
    GET /api/v1/addresses
    GET /api/v1/userProfile

    View Slide

  16. Less reasonable
    {
    "id": 1,
    […]
    "address": 2,
    "phone": 3,
    "links": [{
    "path": "/addresses/2",
    "rel": "address"
    }]
    }
    GET /api/v1/phone_number
    GET /api/v1/addresses
    GET /api/v1/userProfile

    View Slide

  17. HATEOAS is for people
    who build usable APIs.
    Don’t do it.
    Lesson learned

    View Slide

  18. Keywords matter
    POST /api/v1/userProfile
    GET /api/v1/userProfile/1
    GET /api/v1/userProfile
    PATCH /api/v1/userProfile/1
    DELETE /api/v1/userProfile/1
    POST /api/v1/userProfile/1/promote

    View Slide

  19. Keywords don’t matter
    POST /api/v1/userProfile

    View Slide

  20. Keywords don’t matter
    POST /api/v1/userProfile?id=1

    View Slide

  21. Keywords don’t matter
    POST /api/v1/userProfile?id=1&action=delete

    View Slide

  22. If we really want it -
    Everything can be a POST.
    Lesson learned

    View Slide

  23. Who needs errors anyways.

    View Slide

  24. A succesfull response
    HTTP/1.1 200 OK
    {
    "id": 2,
    "firstName": "Max",
    "LastName": "Musterman",
    [.. ]
    }
    POST /api/v1/userProfiles?action=create

    View Slide

  25. A succesfull response
    HTTP/1.1 400 BAD REQUEST
    {
    "error": {
    "code": 1000,
    "message": "Field ‘username’ is required",
    "tracking": "ROUTER_ Pp8q5d"
    }
    }
    POST /api/v1/userProfiles?action=create

    View Slide

  26. Also a succesfull response
    HTTP/1.1 200 OK
    {
    "error": {
    "code": 1000,
    "message": "Field ‘username’ is required",
    "tracking": "ROUTER_ Pp8q5d"
    }
    }
    POST /api/v1/userProfiles?action=create

    View Slide

  27. Status codes are bad.
    Always fail succesfully.
    Lesson learned

    View Slide

  28. Also a succesfull response
    HTTP/1.1 200 OK
    {
    "error": {
    "code": 1000,
    "message": "Field ‘username’ is required",
    "tracking": "ROUTER_ Pp8q5d"
    }
    }
    POST /api/v1/userProfiles?action=create

    View Slide

  29. Error codes are bad.
    Always fail without a way
    to identify the cause.
    Lesson learned

    View Slide

  30. Also a succesfull response
    HTTP/1.1 200 OK
    {
    "error": {
    "message": "Wrong request",
    "tracking": "ROUTER_ Pp8q5d"
    }
    }
    POST /api/v1/userProfiles?action=create

    View Slide

  31. Keeping your errors as
    generic as possible saves
    you time in development.
    Lesson learned

    View Slide

  32. Also a succesfull response
    HTTP/1.1 200 OK
    {
    "error": {
    "message": "Wrong request",
    "tracking": "ROUTER_ Pp8q5d"
    }
    }
    POST /api/v1/userProfiles?action=create

    View Slide

  33. Tracking codes make it
    easy to find logs.
    What’s the fun in easy?
    Lesson learned

    View Slide

  34. I know a limit that you don’t.

    View Slide

  35. On your API docs
    Rate Limiting
    Rate Limiting is required to maintain proper service levels
    for all users of our platform.
    Handling rate-limited requests
    If you hit a rate limit a 429 Too Many Requests status will
    be send. The response also has a Retry-After header
    with your wait time in seconds.

    View Slide

  36. A succesfull response
    HTTP/1.1 429 TOO MANY REQUESTS
    Retry-After: 20
    {
    }
    POST /api/v1/userProfiles?action=create

    View Slide

  37. A succesfull response
    HTTP/1.1 200 OK
    Retry-After: 20
    {
    "error": {
    "message": "Too much"
    }
    }
    POST /api/v1/userProfiles?action=create

    View Slide

  38. A succesfull response
    HTTP/1.1 200 OK
    Retry-After: 20
    {
    "error": {
    "message": "Too much"
    }
    }
    POST /api/v1/userProfiles?action=create

    View Slide

  39. On your API docs
    Rate Limiting
    Rate Limiting is required to maintain proper service levels
    for all users of our platform.
    Handling rate-limited requests
    If you hit a rate limit a 429 Too Many Requests status will
    be send. The response also has a Retry-After header
    with your wait time in seconds.

    View Slide

  40. There is beauty in opaque
    numbers. Why tell the user
    how long to wait for?
    Lesson learned

    View Slide

  41. On your API docs
    Rate Limiting
    […]
    Upper Limits
    You can expect the following upper limits (with some
    leway to burst requests):
    - Maximum of 100 requests per minute
    - Maximum of 60 requests for POST /userProfiles

    View Slide

  42. On your API docs
    Rate Limiting
    […]
    Upper Limits
    You can expect the following upper limits (with some
    leway to burst requests):
    - Maximum of 100 requests per minute
    - Maximum of 60 requests for POST /userProfiles

    View Slide

  43. Consistently inconsistent
    is the way to go for our
    rate limit.
    Lesson learned

    View Slide

  44. A succesfull response
    HTTP/1.1 200 OK
    {
    "error": {
    "message": "Bad user"
    }
    }
    POST /api/v1/userProfiles?action=create

    View Slide

  45. Be draconic. It’s driving up
    your
    bill afterall.
    Lesson learned

    View Slide

  46. In terms of docs, we have
    no docs.

    View Slide

  47. The best doc is no doc.
    Lesson learned

    View Slide

  48. On your API docs
    User Profiles
    POST userProfiles?action=details&id=
    Response Properties:
    - username: The username
    - firstName: The first name

    View Slide

  49. Standards are there to be
    broken. Run your own!
    What’s a "swagger"
    anyway?
    Lesson learned

    View Slide

  50. On your API docs
    User Profiles
    POST userProfiles?action=details&id=
    Response Properties:
    - username: The username
    - firstName: The first name

    View Slide

  51. Don’t generate your docs
    from code (or vice versa).
    That would make them
    consistent.
    Lesson learned

    View Slide

  52. On your API docs
    Changelog
    August 22, 2022
    • The userProfile endpoint now returns the username
    field as user_name.
    • The API now offers a promote action on user profiles.
    Breaking Change
    New
    Subscribe via RSS Subscribe via e-mail

    View Slide

  53. On your companies social media feed
    Marcel Neidinger @squ4rks
    @ACMEInc did you change the username attribute
    for a user profile to user_name?!?
    Today at 03:42am

    View Slide

  54. Use error-driven changelogs
    for your API.
    Lesson learned

    View Slide

  55. Sanity.

    View Slide

  56. Consistency

    View Slide

  57. Predictability

    View Slide

  58. View Slide

  59. View Slide