workshop-rest-iam

Downloads18
Subscribe
745
Bookmark
1
This application is not supported by InterSystems Corporation. Please be notified that you use it at your own responsibility.
Details
Releases
Reviews
Issues
Articles

What's new in this version

IAM 2.3.3 added

Workshop: REST and InterSystems API Manager

This repository contains the materials and some examples you can use to learn the basic concepts of REST and IAM.

You can find more in-depth information in https://learning.intersystems.com.

What do you need to install?

Setup

Get InterSystems IRIS & IAM image

You need to setup your access to InterSystems Container Registry to download IRIS limited access images.

Have a look at this Introducing InterSystems Container Registry on Developer Community.

docker login -u="user" -p="token" containers.intersystems.com
  • Download images:
docker pull containers.intersystems.com/intersystems/iris:2021.1.0.215.0
docker pull containers.intersystems.com/intersystems/iam:2.3.3.2-1

IAM enabled IRIS license

IMPORTANT! Copy your InterSystems IRIS IAM enabled license file into the workshop root and rename it to iris.key.

Build the image

Build the image we will use during the workshop:

$ git clone https://github.com/intersystems-ib/workshop-rest-iam
$ cd workshop-rest-iam
$ docker-compose build

Examples

(a). Run containers and access IAM

  • Run the containers we will use in the workshop and check you access them:
docker-compose up

(b). OpenAPI specification

(c). Data classes and %JSON.Adaptor

  • The sample API we are developing will use two main persistent (table) classes that will hold data for us.
  • Have a look at Webinar.Data.Player and Webinar.Data.Team.
  • Notice that both classes inherit from %Persistent and %JSON.Adaptor.
  • If you are not familiar with %JSON.Adaptor and transforming objects to and from JSON, check this great article JSON Enhancements on Developer Community.
  • Check also the generated data through System Explorer > SQL.

(d). Generate API from OpenAPI specifications

  • Let's build the API implementation skeleton from the OpenAPI specification using ^%REST wizard.
  • Open a WebTerminal session using http://localhost:52773/terminal/ and type:
WEBINAR > do ^%REST 
REST Command Line Interface (CLI) helps you CREATE or DELETE a REST application.Enter an application name or (L)ist all REST applications (L): L 
Applications        Web Applications
------------        ----------------
Enter an application name or (L)ist all REST applications (L): Webinar.API.Leaderboard.v1
REST application not found: Webinar.API.Leaderboard.v1
Do you want to create a new REST application? Y or N (Y): Y
File path or absolute URL of a swagger document.
If no document specified, then create an empty application.
OpenAPI 2.0 swagger: /https://github.com/intersystems-ib/workshop-rest-iam/blob/master/shared/leaderboard-api-v1.json

OpenAPI 2.0 swagger document: /https://github.com/intersystems-ib/workshop-rest-iam/blob/master/shared/leaderboard-api-v1.json
Confirm operation, Y or N (Y): Y
-----Creating REST application: Webinar.API.Leaderboard.v1-----
CREATE Webinar.API.Leaderboard.v1.spec
GENERATE Webinar.API.Leaderboard.v1.disp
CREATE Webinar.API.Leaderboard.v1.impl
REST application successfully created.

Create a web application for the REST application? Y or N (Y): Y
Specify web application name. Default is /csp/Webinar/API/Leaderboard/v1
Web application name: /leaderboard/api/v1

-----Deploying REST application: Webinar.API.Leaderboard.v1-----
Application Webinar.API.Leaderboard.v1 deployed to /leaderboard/api/v1

(e). Implement REST API methods

  • Using VS Code, complete the code of the following methods in Webinar.API.Leaderboard.v1.impl.

addPlayer

ClassMethod addPlayer(body As %DynamicObject) As %DynamicObject
{
    set player = ##class(Webinar.Data.Player).%New()
    do player.%JSONImport(body)
    set sc = player.%Save()
    if $$$ISERR(sc) {
    	do ..%SetStatusCode(405)
    	quit ""
    }
    do player.%JSONExportToStream(.stream)
    quit stream
}

getPlayers

