Advanced Configuration
Below you will find some additional techniques and methods for tweaking your BeAPI implementation.
Versioning
The version for your api is sent through the URL but is set in two different places. Let's first break apart the URL version:
http://localhost:8080/v1.0-1/person/create
In the above example, the version is v1.0-1. This is broken out into three parts:
- [v]1.0-1: The 'v' determines what kind of call it is whether it is a batch call(b), a chained call(c) or a regular api call(v)
- v[1.0]-1: This is the version of the application (as set by the application); this helps us to run multiple versions of the application should we need to.
- v1.0[-1]: This is the version of the IO State. It always defaults to '1' if this is not set (which it doesn't need to be). This allows you to have multiple version of your api rules running should you need to support older versions while running newer versions on the same application. Supports up 1-9 versions on any existing application for any IO State file
Calling Without Version
This is the most common call you will make... which is without any particular version (to the default version in the IO State). This would look like the following:
http://localhost:8080/v1.0/person/create
Calling With Version
If you have a particular version you wish to call (and you know it is supported via documentation), simply call that version of the endpoint like so:
http://localhost:8080/v1.0-1/person/create
Calling With App Version
If you are running separate application versions and are properly routing based on app version, simply call the different app version like so:
http://localhost:8080/v0.5/person/create
... or like so:
http://localhost:8080/v0.5-1/person/create
IO State
The IO State File is a descriptor for your API endpoints. It is a cacheable configuration file holding data directly associated with a specific group of API requests & responses. This enables all URI's (and their associated data) to be easily cached and this data to be shared with services in the architecture (ie Proxy, MQ, etc).
URI's call a common set of data as they are based on classes/method. As such, these URIs can be GROUPED and said to share a common dataset and set of config options. The IO State File is a set of rules for all URI's sharing a common data object.
Examples of IO state files can be found in the example project here.
To bootstrap your IOState from your domains, simply go into the the root directory of your project and type the following command from the shell:
./gradlew GenerateIostate
This will generate IO state files based on all your domains/models. A typical IO State File looks like the following:
/* JSON API Object */
{
"NAME":"person",
"VALUES": {
"id": {
"key": "PRIMARY",
"type": "Long",
"description":"ID for Event",
"mockData":"1"
},
"version": {
"type": "Long",
"description":"Version for Event",
"mockData":"0"
},
"username":{
"type": "String",
"description":"User Name",
"mockData":"guest"
},
"password":{
"type": "String",
"description":"password",
"mockData":"password"
},
"email":{
"type": "String",
"description":"Account Email",
"mockData":"test@mockdata.com"
},
"oauthProvider":{
"type": "String",
"description":"Oauth Provider (if available)",
"mockData":"GMail"
},
"avatarUrl":{
"type": "String",
"description":"Event Type Name",
"mockData":"https://en.gravatar.com/userimage/38053926/62165e36e1f429564a78e82db5b72591.jpg?size=200"
},
"enabled":{
"type": "Boolean",
"description":"Is Account Enabled",
"mockData":"true"
},
"accountExpired":{
"type": "Boolean",
"description":"Is Account Expired",
"mockData":"false"
},
"accountLocked":{
"type": "Boolean",
"description":"Is Account Locked",
"mockData":"false"
},
"passwordExpired":{
"type": "Boolean",
"description":"Is Password Expired",
"mockData":"false"
}
},
"CURRENTSTABLE": "1",
"VERSION": {
"1": {
"DEFAULTACTION":"list",
"URI": {
"create": {
"METHOD":"POST",
"DESCRIPTION":"Create new Person",
"ROLES":{
"DEFAULT":["ROLE_ADMIN","ROLE_USER"],
"BATCH":["ROLE_ADMIN"],
"HOOK":["ROLE_ADMIN"]
},
"REQUEST": {
"permitAll":["username","password","email"]
},
"RESPONSE": {
"permitAll":["id","version"]
}
},
"show": {
"METHOD":"GET",
"DESCRIPTION":"Get Person by ID",
"ROLES":{
"DEFAULT":["ROLE_ADMIN","ROLE_USER"],
"BATCH":["ROLE_ADMIN"],
"HOOK":["ROLE_ADMIN"]
},
"REQUEST": {
"permitAll":[],
"ROLE_ADMIN":["id"],
},
"RESPONSE": {
"permitAll":["id","version","username","email","enabled","accountExpired"]
}
},
"list": {
"METHOD":"GET",
"DESCRIPTION":"List all Event Types",
"ROLES":{
"DEFAULT":["ROLE_ADMIN"],
"BATCH":["ROLE_ADMIN"],
"HOOK":["ROLE_ADMIN"]
},
"REQUEST": {
"permitAll":[]
},
"RESPONSE": {
"permitAll":["id","version","username","email","enabled","accountExpired"]
}
},
"delete": {
"METHOD":"DELETE",
"DESCRIPTION":"Delete Person",
"ROLES":{
"DEFAULT":["ROLE_ADMIN"],
"BATCH":["ROLE_ADMIN"],
"HOOK":["ROLE_ADMIN"]
},
"REQUEST": {
"permitAll":["id"]
},
"RESPONSE": {
"permitAll":["id"]
}
}
}
}
}
}
Variable Declaration
Lets break apart the pieces of an IOState file to see what they consist of and how they work; the first part of the file declares 'VALUES' for use by the endpoints:
"NAME":"person",
"NETWORKGRP": "public",
"VALUES": {
"id": {
"key": "PRIMARY",
"type": "Long",
"description":"ID for Event",
"mockData":"1"
},
"version": {
"type": "Long",
"description":"Version for Event",
"mockData":"0"
},
...
We start by declaring the NAME of the file and our 'values':
- NAME: This corresponds directly with the name of the controller; the controller typically pulls in from the same domain name but does not have to. The controller can create a nested call, return a file, etc. So the name directly relates to the controller.
- NETWORKGRP: This defines the 'networkGroup' that this group of endpoints can be accessed through thus defining the CORS allowed origins and the ROLES that can access it. See Security section for more
- VALUES: These are the individual variables returned. These help us for automating testing, api docs and defining what needs to be sent and returned in each endpoint.
Underneath values, then define each individual value to be sent/returned with the following stats:
- type (required) : One of [PKEY, FKEY, INDEX, STRING, DATE, LONG, BOOLEAN, FLOAT, BIGDECIMAL, EMAIL, URL, ARRAY, COMPOSITE]. Defines scope of variable; only used for apidocs/options or for reference.
- description (required) : description of variable, how it is used; mainly for apidocs
- references (optional) : This allows someone to point a PKEY/FKEY/Index to its referencing IO State Object NAME. Usage is as follows:
- references: self (type:Long): primary key, reference to self;
- references: <name of API Object referenced w/ type:Long> (type:Long): foreign key; referenced to IO State Object
- references: <name of API Object referenced w/ type:String> (type:String): index;reference to IO State Object
- mockData(required) : string descriptor; Mock data for tests and apidoc.
Rules Declaration
Following this, we then define the 'rules' for our endpoints in the IOState like so:
"CURRENTSTABLE": "1",
"VERSION": {
"1": {
"DEFAULTACTION":"list",
"URI": {
"create": {
"METHOD":"POST",
"DESCRIPTION":"Create new Person",
"ROLES":{
"DEFAULT":["ROLE_ADMIN","ROLE_USER"],
"BATCH":["ROLE_ADMIN"],
"HOOK":["ROLE_ADMIN"]
},
"REQUEST": {
"permitAll":["username","password","email"]
},
"RESPONSE": {
"permitAll":["id","version"]
}
},
...
The above data has the following definitions:
- CURRENTSTABLE: This is the current stable version of IOState; if you you do not have multiple versions of this file, this number is the same as 'VERSION'
- VERSION: This is the file version. This allows to API to have several versions of IOState if you are currently supported deprecated versions
- DEFAULTACTION: This is the default endpoint action if if none given; commonly 'list'
- DEPRECATED: (OPTIONAL) Formated a MM/DD/YYYY, this field represents when this api version goes 'stale' and can no longer be used
- URI: This is the endpoint and the rules for the endpoint.
Then under 'URI', you define the rules for the endpoint for the controller/method. In this case for the NAME 'person' and URI 'create' point to the controller method 'person.create', which would be called like so:
http://<yourdomain>:8080/v1.0/person/create
We will explain more about where the 'v1.0' comes under 'Versioning' but for right now, lets finish explaining the rest of the IOState:
- METHOD: One of [GET, PUT, POST,DELETE]. This defines how the endpoint should be called.
- DESCRIPTION: A description of the endpoint for the API Docs
- ROLES: A grouping for security rules (SEE 'Security: Network Roles')
- BATCH: Security roles for endpoints that have batching enabled
- HOOK: Security roles for endpoints that have web hooks enabled
- REQUEST: Per role data for expected when sending the request; concatenates across roles
- RESPONSE: Per role data for expected when sending the request; concatenates across roles
Of note on REQUEST/RESPONSE and ROLES: one can set different access levels by concatenating privileges. Their is a default privilege level called 'permitAll' which will allow ALL privileges set in ROLES>DEFAULT access to this data. You can then allow more atomic access by setting additional values PER ROLE like so:
"REQUEST": {
"permitAll":["name"],
"ROLE_USER":["id"]
},
Empty PermitAll
If you are sending no values (such as a 'list' call) or returning no values, you will often have an empty 'permitAll' such as the following:
"REQUEST": {
"permitAll":[]
},
An empty 'permitAll' should NEVER be declared with quotes as this sends an empty string. Always leave the brackets empty like above to declare that nothing needs to be sent.