Skip to end of banner
Go to start of banner

[DRAFT] How to implement a simple CRUD API

Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

This article describes general guidance to implement a CRUD API for an RMB based module (mod-inventory-storage is used as an sample module). Usually, such API is created for so-called reference data entities - supporting data, that has a list of predefined values, but can be updated/added by institutions in future (e.g. instance statuses, holdings record sources, identifier types, patron groups, etc.).

The API development can be expressed in the following steps:

  1. Model API and domain objects.
  2. Declare the API in module descriptor.
  3. Define DB table for the new entity.
  4. Implement the API logic.
  5. Apply necessary DB constraints and indexes.
  6. Prepare integration tests for the new API.
  7. Create system predefined data for the API.
  8. Make sure the predefined data is loaded.

0. Example used description

For the guidance let's build CRUD API for instance sources. Instance source defines source of the instance - right now we have only MARC and FOLIO sources, but institutions can introduce some custom sources. An instance source usually have an id, name of the source and source - which defines who has created the record - either folio (it is a system source) or local - if it is added by a user, there is also the metadata property which is a system property that holds some technical info about when the record was created and updated, and the user is who has created and updated record.

1. API and domain objects modeling

FOLIO uses JSON schema to define structure of a domain object, this definition is converted to a java class(es) by a maven plugin automatically. RAML is used to define an API (request/response structure, query parameters, possible response statuses, endpoint names, etc.), it is also used to generate API documentation that is published here.

Usually, a CRUD API needs following endpoints:

  1. POST endpoint with entity as request - to create an entity, it has 
  2. GET by id endpoint;
  3. GET collection of entities by a CQL query supporting pagination;
  4. PUT endpoint - to update an entity by id;
  5. DELETE endpoint - to remove an entity by id.

We usually use following URI structure, within core func modules: /{domain-name}-storage/{domain-name} - e.g. /instance-sources-storage/instance-sources. In this case, the API endpoints for the instance-sources API will have following URIs:

  1. POST /instance-sources-storage/instance-sources
  2. GET /instance-sources-storage/instance-sources/{id}
  3. GET /instance-sources-storage/instance-sources?query=<a-query>&limit=<a-limit>&offset=<an-offset>
  4. PUT /instance-sources-storage/instance-sources/{id}
  5. DELETE /instance-sources-storage/instance-sources/{id}

Let's define JSON schema for the instance-source.

Instance sources
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "...",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "..."
    },
    "name": {
      "type": "string",
      "description": "..."
    },
    "source": {
      "type": "string",
      "description": "..."
    },
    "metadata": {
      "type": "object",
      "$ref": "raml-util/schemas/metadata.schema",
      "readonly": true
    }
  },
  "additionalProperties": false,
  "required": [
    "name",
    "source"
  ]
}


Please pay attention that the metadata property is read-only, there is also additionalProperties - it tells that no other properties are allowed, otherwise the validation is failing. We have added name and source to the required list - it means that validation is also fails if those properties are missing in request.

Create a collection schema for the domain - which will be used as response for get by CQL endpoint. You can use an existing domain as an example, the schema basically defines two properties - array of the domain objects and totalRecords - an integer saying how many records matches the criteria.

Next, we need to define RAML for the API.

holdings-sources API
#%RAML 1.0
title: Instance Sources API
version: v1.0
protocols: [ HTTP, HTTPS ]
baseUri: http://localhost

documentation:
  - title: Instance Sources API
    content: This documents the API calls that can be made to query and manage instance sources

types:
  instanceSource: !include instance-source.json #name of the file with domain schema
  instanceSources: !include instance-sources.json #name of the file for get by query response
  errors: !include raml-util/schemas/errors.schema

traits:
  pageable: !include raml-util/traits/pageable.raml
  searchable: !include raml-util/traits/searchable.raml
  language: !include raml-util/traits/language.raml
  validate: !include raml-util/traits/validation.raml

resourceTypes:
  collection: !include raml-util/rtypes/collection.raml
  collection-item: !include raml-util/rtypes/item-collection.raml

/instance-sources-storage/instance-sources: # the common part of the URI
  type:
    collection:
      exampleCollection: !include examples/<example-file-for-get-many-response>.json # replace with actual name 
      exampleItem: !include examples/<example-file-for-single-item>.json # replace with actual name of single item example
      schemaCollection: instanceSources # the one we defined in the traits section
      schemaItem: instanceSource # the one we defined in the traits section
  get:
    is: [
      searchable: {description: "with valid searchable fields", example: "name=aaa"},
      pageable
    ]
    description: Return a list of instance sources
  post:
    description: Create a new instance source
    is: [validate]
  /{id}:
    description: Pass in the instance source id
    type:
      collection-item: # This is predefined type, it will add GET by id, PUT by id, DELETE by id
        exampleItem: !include examples/<example-file-for-single-item>.json # replace with actual name
        schema: instanceSource #the type from traits section


As you can see, there are already some predefined 

  • No labels