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
  2. 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 } }
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. Inconsistency is the key to bad API design. Apply it

    to attributes, endpoints and everything inbetween. Lesson learned
  10. 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
  11. 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
  12. 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
  13. A succesfull response HTTP/1.1 200 OK { "id": 2, "firstName":

    "Max", "LastName": "Musterman", [.. ] } POST /api/v1/userProfiles?action=create
  14. 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
  15. 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
  16. 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
  17. Error codes are bad. Always fail without a way to

    identify the cause. Lesson learned
  18. Also a succesfull response HTTP/1.1 200 OK { "error": {

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

    "message": "Wrong request", "tracking": "ROUTER_ Pp8q5d" } } POST /api/v1/userProfiles?action=create
  20. 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.
  21. A succesfull response HTTP/1.1 429 TOO MANY REQUESTS Retry-After: 20

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

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

    { "message": "Too much" } } POST /api/v1/userProfiles?action=create
  24. 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.
  25. There is beauty in opaque numbers. Why tell the user

    how long to wait for? Lesson learned
  26. 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
  27. 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
  28. A succesfull response HTTP/1.1 200 OK { "error": { "message":

    "Bad user" } } POST /api/v1/userProfiles?action=create
  29. Don’t generate your docs from code (or vice versa). That

    would make them consistent. Lesson learned
  30. 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
  31. 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