Creating an AIMMS Server app

There are several advantages to setting up a client-server architecture:

  • Independent developments - clients can be developed independently from the server

  • Scalability - the AIMMS Cloud permits to run several tasks in parallel

AIMMS is designed to create Decision Support Applications, thereby a good choice to specify the actual implementation of a service. Besides actually implementing decision support, to make a service out of your application, you want to take care of the following aspects:

  • which services should defined,

  • how to implement the services,

  • testing the services, and

  • documenting the services.

This article assumes you are familiar with

The application that is used to illustrate various concepts, is just counting the asterisks in a list of strings. As such it is a “Hello World” type of application - just illustrating technology and getting you started to with that technology.

Functionalities of the application

Let’s start by describing the functionalities of the application:

  1. It counts the number of asterisks in its input.

  2. Generate an input file with some asterisks in it.

The input can be in one of the de facto data standards for providing data:

  1. CSV

  2. Json

  3. Parquet

  4. Excel

  5. XML

For each of the services, the data format of the output is the same as the data format of the input unless there are errors. If there are errors, the data format is always a json file.

For generating sample input files, the output can be in any of the above formats.

As a service is an exact specification of both functionality and input/output data formats, we have ten services to implement. Only one service will be detailed in this article, the others are small variations on that. The details of the other services can be found in the downloads.

Defining the service

A service is defined by associating a service name with an AIMMS procedure, as illustrated below:

 1Procedure pr_countTheStarsJson {
 2    Body: {
 3        _sp_inp := dex::api::RequestAttribute( 'request-data-path'  ) ;
 4        _sp_out := dex::api::RequestAttribute( 'response-data-path' ) ;
 5
 6        pr_actuallyCountStarsJson( _sp_inp, _sp_out );
 7
 8        return 1;
 9    }
10    dex::ServiceName: countStarsJson;
11    StringParameter _sp_inp;
12    StringParameter _sp_out;
13}

Remarks:

  • Line 10: The annotation dex::ServiceName associates the procedure pr_countTheStars with the service countStars.

  • Lines 3-4: When the procedure is invoked as a task, the string parameter dex::api::RequestAttribute is available. Here it is used to pass the names of the input and output files.

  • Line 6: Call the workhorse (see sub section below).

Similar procedures in this example application define the same service, but use other data formats, such as CSV, Excel, Parquet, and XML. In addition, there are similar procedures to generate a sample input file in the various data formats.

Implementing a service

The actual working code developed is the function fnc_numberOfStars. Here the connection is made between that function and the procedure defining the service above.

 1Procedure pr_actuallyCountStarsJson {
 2    Arguments: (sp_input,sp_output);
 3    Body: {
 4        empty s_lineNumbers ;
 5
 6        dex::ReadFromFile(
 7            dataFile         :  sp_input,
 8            mappingName      :  "starsJSON",
 9            emptyIdentifiers :  0,
10            emptySets        :  0,
11            resetCounters    :  0);
12
13        p_noStars := fnc_numberOfStars( sp_lines );
14
15        ! write response body
16        dex::WriteToFile(
17            dataFile    :  sp_output,
18            mappingName :  "countedJSON",
19            pretty      :  0);
20
21        ! Application specific return code.
22        return 1;
23    }
24    StringParameter sp_input {
25        Property: Input;
26    }
27    StringParameter sp_output {
28        Property: Input;
29    }
30}

remarks:

  • Line 2: The arguments denote the name of the input and output files.

  • Lines 6-11: Reading of input

  • Line 13: The actual computation as a simple function call.

  • Lines 16-19: Writing the output

Tip

The procedure ProfilerStart is called in MainInitialization enabling tracking task invocations, and task performance.

Testing the service

There are three types of tests:

  1. In the server app itself, also called unit tests.

  2. On the machine of the AIMMS app developer, using a client app for this purpose.

  3. On the AIMMS Cloud, and using a client app for this purpose.

Performing unit tests

An example of a unit test is the following:

 1Procedure pr_testCountJson {
 2    Body: {
 3        dex::AddMapping(
 4            mappingName :  "starsJSON",
 5            mappingFile :  "Mappings/starsJSON.xml");
 6        dex::AddMapping(
 7            mappingName :  "countedJSON",
 8            mappingFile :  "Mappings/countedJSON.xml");
 9
10        ! Call the procedure that does the actual implementation.
11        pr_actuallyCountStarsJson("data/data.json", "data/noStars.json");
12
13        ! Verify that the output file has the expected contents.
14        _sp_jsonContents := FileRead( "data/noStars.json" );
15        aimmsunit::AssertTrue(
16            descr :  "Expected outcome json",
17            expr  :  _sp_jsonContents = "{\"count\":28.0}",
18            cont  :  0);
19    }
20    aimmsunit::TestSuite: CountStarsUnitTests;
21    StringParameter _sp_jsonContents;
22}

Such unit tests verify that the server application still has the verified behavior.

More about unit tests can be found at:

  1. https://documentation.aimms.com/unit-test/index.html

  2. https://how-to.aimms.com/Articles/216/216-effective-use-unit-test-library.html

Facilitating tests with client apps

To facilitate testing by client apps of the service, the service will need to be:

  1. On local host:

    To develop the AIMMS service itself, in AIMMS Developer the service can be started using dex::api::StartAPIService. See also https://documentation.aimms.com/dataexchange/rest-server.html#activating-the-rest-service.

  2. In the AIMMS Cloud:

    After publishing an app app with version ver on the AIMMS Cloud, the service is started when a POST request of the above form is made; there is no need to call dex::api::StartAPIService from within the service app.

Development architecture of an AIMMS Service contains a more detailed overview of the communication between the server app and the client apps.

Documenting the service

For each service, we need to specify its:

  1. Functionality. IIn the running example this would be:

    The service countStarsJson counts the number of asterisks in a list of strings.

  2. Expected input / request body. In the running example this would be:

    The expected input is a json file with one member named “lines” and has as value an array of strings.

  3. Output / response body to be expected. In the running example this would be:

    The output to be expected is a json file with one member named count, and value the number of asterisks.

Summary

Using the AIMMS language is a good way to define a service atop of a Decision Support application.

With the DataExchange library, defining the interface is essentially a matter of

  1. Selecting input and output formats and linking the contents of these data files to identifiers in the AIMMS application

  2. Selecting the procedure to run

It is good practice to implement unit tests and provide good and detailed documentation of your services.

Next

Using AIMMS Services with a Python Application