ClassMethod getPlayers() As %DynamicObject
{
    set sql = "SELECT Id, Name, Alias FROM Webinar_Data.Player order by Score"
    set statement = ##class(%SQL.Statement).%New()
    set sc = statement.%Prepare(sql)
    set rs = statement.%Execute()
<span class="pl-k">set</span> <span class="pl-v">array</span> = []
<span class="pl-k">while</span> <span class="pl-v">rs</span>.<span class="pl-e">%Next</span>() {
	<span class="pl-k">do</span> <span class="pl-v">array</span>.<span class="pl-e">%Push</span>( 
		{
			<span class="pl-s"><span class="pl-pds">"</span>Id<span class="pl-pds">"</span></span>: (<span class="pl-v">rs</span>.<span class="pl-e">%Get</span>(<span class="pl-s"><span class="pl-pds">"</span>Id<span class="pl-pds">"</span></span>)),
			<span class="pl-s"><span class="pl-pds">"</span>Name<span class="pl-pds">"</span></span>: (<span class="pl-v">rs</span>.<span class="pl-e">%Get</span>(<span class="pl-s"><span class="pl-pds">"</span>Name<span class="pl-pds">"</span></span>)),
			<span class="pl-s"><span class="pl-pds">"</span>Alias<span class="pl-pds">"</span></span>: (<span class="pl-v">rs</span>.<span class="pl-e">%Get</span>(<span class="pl-s"><span class="pl-pds">"</span>Alias<span class="pl-pds">"</span></span>)),
			<span class="pl-s"><span class="pl-pds">"</span>Node<span class="pl-pds">"</span></span>: (<span class="pl-en">$system</span>.<span class="pl-e">INetInfo</span>.<span class="pl-e">LocalHostName</span>())
		})
}

<span class="pl-k">quit</span> <span class="pl-v">array</span>

}

getPlayerById

ClassMethod getPlayerById(playerId As %Integer) As %DynamicObject
{
    set player = ##class(Webinar.Data.Player).%OpenId(playerId)
    if '$isobject(player) {
    	do ..%SetStatusCode(404)
    	quit ""
    }
    do player.%JSONExportToStream(.stream)
    quit stream
}

updatePlayer

ClassMethod updatePlayer(playerId As %Integer, body As %DynamicObject) As %DynamicObject
{
    set player = ##class(Webinar.Data.Player).%OpenId(playerId)
    if '$isobject(player) {
    	do ..%SetStatusCode(404)
    	quit ""
    }
    do player.%JSONImport(body)
    do player.%Save()
    do player.%JSONExportToStream(.stream)
    quit stream
}

deletePlayer

ClassMethod deletePlayer(playerId As %Integer) As %DynamicObject
{
    set sc = ##class(Webinar.Data.Player).%DeleteId(playerId)
    if $$$ISERR(sc) {
    	do ..%SetStatusCode(404)
    }
    quit ""
}

(f). Test the API

(g). Make your REST API part of an interoperability production (optional)

You'll plug the API implementation into a production, so you could use any feature of interoperability productions withing a REST API implementation.

Create production and add a dummy BO

  • Using the Management Portal Interoperability > List > Productions > New, create a new production called Webinar.Production.
  • Click on Production Settings and in Settings tab make sure Testing enabled is checked.
  • Let's create a simple Business Operation that you will use to request some information you will include in your REST API response. This component sends a REST message to an external dummy service and returns a value. Have a look at the source code https://github.com/intersystems-ib/workshop-rest-iam/blob/master/src/Webinar/BO/DummyREST.cls
  • Add a new Business Operation in your production, choose Webinar.BO.DummyREST. Then, in the Settings tab configure:
    • HTTP Server: mockbin.com
    • URL: /request
  • Test the Business Operation in the Actions tab clicking on the Test button.

Make your REST API a Business Service

ClassMethod getPlayerById(playerId As %Integer) As %DynamicObject
{
    set player = ##class(Webinar.Data.Player).%OpenId(playerId)
    if '$isobject(player) {
    	do ..%SetStatusCode(404)
    	quit ""
    }

