Skip to content

Service Interfaces

Service interfaces expose one or more operations that are used inside service model classes to define sequences of incoming and outgoing virtual service messages. You can either use a generic built-in service interface, or create a custom one by extending the built-in generic class. There is one built-in service interface class for each protocol.

REST service interface

You can use a REST service interface inside a service scenario in one of the following ways:

Defining REST service calls

When defining REST message calls using the withRequest and withResponse constructs, you must include several mandatory values, as shown below.

this.service.GET("/hello")
    .withRequest()
    .withResponse({"greeting": "Hello, world!"}, sv.JSON)
    .withStatusCode(200);
  1. For generic and Swagger-based service interfaces, you must manually provide an HTTP method with the path parameter: GET("/hello"). Auto-completion guides you in adding other HTTP methods.
  2. Provide a content type, for example the sv.JSON in .withResponse(...)
  3. Optionally, specify a status code, for example .withStatusCode(200). The default is 200 OK.
  4. If you need to work with a parametrized path, use placeholders like the {id} below. In the withPathParameters construct, the value of id parameter is saved to the selectedId SV variable.
    this.service.GET("/items/{id}")
        .withRequest()
        .withPathParameters({id: selectedId})
        ...
  5. To work with query string parameters, use the withQueryParameters construct. The example below shows the id parameter specified as a query string parameter. Never include query string parameters in the operation path (GET("/items")).
    this.service.GET("/items")
        .withRequest()
        .withQueryParameters({id: selectedId})
        ...
  6. Use the withHeaders construct to specify additional HTTP headers. Headers can be defined for both the request and response, and you can define several of them at once.
    this.service.GET("/items")
        .withRequest()
        .withHeaders({"Accept-Encoding":"gzip, deflate, br",
                      "Accept-Language":"en-US,en;q=0.9"})
        ...              
  7. To ignore headers during simulation, enclose them in an implicit SV variable:
    this.service.GET("/items")
        .withRequest()
        .withHeaders(sv.svVar({"Accept-Encoding":"gzip, deflate, br",
                               "Accept-Language":"en-US,en;q=0.9"}));
        ...                       

Using the generic REST interface

The example below uses the generic REST interface RestServiceInterface inside the service model, MyServiceModel. It passes the RestServiceInterface interface via a constructor argument to make it available inside the service model.

The VSL compiler automatically injects an instance of the service interface, whenever the service model is to be used. Subsequently you can define 'raw' REST service calls using the interface instance, as explained in Defining REST service calls.

import * as sv from "sv-vsl.js";

export class MyServiceModel extends sv.ServiceModel {

    constructor(s: sv.RestServiceInterface) {
        super(s);
        this.service = s;
    }

    @sv.scenario
    myServiceScenario() {
        this.service.GET("/items/{id}")
            .withRequest()
            .withResponse({"greeting": "Hello, world!"}, sv.JSON)
            .withStatusCode(200);
    }
}

Defining a custom REST service interface

It is a good practice to define a custom REST interface providing you with named service operations encapsulating the 'raw' HTTP calls.

To define a custom REST service interface:

  1. Create a new file and include the sv-vsl.js file from its current
    location. This is necessary in order to enable auto-completion in your IDE.
  2. Define a class that extends the _generic REST service interface, RestServiceInterface.
  3. Define operations encapsulating the 'raw' REST calls as shown below.
    Operation methods must be annotated with the @sv.operation decorator, and return the sv.RestOperation type. For example, myGetOperation(): sv .RestOperation {...}.
    import * as sv from "sv-vsl.js";
    
    export class MyServiceRestInterface extends sv.RestServiceInterface {
    
        @sv.operation
        myGetOperation(): sv.RestOperation {
            return new sv.RestOperation(sv.GET, "/status")
                .withRequest()
                .withResponse({"status":"OK"});
        }
    
        @sv.operation
        myPostOperation(): sv.RestOperation {
            return new sv.RestOperation(sv.POST, "/storage/{id}")
                .withRequest({
                    "myData": {
                         //...
                    }
                }, sv.JSON)
                .withPathParameters({id: 1})
                .withResponse({
                    "myData": {
                        //...
                    }
                }, sv.JSON);
        }
    }
  4. Import your custom service interface and make it available inside the service model via constructor argument injection, as shown below.
    import * as sv from "sv-vsl.js";
    import {MyServiceRestInterface} from "MyServiceRestInterface.js";
    
    export class MyServiceModel extends sv.ServiceModel {
    
        constructor(s: MyServiceRestInterface) {
            super(s);
            this.service = s;
        }
    
        @sv.scenario
        myServiceScenario() {
            this.service.myGetOperation()
                .withStatusCode(500) // You can further adjust
                .withDelay(500);     // the operation if needed.
        }
    }

