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:
- Use the generic REST service interface
- Define a custom REST service interface by extending the generic one
- Import the REST service structure from a Swagger file
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);
- 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. - Provide a content type, for example the
sv.JSON
in.withResponse(...)
- Optionally, specify a status code, for example
.withStatusCode(200)
. The default is200 OK
. - If you need to work with a parametrized path, use placeholders like the
{id}
below. In thewithPathParameters
construct, the value ofid
parameter is saved to theselectedId
SV variable.this.service.GET("/items/{id}") .withRequest() .withPathParameters({id: selectedId}) ...
- To work with query string parameters, use the
withQueryParameters
construct. The example below shows theid
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}) ...
- 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"}) ...
- 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:
- 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. - Define a class that extends the _generic REST service interface,
RestServiceInterface
. - Define operations encapsulating the 'raw' REST calls as shown below.
Operation methods must be annotated with the@sv.operation
decorator, and return thesv.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); } }
- 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:
withRequest
orwithResponse
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 thewithResponse
method without any parameter.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.- SOAP Fault properties methods:
withFaultCode
sets the Fault Code property. The string parameter literal or variable specifies a local part of the QName value.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.withFaultString
andwithFaultActor
set corresponding properties of SOAP 1.1 Fault responses.withFaultReason
andwithFaultRole
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.