// instantiate Business Service (interoperability framework)
set sc = ##class(Ens.Director).CreateBusinessService("Webinar.API.Leaderboard.v1.impl",.service)
$$$ThrowOnError(sc)

// build request message
set req = ##class(Ens.StringContainer).%New()
set req.StringValue = playerId

// send message to Business Operation
set sc = service.SendRequestSync("Webinar.BO.DummyREST", req, .rsp)
$$$ThrowOnError(sc)

// concatenate Business Operation response to REST API response
set player.Name = player.Name"("rsp.StringValue_")"

<span class="pl-k">do</span> <span class="pl-v">player</span>.<span class="pl-e">%JSONExportToStream</span>(.<span class="pl-e">stream</span>)
<span class="pl-k">quit</span> <span class="pl-v">stream</span>

}

(h). API Manager: Basic Scenario

Now, you will build a basic scenario to manage the REST API in InterSystems API Manager (IAM).

Remember IAM can be managed using the UI or using the REST interface.

Tip: open a VS Code Terminal session and type the following so you can send curl commands to IAM.

docker exec -it tools sh

Add API to API Manager

  • Add a service to which will invoke the API in IRIS.
curl -X POST --url http://iam:8001/services/ \
--data 'name=iris-leaderboard-v1-service' \
--data 'url=http://irisA:52773/leaderboard/api/v1' | jq
  • Add a route that will give access to the service you have just created.
curl -X POST --url http://iam:8001/services/iris-leaderboard-v1-service/routes \
--data 'paths[]=/leaderboard' | jq
  • In Postman, test the IAM - Get Player - No auth request.
  • Add Authentication by setting up the key-auth plugin in the service.
curl -X POST http://iam:8001/services/iris-leaderboard-v1-service/plugins \
--data "name=key-auth" | jq
  • In Postman, test again the IAM - Get Player - No auth request.

Consumers

  • Create some consumers so you can authenticate to access the API.
  • Create consumer systemA