Defining a custom REST service interface in a Swagger file

If you have an up-to-date Swagger file, you can define service interface by importing it from an external file, as shown below. Then you can use the service interface as it explicitly implemented the interface defined in the Swagger file. The service interface class must inherit from sv.RestServiceInterface. This approach is used when recording REST messages from real service using the SV Capture tool.

import * as sv from "sv-vsl.js";

export class MyServiceRestInterface extends sv.RestServiceInterface {
    constructor() {
        super();
        this.importExternal("MyServiceInterfaceSwagger.json");
    }
}

SOAP service interface

In VSL, two base classes for the SOAP service interface are available. Select Soap11ServiceInterface and use Soap11Operation for SOAP 1.1 services or select Soap12ServiceInterface and use Soap12Operation for SOAP 1.2 services.

The SoapOperation provides the following methods:

  1. withRequest or withResponse are used to set SOAP Body content in the form of an XML literal or variable. In case of a response containing a SOAP Fault, use the withResponse method without any parameter.
  2. withSoapHeader specifies a SOAP header of the message. It must be either an XML literal or variable. You can specify multiple SOAP headers by multiple calls of this method.
  3. SOAP Fault properties methods:
  4. withFaultCode sets the Fault Code property. The string parameter literal or variable specifies a local part of the QName value.
  5. withFaultDetail adds a new SOAP Fault Detail part in the form of an XML literal or variable. To set more detail parts, call this method multiple times.
  6. withFaultString and withFaultActor set corresponding properties of SOAP 1.1 Fault responses.
  7. withFaultReason and withFaultRole set corresponding properties of SOAP 1.2 Fault responses.

Using the generic SOAP interface

The example below uses the generic SOAP interface Soap11ServiceInterface inside the service model, AnimalBanquetServiceModel. It passes the Soap11ServiceInterface interface via a constructor argument to make it available inside the service model.

To make a call, use the invoke method provided by the interface class. The string parameter will become a SOAP action of the operation invoked. For SOAP 1.2 services, an additional second parameter can be used to set a response SOAP action.

import * as sv from "sv-vsl.js";

export class AnimalBanquetServiceModel extends sv.ServiceModel {

    constructor(s: sv.Soap11ServiceInterface) {
        super(s);
        this.service = super.getService();
    }

    @sv.scenario
    partyScenario() {
        this.service.invoke("inviteAnimalSoapAction")
            .withRequest(
                <inviteAnimalRequest>
                    <invitation>
                        <animal>sheep</animal>
                    </invitation>
                </inviteAnimalRequest>
            )
            .withResponse(
                <inviteAnimalResponse>
                    <invitationStatus>invited</invitationStatus>
                </inviteAnimalResponse>
            );
    }
}

Defining a custom SOAP service interface

It is a good practice to define a custom SOAP interface providing you with named service operations.

import {Soap12Operation, Soap12ServiceInterface, operation} from "sv-vsl.js"

export class AnimalBanquetServiceInterface extends Soap12ServiceInterface {

    @operation
    inviteAnimal():Soap12Operation {
        return new Soap12Operation()
            .withRequest(<inviteAnimalRequest/>)
            .withResponse(<inviteAnimalResponse/>);
    }

    @operation
    rejectAnimal():Soap12Operation {
        return new Soap12Operation()
            .withRequest(
                <rejectAnimalRequest>
                    <rejection>
                        <animal>fox</animal>
                    </rejection>
                </rejectAnimalRequest>)
            .withResponse(
                <rejectAnimalResponse>
                    <invitationStatus>rejected</invitationStatus>
                </rejectAnimalResponse>);
    }
}

Import your custom service interface and make it available inside the service model via constructor argument injection, as shown below.

import * as sv from "sv-vsl.js";
import {AnimalBanquetServiceInterface} from "AnimalBanquetServiceInterface.js";

