Trabe

Trabe logo

Asís & David

https://trabe-teaching.github.io/1001-apis

Context

1001 APIs

APIs as a contract

Initial requisites

Initial solution


const MAX_REQUESTS = 100;

let requestCount = 0;

function handle(request) {
  if (requestCount > MAX_REQUESTS) {
    throw new Error(429);
  }

  requestCount++;

  return data;
}

server.use((request) => handle(request)).start();
      

New requisite: configurable max requests


const config = readYaml("path/to/config.yml");

let requestCount = 0;

function handle(request) {
  if (requestCount > config.max_requests) {
    throw new Error(429);
  }

  requestCount++;

  return data;
}

server.use((request) => handle(request)).start();
      

New requisite: serverless

Workaround


writeYaml("path/to/config.yml", {
  max_requests: process.env.MAX_REQUESTS
});
      

New requisite: cloud workers


let requestCount = 0;

function handle(request, { config }) {
  if (requestCount > config.max_requests) {
    throw new Error(429);
  }

  requestCount++;

  return data;
}

const config = readYaml("path/to/config.yml");

server.use((request) => handle(request, { config })).start();
      

Side note: APIs and testing


test("handle max requests", () => {
  const config = {
    max_requests: 1,
  };

  expect(handle(mockRequest, { config })).toBe(data)
  expect(handle(mockRequest, { config })).toThrow(429);
  expect(handle(mockRequest, { config })).toThrow(429);
});
      

test("handle max requests", () => {
  writeYaml("path/to/config.yml", {
    max_requests: 1
  });

  expect(handle(mockRequest)).toBe(data)
  expect(handle(mockRequest)).toThrow(429);
  expect(handle(mockRequest)).toThrow(429);
});
      

Dealing with API breaking changes


let requestCount = 0;

const deprecatedConfig = readYaml("path/to/config.yml");

function handle(request, { config } = {}) {
  if (!config) {
    console.warn("handle with no config is deprecated");
  }

  if (requestCount > (config ?? deprecatedConfig).max_requests) {
    throw new Error(429);
  }

  requestCount++;

  return data;
}

// Deprecated
server.use((request) => handle(request)).start();
// vs. new
server.use((request) => handle(request, { config: { max_requests: 10 } })).start();
      

New requisite: dynamic config


max_requests:
  - priority: low
    max: 10
  - priority: high
    max: 100
      

let requestCount = 0;

function getMaxRequests(request, config) {
  config.max_requests.find(/* predicate */).max;
}

function handle(request, { config } = {}) {
  if (requestCount > getMaxRequests(request, config) {
    throw new Error(429);
  }

  requestCount++;

  return data;
}

server.use((request) => handle(request, { config: /* DSL */ })).start();
      

let requestCount = 0;

function handle(request, { config } = {}) {
  if (requestCount > config.getMaxRequests(request)) {
    throw new Error(429);
  }

  requestCount++;

  return data;
}

function getMaxRequests(request) {
  return someCalculatedMaxValue;
}

server.use((request) => handle(request, { config: { getMaxRequests } })).start();
      

Parting thoughts

This was just an example

Questions?