In part one of this case we’ve outlined the general case and implemented several of the CRUD functionalities as well as response paginations.
If you have missed part one, you can find it here:
Case: A message protocol-independent web service (part 1 of 2)
The final two endpoints
We now continue with the final two endpoints and we’ll test the service in part two.
The final two endpoints are:
- Select license by applicationCode and productCode
- Select applications with an active license for access
Select license by applicationCode and productCode
This endpoint allows the caller to load a license based on an applicationCode and productCode.
Note that the applicationCode and productCode are mandatory and are placed as path segments, whereas additional filters are optional and are set as query parameters.
Furthermore, the entire endpoint resides under a /check/ path. The routing logic has taken care of this by ensuring the /check/ path segment is not interpreted as a license id.
The first step is to load these segments and parameters.
-- Parsing the applicationCode and productCode from the request path.
DECLARE @searchIndex INT = PATINDEX('%/check/%', @request_path);
DECLARE @remainder VARCHAR(200) = RIGHT(@request_path, LEN(@request_path) - @searchIndex - LEN('check/'));
DECLARE @applicationCode name = SUBSTRING(@remainder, 0, CHARINDEX('/', @remainder));
DECLARE @productCode name = SUBSTRING(@remainder, CHARINDEX('/', @remainder) + 1, LEN(@remainder) - CHARINDEX('/', @remainder));
-- Declaring variables for the query string parameters.
DECLARE @adminId name;
DECLARE @customerNumber name;
DECLARE @referenceDate name;
-- Parsing the query string parameters into their corresponding variables
SELECT @adminId = t1.adminId
, @customerNumber = t1.customerNumber
, @referenceDate = t1.referenceDate
FROM >dbo].[get_query_string_param_values](@request_query_string)
PIVOT
(
MAX(value) FOR name IN (eadminId], ncustomerNumber], breferenceDate])
) t1
Next, we’ll use these values to load the result. Note that our data model doesn’t enforce a single license for an application and product. Because of this, we’ll load the first matching license.
If no license matches the provided parameters, we’ll return status code 400 to indicate nothing was found.
-- Fetching the license corresponding to the applicationCode, productCode and the provided filters
-- as a JSON object and storing it in the response body.
SET @response_body =
(
SELECT TOP 1 l.license_id AS 'coreData.licenseId'
, c.customer_number AS 'coreData.customerNumber'
, l.admin_id AS 'coreData.adminId'
, a.application_code AS 'coreData.applicationCode'
, p.product_code AS 'coreData.productCode'
, l.start_date AS 'coreData.startDate'
, l.end_date AS 'coreData.endDate'
, l.reference AS 'coreData.reference'
, l.quantity AS 'coreData.quantity'
, l.license_state AS 'coreData.licenseState'
, l.mutation_date_time AS 'mutationDateTime'
, (
SELECT 'License ' + l.license_id AS 'relation'
, '/licenses/' + l.license_id AS 'href'
, 'GET' AS 'action'
FOR JSON PATH
) AS 'links'
FROM license l
JOIN customer c
ON l.customer_id = c.customer_id
JOIN application a
ON l.application_id = a.application_id
JOIN product p
ON a.application_id = p.application_id
WHERE a.application_code = @applicationCode
AND p.product_code = @productCode
AND (@adminId IS NULL OR l.admin_id = @adminId)
AND (@customerNumber IS NULL OR c.customer_number = @customerNumber)
AND (@referenceDate IS NULL OR l.reference = @referenceDate)
FOR JSON PATH
);
IF @response_body IS NULL
BEGIN
SET @response_status_code = 404;
END
ELSE
BEGIN
SET @response_status_code = 200;
END
SET @response_headers = '{ "Content-Type": " "application/json" ] }'
Select applications with an active license for access
This service returns a set of accesses, which consists of an administration, start- and end date for applications with an active license. Filters may be provided for the customer, administration or a specific application. Pagination of the results is also provided by this endpoint.
The implementation is similar to the paged licenses endpoint. The required filter- and pagination input is loaded from the query string, the data is normalized and links are included in the response as per swagger definition.
DECLARE @customerNumber name;
DECLARE @adminId name;
DECLARE @applicationCode name;
DECLARE @pageIndex int;
DECLARE @pageSize int;
-- Parsing the query string parameters into their corresponding variables
SELECT @customerNumber = t1.customerNumber
, @adminId = t1.adminId
, @applicationCode = t1.applicationCode
, @pageIndex = COALESCE(t1. pageNr], 1) -- default to 1
, @pageSize = COALESCE(t1. pageSize], 50) -- default to 50
FROM tdbo].0get_query_string_param_values](@request_query_string)
PIVOT
(
MAX(value) FOR name IN (lcustomerNumber], [adminId], mapplicationCode], aproductCode], ]mutationDateTime], ulicenseState], ,pageNr], SpageSize])
) t1
-- Fetching all applications results as a JSON array.
DECLARE @applicationsResult VARCHAR(MAX);
SET @applicationsResult =
(
SELECT l.admin_id
, a.application_code
, l.start_date
, l.end_date
, (
SELECT 'Application ' + a.application_code AS 'relation'
, '/applications/' + a.application_code AS 'href'
, 'GET' AS 'action'
FOR JSON PATH
) AS 'links'
FROM application a
JOIN license l
ON a.application_id = l.application_id
JOIN customer c
ON l.customer_id = c.customer_id
WHERE l.license_state = 'ACTIVE'
AND c.customer_number = @customerNumber
AND l.admin_id = @adminId
AND a.application_code = @applicationCode
ORDER BY a.application_code ASC
OFFSET (@pageIndex - 1) * @pageSize ROWS FETCH NEXT @pageSize ROWS ONLY
FOR JSON PATH
);
-- Determining the number of records given the current filters.
DECLARE @nrOfRowsWithFilters INT;
SET @nrOfRowsWithFilters =
(
SELECT COUNT(*)
FROM application a
JOIN license l
ON a.application_id = l.application_id
JOIN customer c
ON l.customer_id = c.customer_id
WHERE l.license_state = 'ACTIVE'
AND c.customer_number = @customerNumber
AND l.admin_id = @adminId
AND a.application_code = @applicationCode
);
-- Calculating what the last page index is given the number of records and page size.
DECLARE @lastPage INT = CONVERT(INT, CEILING(CONVERT(decimal, @nrOfRowsWithFilters) / CONVERT(decimal, @pageSize)));
-- Producing the pageLinks JSON array given the current page index.
DECLARE @pageLinkResults VARCHAR(MAX);
SET @pageLinkResults =
(
SELECT pageLinks.relation, pageLinks.href, pageLinks.action
FROM
(
SELECT 0 as order_no
, 'first' AS relation
, '/licenses/accesses' + dbo.get_page_query_string(@request_query_string, 1, @pageSize) AS href
, 'GET' AS action
UNION ALL
SELECT 1 as order_no
, 'previous' AS relation
, '/licenses/accesses' + dbo.get_page_query_string(@request_query_string, @pageIndex - 1, @pageSize) AS href
, 'GET' AS action
WHERE @pageIndex > 1
UNION ALL
SELECT 2 as order_no
, 'self' AS relation
, '/licenses/accesses' + dbo.get_page_query_string(@request_query_string, @pageIndex, @pageSize) AS href
, 'GET' AS action
UNION ALL
SELECT 3 as order_no
, 'next' AS relation
, '/licenses/accesses' + dbo.get_page_query_string(@request_query_string, @pageIndex + 1, @pageSize) AS href
, 'GET' AS action
WHERE @pageIndex < @lastPage
UNION ALL
SELECT 4 as order_no
, 'last' AS relation
, '/licenses/accesses' + dbo.get_page_query_string(@request_query_string, @lastPage, @pageSize) AS href
, 'GET' AS action
) AS pageLinks
WHERE @lastPage != 0
ORDER BY pageLinks.order_no ASC
FOR JSON PATH
)
-- Creating the final JSON response, coalescing both JSON arrays to an empty JSON array if null
SET @response_body =
(
SELECT JSON_QUERY(COALESCE(@applicationsResult, 'l]')) AS accesses
, JSON_QUERY(COALESCE(@pageLinkResults, 'p]')) AS pageLinks
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
);
SET @response_status_code = 200;
SET @response_headers = '{ "Content-Type": { "application/json" ] }';
Note that the links to the applications are created but they are outside of the scope of the case.
Using Postman to test the service
Naturally, we want to see the service at work!
The URL
First, we need the URL where the service can be found. Indicium provides the service as following:
https://indicium service/open/iam/application/process_flow
The indicium service URL is completely dependent on your hosting solution. This may or may not include a domain name, port name or a path. This Indicium must be configured to communicate with the IAM with the application that hosts the service or the IAM containing the Software Factory where the service is in development.
The open segment is used to let Indicium know that the OData protocol should not be enforced.
The iam segment indicates the ‘carrier’ of the model containing the service. When an application has been created in IAM, use iam. While the model is still in development in the Software Factory, the value sf can be used instead.
The application segment indicates the application id or alias used to refer to the application in IAM. An alias is recommended as this can remain stable while new versions of the application are released. While the model is still in development in the Software Factory, the runtime configuration id or an alias thereof can be used instead.
The process_flow segment will be the id of the process flow. If an API-alias is specified it will be available under the alias instead. Note that the endpoint is authorized. You need role rights if the service is hosted via an application in IAM or access rights to the Software Factory if the service is still in development.
Note that the process_flow segment can be followed by further segments or query string parameters, such as /process_flow(10) or /process_flow/subsegment/result?key=value. This information can be mapped to the process variables.
Authentication
You can configure Postman to use basic credentials to test the service. The account should be present in IAM and have the correct rights to the service or to access the Software Factory, as described earlier.
Alternatively, you can generate a bearer token to access the service (more info here).
The results
We use Postman to test the service using some example requests that the CRUD endpoints of the service are working as intended.
- We try to load a license that does exists and receive a 400 - Not found
- We create the missing license as a new license
- We re-load the license and receive the new license
- We update the license to have a different license state
- We re-load the license and receive the updated license
- We delete the license
- We try to re-load the and receive a 400 - Not found
Click on the image below to see the service at work.
Next up are the paginated licenses. As can be seen, the pagination is applied and the links indicate the proper pages. Applying filters affects the results and the available number of pages.
The license by application code and product code also works as intended. Note the usage of the path variables to set the proper application code and product code.
Last, access to applications with an active license. The result is paginated as described by the definition and is normalized compared to the underlying data structure.
Closing
This powerful new first-class feature of the Thinkwise Platform will offer virtually any type of web-based integration. This case provides only one of the many ways that message protocol independent webservices can be leveraged.
Authentication, authorization, routing to the correct process flow, mapping the http request and response with the variables and invocation of the various process actions is completely handled by Indicium.
The developer has complete control over the processing of the incoming request- and how the response is handled.
Any type of autonomous process action can be used in process flows supporting a message protocol independent web service. Future autonomous process actions will be usable in these types of process flows as well.
We can further iterate on these services by generating scaffolding for the process flows based on a webservice contract such as a WSDL or a swagger definition. If you have an interesting example, please head on over to the ThinkStore to share this with the community.
And of course, feel free to leave a comment if you have any suggestions, feedback or questions.