La mayoría de los proyectos en los que participamos en mi trabajo requieren diseñar e implementar APIs de acceso para las aplicaciones móviles y webapps. Este diseño requiere definir un sistema de autenticación (por ejemplo uno basado en tokens como puede ser JWT) y cada uno de los endpoints que serán accesibles. En esta definición de endpoints debemos decidir que campos de entrada y de salida tendrá cada uno.
Para diseñar una buena API se requiere emplear los verbos HTTP adecuadamente utilizando RESTFul, pero también debemos de implementar un diseño intuitivo, una buena documentación y que ésta represente fielmente a la implementación. A parte de que la implementación sea buena, es muy importante que la documentación asociada a ella sea completa y esté actualizada al 100% teniendo controlado el comportamiento esperado por cada request. En mi opinión, mantener este compromiso suele ser bastante complicado pero utilizando las herramientas adecuadas se puede simplificar y beneficiar al equipo de desarrollo, y a los futuros desarrolladores que se puedan encargar del desarrollo software.
¿Que es RAML y por qué usarlo en nuestras APIs?
RAML es el acrónimo de “Restful Api Modeling Language” y se trata de un lenguaje de modelado para definir apis REST con una sintaxis bastante sencilla y fácilmente comprensibles tanto para seres humanos como para sistemas software. Este lenguaje permite definir recursos, métodos, parámetros, respuestas, tipos de medios y otros componentes HTTP básicos. Básicamente es una especificación no propietaria y totalmente independiente basada en YAML y JSON. En otras palabras, lo que nos permite es escribir la especificación de las apis siguiendo un estándar.
La gran ventaja de implementar API con RAML es que te centras totalmente en el “contrato” que ofrece el endpoint; esto permite comenzar generando la documentación, para la cual una vez esté lista, existen distintos generadores que nos generan el “scaffolding” básico del servicio e incluso servicios que nos devuelvan respuestas simuladas para arrancar con nuestro testing.
Esta metodología favorece el proceso de testing aportándonos el entorno perfecto para usar TDD. Básicamente, definimos la api, escribimos tests para consumir esa api y empezamos a construir la implementación real y necesaria para validar tanto los tests como la especificación descrita.
¿Como escribir RAML?
Puedes escribirlo tan solo con un fichero normal con la extensión .raml y procesarlo con distintas herramientas. Personalmente usamos una herramienta oficial de Mulesoft llamada Api Designer (https://github.com/mulesoft/api-designer). Esta herramienta es un editor construido con angularjs y que puedes instalar a través de NPM en tu ordenador.
npm install -g api-designer
Ejemplo de diseño en RAML
#%RAML 0.8title: examle
version:1.0baseUri: http://example.local/api
traits:
- secured:
description: Some requests require authentication
headers:
X-User:
description: Token to authenticate the user
required: true
- unsecured:
description: This is not secured
- pageable:
queryParameters: offset: description: Skip over a number of elements by specifying an offset value for the query
type: integer
required: false
example:20 default:0 limit: description: Limit the number of elements on the response
type: integer
required: false
example:80 default:10
- unauthorized:
responses:401:
description: User Not Authorized, token invalid
body:
application/json:
schema: |
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"description": "User not authorized",
"properties": {
"error": {
"description": "The error definition",
"type": "string"
}
},
"required": ["error"]
}
example: |
{
"code": 401,
"error": "Not Authorized"
}
- badRequest:
responses:400:
description: Bad Request
body:
application/json:
example: |
{
"code": 400,
"error": "Bad Request"
}
- internalError:
responses:500:
description: Internal Server Error
body:
application/json:
schema: |
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"description": "Internal Server Error",
"properties": {
"error": {
"description": "The error definition",
"type": "string"
}
},
"required": ["error"]
}
example: |
{
"error": "Internal Server Error"
}
- missingParams:
responses:400:
description: Bad Request, parameters missing
body:
application/json:
schema: |
{
"$schema": "http://json-schema.org/schema#",
"type": "object",
"description": "Parametter Missing",
"properties": {
"error": {
"description": "The error definition",
"type": "string"
}
},
"required": ["error"]
}
example: |
{
"error": "Parametter Missing"
}
/callback/order:
/sim:
displayName: postSimOrderAction
description: Set New Sim from External Provider
is: [ unauthorized, internalError]
post: is: [ unauthorized, internalError]
description:" Set New Sim from External Provider" body:
application/json:
schema: callbacksim
example: |
{
"trackingId": "dsn16zueuw",
"iccid": "89341231321312",
"checksum": "4d4fdcca079a2c98ef327f597b6cadfe8731feec94d8d2aabfcfe3db66d98410ca0201fc3df419a7534843f03fcbefe3afe4d9917f37719863254b45ae71fdf1"
}
responses:200:
body:
application/json:
example: |
{
"result": "OK",
}
/contract:
displayName: postSignContractOrderAction
description: Set ContractSign from External Provider
is: [ unauthorized, internalError]
post: is: [ unauthorized, internalError]
description:" Set New Sim from External Provider" body:
application/json:
schema: callbacksigncontract
example: |
{
"trackingId": "dsn16zueuw",
"date": "16/02/2016 15:23",
"checksum": "4d4fdcca079a2c98ef327f597b6cadfe8731feec94d8d2aabfcfe3db66d98410ca0201fc3df419a7534843f03fcbefe3afe4d9917f37719863254b45ae71fdf1"
}
responses:200:
body:
application/json:
example: |
{
"result": "OK",
}