export class AnimalBanquetServiceModel extends sv.ServiceModel {
    constructor(s:AnimalBanquetServiceInterface) {
        super(s);
        this.service = super.getService();
    }

    @sv.scenario
    cancelledPartyScenario() {
        this.service.inviteAnimal()
            .withRequest(
                <inviteAnimalRequest>
                    <invitation>
                        <animal>sheep</animal>
                    </invitation>
                </inviteAnimalRequest>
            )
            .withResponse(
                <inviteAnimalResponse>
                    <invitationStatus>invited</invitationStatus>
                </inviteAnimalResponse>
            );
    }
}

Mobile API service interface

The generic Mobile API service interface provides a set of predefined operations that does not need to be customized in most cases.

The example below uses the generic MobileApiServiceInterface service interface inside the MyMobileApiServiceModel service model:

import * as sv from "sv-vsl.js";

export class MyMobileApiServiceModel extends sv.ServiceModel {
    constructor(s: sv.MobileApiServiceInterface) {
        super(s);
        this.service = s;
    }

    @sv.scenario
    myScenario() {
        this.service.nfc_processCommandApdu()
            .withRequest({
                "command":"00A4040005F222222222",
                "deactivatedArg":"-1",
                "operation":"nfc_processCommandApdu"
            })
            .withResponse({"result": "01"});
        this.service.nfc_onDeactivated()
            .withRequest({"command":"","deactivatedArg":0,
                          "operation":"nfc_onDeactivated"});
    }
}

Apache Kafka service interface

When the service is using the Spring KafkaTemplate, the Java types of items can be specified as well using the third and fourth arguments of operation declaring methods: * .withRequest(req, type, sprintJavaType, springJavaComponentType) * .withResponse(resp, type, sprintJavaType, springJavaComponentType) * .withKey(key, type, sprintJavaType, springJavaComponentType) * .withHeader(header, type, sprintJavaType, springJavaComponentType)

The springJavaType specifies a full qualified name of Java type used in the application. When it is a collection type, then the springJavaComponentType parameter specifies the Java type of collection items.

The example below shows a Kafka operation specifying Java types:

@operation
doSubtract() : KafkaOperation {
    return new KafkaOperation("doSubtractOperation")
        .withRequest({"sub" : 50}, JSON, "my.SubStruct", "my.SubStructItem")
            .withKey({"a":"a"}, JSON, "my.KeySubStruct", "my.KeySubStructItem")
            .withHeader("reqHeader", {"ah":"ahVal"}, JSON, "my.HeaderSubStruct", "my.HeaderSubStructItem")
        .withResponse({"response":{"status": "ok"}, "result" : 100}, JSON, "my.RespSubStruct", "my.RespSubStructItem")
            .withKey({"aResp": "aRespVal"}, JSON, "my.KeyRespSubStruct", "my.KeyRespSubStructItem")
            .withHeader("respHeader", {"ahResp":"ahValReso"}, JSON,"my.HeaderRespSubStruct", "my.HeaderRespSubStructItem");
}

When using the request-response message exchange pattern with the Spring ReplyingKafkaTemplate, customize the operation with the .withSpringCorrelation() property to tell SV to use proper message headers for coupling of responses to requests. For more, see Spring correlation.

JMS service interface

The JMS operation definition requires specifying the request and response message type to determine whether the JMS TextMessage or BytesMessage should be used for transport. Use one of:

  • sv.JmsProperties.MESSAGE_TYPE_TEXT
  • sv.JmsProperties.MESSAGE_TYPE_BINARY

The example below shows a JMS service interface where the text messages are used for operations with XML, JSON and text payload:

export class SimpleJmsServiceInterface extends sv.JmsServiceInterface {
    @sv.operation
    xmlOperation():sv.JmsOperation{
        return new sv.JmsOperation("xmlOperation", sv.JmsProperties.MESSAGE_TYPE_TEXT, sv.JmsProperties.MESSAGE_TYPE_TEXT);
    }

    @sv.operation
    jsonOperation():sv.JmsOperation{
        return new sv.JmsOperation("jsonOperation", sv.JmsProperties.MESSAGE_TYPE_TEXT, sv.JmsProperties.MESSAGE_TYPE_TEXT);
    }

    @sv.operation
    textOperation():sv.JmsOperation{
        return new sv.JmsOperation("textOperation", sv.JmsProperties.MESSAGE_TYPE_TEXT, sv.JmsProperties.MESSAGE_TYPE_TEXT);
    }

