Aggregation

The Aggregation is an architectural operator between an inputPort and a set of outputPorts which allows for composing services in a way that the API of the aggregated services are merged with those of the aggregator. It is a generalisation of network proxies that allow a service to expose operations without implementing them in its behaviour, but delegating them to other services. Aggregation can also be used for programming various architectural patterns, such as load balancers, reverse proxies, and adapters.

The syntax for aggregation extends that given for input ports.

inputPort id {
    location: URI
    protocol: p
    interfaces: iface_1, ..., iface_n
    aggregates: outputPort_1, outputPort_2, ...
}

Where the aggregates primitive expects a list of output port names.

If we observe the list of the operations available at the inputPort of the aggregator, we will see the list of all the aggregated operations together with those of the aggregator.

How Aggregation works

We can now define how aggregation works. Given IP as an input port, whenever a message for operation OP is received through IP, we have three scenarios:

  • OP is an operation declared in one of the interfaces of IP. In this case, the message is normally received by the program.
  • OP is not declared in one of the interfaces of IP and is declared in the interface of an output port (OP_port) aggregated by IP. In this case the message is forwarded to OP_port port as an output from the aggregator.
  • OP is not declared in any interface of IP or of its aggregated output ports. Then, the message is rejected and an IOException fault is sent to the caller.

We can observe that in the second scenario aggregation merges the interfaces of the aggregated output ports and makes them accessible through a single input port. Thus, an invoker would see all the aggregated services as a single one.

Remarkably, aggregation handles the request-response pattern seamlessly: when forwarding a request-response invocation to an aggregated service, the aggregator will automatically take care of relaying the response to the original invoker.

As an example let us consider the case of two services, the printer and fax, aggregated into one service which also add another operation called faxAndPrint. The code may be consulted here.

The service printer offers two operations called print and del. The former allows for the printing of a document whereas the latter allows for its deletion from the queue. On the other hand the service fax offers just one operation called fax. The aggregator, aggregates on its inputPort called Aggregator both the printer and fax services as it is shown below where we report the ports declaration of the aggregator service:

	/* this outputPort points to service Printer */
	outputPort Printer {
		location: "socket://localhost:9000"
		protocol: sodep
		interfaces: PrinterInterface
	}

	/* this outputPort points to the service Fax */
	outputPort Fax {
		location: "socket://localhost:9001"
		protocol: sodep
		interfaces: FaxInterface
	}

	/* this is the inputPort of the Aggregation service */
	inputPort Aggregator {
		location: "socket://localhost:9002"
		protocol: sodep
		/* the service Aggregator does not only aggregates other services, but it also provides its own operations */
		interfaces: AggregatorInterface
		/* Printer and Fax outputPorts are aggregated here. All the messages for their operations
		will be forwarded to them */
		aggregates: Printer, Fax
	}

The surface can be included by an invoker service for getting all the available operations for invoking the port Aggregator.

jolie2surface

One important characteristic of the surface is that it actually does not exist as a software artifact until it is automatically derived and created from an input port declaration. So, how could we create a surface?

The Jolie installation is equipped with a tool called jolie2surface which allows for the creation of a surface starting from a service definition. Its usage is very simple, it is sufficient to run the following command:

jolie2surface <filename.ol> <name of the port>

in order to obtain the surface of port Aggregator discussed in the previous section, the command is:

jolie2surface aggregator.ol Aggregator

if you need to save it into a file, just redirects the standard output:

jolie2surface aggregator.ol Aggregator > surface.iol

Note that the tool jolie2surface also adds the outputPort declaration connected to the input port.

Extracting surface programmatically

The surface can be extracted in a programmatic way too by exploiting the standard library of Jolie. In particular, we can use the services MetaJolie and MetaRender for getting the surface of a an input port of a service. The service MetaJolie provides a set of functionalities for getting important meta information about a service whereas the service MetaParser provides for transforming these information into a syntactically correct Jolie definition. If we want to extract the surface of an input port we can use the operation getInputPortMetaData@MetaJolie which returns a complete description of the input port of a service definition. Then, with the operation getSurface@Parser we can extract the surface by passing the definition of the input port obtained from the previous operation.

In the following you can find the example of the programmatic surface extraction of service aggregator.ol.

include "metajolie.iol"
include "metaparser.iol"
include "console.iol"

main {
    getInputPortMetaData@MetaJolie( { .filename = "aggregator.ol" } )( meta_description );
    getSurface@Parser( meta_description.input[ 0 ] )( surface );
    println@Console( surface )()
}

The executable code can be found at this link

Protocol Transformation

Aggregation can be used for system integration, e.g., bridging services that use different communication technologies or protocols. As an example, let us consider the system discussed in the previous section but considering that the aggregated services offers they operation using different protocols like http/json and http/soap as depicted in the following picture:

transformation

In this case the aggregator automatically transforms the messages thus enabling a transparent composition of services which exploit different protocols.

The full executable example can be found here. Here we report the input ports of both the fax and the printer services, and the output ports of the aggregator together with its main input port.

	outputPort Printer {
		location: "socket://localhost:9000"
		protocol: http { .fomat = "json"; .debug=false }
		interfaces: PrinterInterface
	}

	/* this outputPort points to the service Fax */
	outputPort Fax {
		location: "socket://localhost:9001"
		protocol: soap { .wsdl = "fax.wsdl"; .debug=false }
		interfaces: FaxInterface
	}

	/* this is the inputPort of the Aggregation service */
	inputPort Aggregator {
		location: "socket://localhost:9002"
		protocol: sodep
		/* the service Aggregator does not only aggregates other services, but it also provides its own operations */
		interfaces: AggregatorInterface
		/* Printer and Fax outputPorts are aggregated here. All the messages for their operations
		will be forwarded to them */
		aggregates: Printer, Fax
	}