Application Development with BeAPI
The Beapi Springboot Starter has several built-in tools to help you get started that will automate most of the development/build for you. Below is an explaination of those tools and how to use them.
The Build
Currently we are doing everything with GRADLE (if someone wants to contribute instructions for MAVEN, let us know).
dependencies {
...
implementation 'io.beapi:spring-boot-starter-beapi:0.7.0-SNAPSHOT'
...
}
The Controllers
When building a controller class, you require only two things : a @Controller annotation and to 'extend'' the BeapiRequestHandler class :
import io.beapi.api.controller.BeapiRequestHandler;
import org.springframework.stereotype.Controller;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller("your")
public class YourController extends BeapiRequestHandler {
...
}
Notice that the @Controller annotation has a value that matches the NAME in the corresponding I/O state file (which in turn matches how it is called). This is how we map the controller to the I/O state and mapping.
It should also be mentioned that the methods (like most controller methods) are using HttpServletRequest and HttpServletResponse so you will have to import those as well.
When returning data from your methods, you can return data as an:
- JPA ENTITY
- List / ArrayList / HashSet / Set : these will then need to be list of maps to be compliant with IO State
- Map / HashMap / LinkedHashMap
If you need examples, see the demo-application/src/main/java/demo/application/controller directory
IO State
An IO State File is RULES / DESCRIPTIONS for your API endpoints ASSOCIATED a CONTROLLER; It is a cacheable configuration file that can then be synchronized with all application servers.
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).
Examples of IO state files can be found in spring-boot-starter-beapi-config.
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": "user",
"HANDLER": "demo.application.controller.UserController",
"NETWORKGRP": "public",
"VALUES": {
"id": {
"key": "PKEY",
"type": "Long",
"description": "",
"mockData": "112"
},
"version": {
"type": "Long",
"description": "",
"mockData": "0"
}
...
We start by declaring the NAME of the file and our 'values':
- NAME (REQUIRED): This corresponds directly with the name of the controller(ie UserController); lowercase because that is how you will call it.
- NETWORKGRP (REQUIRED): 'networkGroup' is the group of 'roles' that can access it. See 'networkGroups' and 'networkRoles' in your beapi_api.yaml file for more
- VALUES (REQUIRED): These are the individual variables returned. These help us for automating testing, api docs and defining what needs to be sent/returned for each endpoint.
Underneath values, we then define each individual value to be sent/returned with the following stats:
- key (optional): One of [PKEY, FKEY]; this is used when the value is a ID(PKEY) or a reference to another table(FKEY)
- reference (optional): When key equals FKEY, we then supply the ENTITY name that 'reference'. You can see an example here.
- type (REQUIRED) : variable type. Merely a descriptor for the ApiDocs. Defines scope of variable for people implementing your API; be descriptive and accurate.
- description (REQUIRED) : description of variable, how it is used; mainly for apidocs
- mockData(REQUIRED) : string descriptor; Mock data for tests and apidoc.
Rules Declaration
Following this, we then define the 'rules' for our endpoints like so:
"CURRENTSTABLE": "1",
"VERSION": {
"1": {
"DEFAULTACTION":"list",
"URI": {
"create": {
"METHOD":"POST",
"DESCRIPTION":"Create new User",
"ROLES":{
"BATCH":["ROLE_ADMIN"],
"HOOK":["ROLE_ADMIN"]
},
"REQUEST": {
"permitAll":["username","password","email"]
},
"RESPONSE": {
"permitAll":["id","version"]
}
},
...
The above data has the following definitions:
- CURRENTSTABLE (REQUIRED): This is the current stable version of API's; if you you do not have multiple versions of this file, this number is the same as 'VERSION'
- VERSION (REQUIRED): This is the file version. This allows to API to have several versions of IOState if you are currently supported deprecated versions
- DEFAULTACTION (REQUIRED): 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 (REQUIRED): 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:'user' and URI:'create' point to the controller/method 'person.create', which would be called like so:
http://<yourdomain>:8080/v{applicationVersion}/user/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 (REQUIRED): One of [GET, PUT, POST,DELETE]. This defines how the endpoint should be called.
- DESCRIPTION (REQUIRED): A description of the endpoint for the API Docs
- ROLES (REQUIRED): A grouping for security rules (SEE 'Security: Network Roles')
- BATCH (REQUIRED if enabled): Security roles for endpoints that have batching enabled
- HOOK (REQUIRED if enabled): Security roles for endpoints that have web hooks enabled
- REQUEST (REQUIRED): Per role data for expected when sending the request; concatenates across roles
- RESPONSE (REQUIRED): 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 in the NetworkGrp 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.
Troubleshooting
- If you are having issues with your build, try running './gradlew --stop;./gradlew clean;./gradlew build --stacktrace --refresh-dependencies'. This will give you a fresh gradle instance, show your errors and refresh all dependencies.