    @sv.operation
    binaryOperation():sv.JmsOperation{
        return new sv.JmsOperation("binaryOperation", sv.JmsProperties.MESSAGE_TYPE_BINARY);
    }
}

MQTT service interface

MQTT operation definition requires the endpoint displayName ("statusUpdate" in this example) to bind to in case there are multiple endpoints used in service:

@operation
statusUpdate() : sv.MqttOperation {
    return new sv.MqttOperation("statusUpdate");
}

RabbitMQ service interface

When the service is using the Spring RabbitTemplate, the Java types of items can be specified as well using the third and fourth arguments of operation declaring methods: * .withRequest(req, type, sprintJavaType, springJavaComponentType) * .withResponse(resp, type, sprintJavaType, springJavaComponentType)

The springJavaType specifies a full qualified name of Java type used in the application. When it is a collection type, then the springJavaComponentType parameter specifies the Java type of collection items.

The example below shows a RabbitMQ operation specifying Java types:

@sv.operation
fib():sv.RabbitMqOperation {
    return new sv.RabbitMqOperation("fibEndpoint")
        .withRequest({
            "n" : 0
            }, sv.JSON, "demo.FibRequest")
        .withResponse({
            "n" : 0,
            "fibN" : 0
            }, sv.JSON, "demo.FibResponse");
}

The RabbitMQ protocol implementation in SV supports RPC message exchange pattern with Spring correlation.

The RabbitMQ operations allow to specify message properties present in every call, for example:

@operation
doReset() : RabbitMqOperation {
    return new RabbitMqOperation("doResetOperation")
        .withRequest(<reset/>, sv.XML)
        .withProperties({
            "myDoublePropName":sv.double(546.0)
            })
        .withResponse(<response><status>ok</status></response>, sv.XML);
} 

Spring correlation

Spring provides request/response pairing for Kafka and RabbitMQ with the ReplyingKafkaTemplate or the RabbitTemplate.sendAndReceive() methods.

The Kafka implementation uses the kafka_replyTopic, kafka_replyPartition and kafka_correlationId message headers to specify the topic for response and to store the correlation ID.

The RabbitMQ implementation uses the RabbitMQ replyTo and correlationId message headers to store correlation ID into message and to specify queue where to send response.

SV will automatically detect these properties during learning and learn/simulate the desired behavior.

Note

When Kafka service is implemented using VSL instead of learning, the operation must be marked as supporting Spring correlation. Without this mark, SV wouldn't generate correlation ID and reply-to in the INVOKE_REAL_SERVICE mode and the virtual service would not work in this mode. The marking can be achieved by using withSpringCorrelation() in the operation definition.

Example Kafka operation using the Spring correlation:

@sv.operation
fib(): sv.KafkaOperation {
    return new sv.KafkaOperation("fibEndpoint")
        .withRequest({
                "n": 0
            }
            , sv.JSON, "demo.FibRequest")
        .withResponse({
                "n": 0,
                "fibN": 0
            }, sv.JSON, "demo.FibResponse")
        .withSpringCorrelation();
}

How it works:

  • FORWARD_TO_REAL_SERVICE mode:
  • Request is received by SV on virtual request destination, it's reply-to is remembered, message is learned and sent to real request destination (the topic in Kafka or the queue or exchange in RabbitMQ) with reply-to set to configured real response destination.
  • The real service then sends response back to real response destination where SV receives it, learns it and sends to the remembered reply-to from request.
  • In this case the virtual response destination is not needed.
  • SIMULATE_SERVICE mode:
  • In this mode SV receives request on virtual request destination, generates response and sends it to reply-to destination specified in request.
  • There is only virtual request destination required in this case.
  • INVOKE_REAL_SERVICE mode:
  • SV generates a message with correlation ID and reply-to set to real response destination. The message is sent to real request destination.
  • Only the real part of endpoint configuration is needed.

Virtual response destination is not needed in any of the modes since the Spring client sends the reply-to information with every request.

Note

The real response Kafka topic must have only one consumer, i.e. you cannot run SV multiple times to use the same real response topic. This is because SV does not check the exact subscribed partition and thus the response could be received by different process. If you need multiple consumers and partitions, specify exact partition in endpoint configuration. SV will set both topic name and partition in reply-to.