Stateful Simulations

Overview

Many APIs in real life offer CRUD functionality - Create, Read, Update, and Delete:

  • Create will save a new resource on the server side (in RESTful APIs terms; aka entity, object).

  • Read, sometimes also called Retrieve, will find the resource and return it to the client.

  • Update operation modifies a resource that has been created already.

  • Delete removes a resource from the server.

The expectation of a client using such API is that if the client makes a call to create or update a resource, it will get back that very same resource in the next read call. That assumes, of course, that there are no concurrent calls by other clients modifying or deleting the same resource.

The API Simulator’s support for Stateful Simulations can simulate such APIs using KV (Key/Value) Stores that allow state between calls to be maintained on the server side.

Some tools use Finite-State Machines, for which you have to predefine all states and rules for transitioning from one state to another. Using KV Stores is a much simpler solution for a wide range of Stateful Simulations.

KV Stores

Key/Value Stores is a very simple concept: a key uniquely identifies a value in the Store. You have at your disposal a few operations:

  • Create or update (replace) a value identified by a key:
    put(key, value)

    Returns the previous value if it existed for the given key. Neither the key nor the value is allowed to be null.

  • Read a value by its key:
    get(key)

    Returns the value for the given key or null if the key isn’t found in the KV Store. The value returned will be null if the input key is null.

  • Get a collection of all key/value element pairs currently in the KV Store:
    getAll()

    Use .key and .value on each element in the collection to get to the key and value, respectively.

  • Remove a value:
    remove(key)

    Returns the value removed or null if the key isn’t found in the KV Store. The value returned will be null if the input key is null.

  • Remove all values:
    removeAll()

    No return value. The KV Store will be empty after this operation.

  • Count the number of elements in the Store:
    count()

  • Check if a key exists in the Store. In other words, if the Store contains an element for the given key:
    containsKey(key)

    Returns true if an element for the given key exists in the Store, or false otherwise. If the input key is null the method will return false.

The key for an element in the KV Store is a non-null string. It is up to you what a key will be - an element from the request body or URL, UUID generated by the simlet, or something else…​for as long as that key uniquely identifies the value.

The non-null value can be anything: a JSON string, XML string, an object obtained after parsing the request payload…​

You are in full control.

Configuration

To prevent running Out of Memory (OOM), the API Simulator limits the number of elements in KV Stores used in Stateful Simulations. The default is 64 elements per KV Store. Much like a cache, API Simulator will remove the Least Recently Used (LRU) element when the limit - maximum number of slots - is reached.

If that default satisfies your requirements, you can just define in simlets a parameter from KV Store and use it:

Customers:
  is: parameter
  from: kvStore
  named: customers

You have to configure the KV Store in the simulation’s apisim.yaml if your API simulation needs to store and use more elements than the default. That is described in the documentation for the Configuration File. For example, to configure a KV Store named "customers" with maximum of 1,000 elements (slots):

simulation: your-awesome-simulation
...
kvStores:
  - name: customers
    slots: 1000
...

Keep in mind that for large KV Stores you may also have to increase the memory for API Simulator. See Environment Variables.

KV Store Names

You may have noticed that the KV Stores are named. Use a combination of letters, digits, and possibly special characters but, to assure forward compatibility, don’t use a name for KV Store that starts with an underscore _. Such names are reserved for potential internal use in the future.

CRUD API Example

Below are two simlets, part of a stateful simulation, that implement the Read and Create operations of a CRUD API. The API Simulator download distro contains the full example.

The example doesn’t try to be the smallest CRUD API. Instead, it shows techniques to simulate real CRUD APIs that do input validation, check for duplicates, return a proper status code when data is missing, etc. It is to note that the CRUD APIs you have may be different from the example, like in how they handle POST vs. PUT, whether the IDs are generated by the server (API Simulator) or on the client side…​ Still, the example is a great guide to help you in simulating CRUD APIs using stateful simulations.

Read a Customer simlet:

simlet: read-customer

# The value of the customerId from the URI path
# or null if the path doesn't match the pattern
CustomerId:  (1)
  is: parameter
  from: uriPathPattern
  pattern: /api/v1/customers/{customerId}

request:  (2)
  - method: GET
  - where: parameter
    named: CustomerId
    not equals: null

# KV Store with Customers data persisted across requests
Customers:  (3)
  is: parameter
  from: kvStore
  named: customers

# The `eval` expression will return the element identified
# by the CustomerId key if it exists, or null otherwise
Customer:  (4)
  is: parameter
  from: constant
  eval: return Customers.get(CustomerId)

responses:
  - when:
      request:
        - where: parameter
          named: Customer
          equals: null  (5)
    from: stub
    # Not Found
    status: 404  (6)

    # otherwise
  - from: template
    status: 200
    body: "${Customer}"  (7)
1 Whatever is passed in for the customerId path segment becomes the value for the CustomerId parameter.
2 The simlet matches GET requests that have Customer Id path parameter. That parameter can change from one call to the next.
3 This is a parameter to access a KV Store named "customers". The same name - "customers" - shall be used in the simlets of this simulation that add, update, or delete elements representing Customers.
4 Customer is a reusable parameter which value is retrieved from the Customers KV Store using the CustomerId passed in the URI path.
5 Check if the Customer wasn’t found in the KV Store for the input CustomerId.
6 Returns status code 404 (Not Found) if Customer for the input CustomerId wasn’t found in the KV Store.
7 Otherwise, if the Customer exists in the KV Store, this returns the Customer data in the body of the response.

Create a new Customer simlet:

simlet: create-customer

request:  (1)
  - method: POST
  - uriPath: /api/v1/customers
  - where: header
    named: "content-type"
    contains: "application/json"

Customer:  (2)
  is: parameter
  from: body

CustomerId:  (3)
  is: parameter
  from: body
  element: .id

Customers:  (4)
  is: parameter
  from: kvStore
  named: customers

CustomerExists:  (5)
  is: parameter
  from: constant
  eval: return Customers.containsKey(CustomerId)

responses:
  - when:
      request:
        - where: parameter
          named: CustomerId
          exists: false
    from: stub
    status: 400  (6)

  - when:
      request:
        - where: parameter
          named: CustomerExists
          equals: true
    from: stub
    status: 409  (7)

    # otherwise
  - from: template
    status: 201  (8)
    headers:
      - "Location: /api/v1/customers/${CustomerId}"  (9)
    body: <% Customers.put(CustomerId, Customer) %>  (10)
1 The simlet matches POST requests sent with the given path and having content-type header containing application/json to specify that the body of the request is in JSON.
2 The whole JSON body of the request becomes value for the Customer parameter.
3 The value of the "id" field in the JSON body becomes the value for the CustomerId parameter.
4 This is a parameter to access a KV Store named "customers". The same name - "customers" - shall be used in the simlets of this simulation that read, update, or delete elements representing Customers.
5 A boolean (true/false) flag whether Customer identified by the input CustomerId already exists in the Store or not.
6 Returns status 400 (Bad Request), if the request body doesn’t contain id field to identify the Customer.
7 Customer exists already so can’t create a duplicate. Here the simlet returns 409 (Conflict) but an API could be designed to return something else like, say, 422 (Unprocessable Entity).
8 Returns status 201 (Created) to denote the adding of a new Customer
9 RESTful APIs usually return in a Location header where the newly created resource can be accessed.
10 This is where the Customer data is actually put in the KV Store. Nothing is returned in the body - notice that storing the Customer in the KV Store is via a <%%> scriptlet and not ${} placeholder (which returns a value and the value is written out).

We would love to hear your feedback - send us a quick email to [feedback at APISimulator.com]

Happy API Simulating!