curl -d "username=systemA&custom_id=SYSTEM_A" http://iam:8001/consumers/ | jq
  • Create secret for `systemA``
curl -X POST http://iam:8001/consumers/systemA/key-auth -d 'key=systemAsecret' | jq
  • In Postman, test IAM - GET Player. Consumer SystemA request.
  • Create another consumer called webapp
curl -d "username=webapp&custom_id=WEB_APP" http://iam:8001/consumers/ | jq
  • Create secret for webapp
curl -X POST http://iam:8001/consumers/webapp/key-auth -d 'key=webappsecret' | jq
  • In Postman, test IAM - GET Players - Consumer WebApp request.

Rate Limiting

/https://github.com/intersystems-ib/workshop-rest-iam/blob/master/shared/simulate.sh
  • Add a restriction for webapp consumer. Limit it to 100 requests in a minute.
curl -X POST http://iam:8001/consumers/webapp/plugins \
    --data "name=rate-limiting" \
    --data "config.minute=100" | jq
  • Remove the restriction using the IAM Portal so you can continue.

Developer Portal

  • Set up the Developer Portal in IAM so developers could sign up automatically.
  • Go to IAM Portal and Dev Portal > Settings:
  • Set Authentication Plugin=Basic
  • Set Auto Approve Access=Enable
  • Set Session Config (JSON)=Custom and enter:
{
    "cookie_name": "portal_session",
    "secret": "CHANGE_THIS",
    "storage": "kong",
    "cookie_secure": false
}
  • Save Changes
  • Publish the OpenAPI specs of the REST API you have just built in IAM Portal and Dev Portal > Editor
  • Click on New File + and set File Type=spec and File Path=leaderboard.yaml.
  • Copy the content of leaderboard-api-v1.yaml.

API credentials and developers

  • Go to the Developer Portal and click Sign Up.
  • Logged as a developer, create your own API credential in Create API Credential.
  • In Postman, test IAM - Get Players - Developer replacing the api-key header by the actual credential you have just created.
  • Access the APIs documentation in Documentation.

Auditing

  • There are different ways of exposing the audit logs. For instance, you can configure a global http log plugin to push logs to your remote audit interface.
  • In this case you can use a very simple REST audit interface that will audit IAM requests into shared/audit.json file.
curl -X POST http://iam:8001/plugins/ \
    --data "name=http-log" \
    --data "config.http_endpoint=http://irisA:52773/audit/log" \
    | jq
  • Try again some IAM requests in Postman and check the audit file.

(i). API Manager: Load Balancing Scenario

You will build a load balancing scenario between two IRIS instances with the leaderboard REST API.

This can be useful in case you want to spread the workload, blue-green deployment, etc.

Tip: open a VS Code Terminal session and type the following so you can send curl commands to IAM.

docker exec -it tools sh

  • Create an upstream
curl -s -X POST http://iam:8001/upstreams \
    -d name=leaderboard-lb-stream \
    | jq
  • Add the two IRIS instances targets to upstream
curl -s -X POST http://iam:8001/upstreams/leaderboard-lb-stream/targets \
    -d target=irisA:52773 \
    -d weight=500 \
    | jq
curl -s -X POST http://iam:8001/upstreams/leaderboard-lb-stream/targets \
    -d target=irisB:52773 \
    -d weight=500 \
    | jq
  • Add a service referencing the upstream
curl -s -X POST http://iam:8001/services/ \
    --data 'name=leaderboard-lb' \
    --data 'host=leaderboard-lb-stream' \
    --data 'path=/leaderboard/api/v1' \
    | jq
  • Add a route to access the service
curl -s -X POST http://iam:8001/services/leaderboard-lb/routes \
    --data 'paths[]=/leaderboard-lb' \
    | jq
  • In Postman, test the IAM - GET Players - LB request. Pay attention to the Node property in the response body.

(j). API Manager: Route by Header Scenario

You will now build a route by header scenario using three IRIS instances with the leaderboard REST API.

This could be useful in case you want use different servers depending on request headers (e.g. different versions).

Tip: open a VS Code Terminal session and type the following so you can send curl commands to IAM.

docker exec -it tools sh

  • Create Default, V1 and V2 upstreams
curl -s -X POST http://iam:8001/upstreams \
    -d name=leaderboard-header-stream \
    | jq
curl -s -X POST http://iam:8001/upstreams \
    -d name=leaderboard-header-v1-stream \
    | jq
curl -s -X POST http://iam:8001/upstreams \
    -d name=leaderboard-header-v2-stream \
    | jq
  • Add targets to each IRIS instance
curl -s -X POST http://iam:8001/upstreams/leaderboard-header-stream/targets \
    -d target=irisA:52773 \
    | jq
curl -s -X POST http://iam:8001/upstreams/leaderboard-header-v1-stream/targets \
    -d target=irisB:52773 \
    | jq
curl -s -X POST http://iam:8001/upstreams/leaderboard-header-v2-stream/targets \
    -d target=irisC:52773 \
    | jq
  • Add a service referencing the default upstream:
curl -s -X POST http://iam:8001/services/ \
    --data 'name=leaderboard-header' \
    --data 'host=leaderboard-header-stream' \
    --data 'path=/leaderboard/api/v1' \
    | jq
  • Add a route to access your service:
curl -s -X POST http://iam:8001/services/leaderboard-header/routes \
    --data 'paths[]=/leaderboard-header' \
    | jq
  • Add route-by-header plugin with some conditions on request header version:
curl -s -X POST http://iam:8001/services/leaderboard-header/plugins \
    -H 'Content-Type: application/json' \
    -d '{"name": "route-by-header", "config": {"rules":[{"condition": {"version":"v1"}, "upstream_name": "leaderboard-header-v1-stream"}, {"condition": {"version":"v2"}, "upstream_name": "leaderboard-header-v2-stream"}]}}' \
    | jq
  • In Postman, try the IAM - GET Players - Route By Header using different version header request values.

Explore other scenarios

Have a look at this example where you can see in action a REST API in IRIS as backend for an Angular application: https://github.com/intersystems-ib/iris-sample-rest-angular

Rating
0 (0)
Category
Technology Example
Works with
InterSystems IRISInterSystems IRIS for Health
Tags
Info
Version
1.0.1
Last updated
2021-06-14
Repository
Open
Documentation
Open
License
Link