Request Server - advanced
Audit tables
If you want to create a requestReply
codeblock for an Audit table, and you also want to specify a specific set of request fields, those fields must belong to an index in the underlying audit table.
Pre-processing a request
Request Server scripts can optionally transform a request parameter’s value using withTransformation
. This takes two inputs:
- the request parameter’s value (which is nullable)
- the full request message
In the example below, withTransformation
is used twice.
- If the ALTERNATE_TYPE parameter value is null, then the Request Server will use "UNKNOWN" by default.
- If the ALTERNATE_TYPE parameter has the value "RIC", then the transformation block will use the value of INSTRUMENT_CODE from the request. Otherwise, it will assign it the value "NOT_RIC" before making the database lookup.
requestReplies {
requestReply("INSTRUMENT_DETAILS", INSTRUMENT_DETAILS) {
request {
ALTERNATE_TYPE withTransformation { type, _ ->
type?.toUpperCase() ?: "UNKNOWN"
}
INSTRUMENT_CODE withTransformation { type, set ->
val value = if (set.fields["ALTERNATE_TYPE"].toString().toUpperCase() == "RIC") {
type
} else {
"NOT_RIC"
}
value
} withAlias "ALTERNATE_CODE"
}
reply {
INSTRUMENT_CODE
INSTRUMENT_ID
INSTRUMENT_NAME
LAST_TRADED_PRICE
VWAP
SPREAD
TRADED_CURRENCY
EXCHANGE_ID
}
}
}
Limit the number of rows returned
You can limit the number of rows returned using the property rowReturnLimit
. In this example, we limit it to 5.
requestReplies {
requestReply(INSTRUMENT_DETAILS) {
rowReturnLimit = 5
timeout = 15
request {
ALTERNATE_TYPE withAlias "ALTERNATE_TYPE"
}
reply {
INSTRUMENT_ID
INSTRUMENT_NAME
LAST_TRADED_PRICE
VWAP
SPREAD
TRADED_CURRENCY
EXCHANGE_ID
}
}
}
Timeout
You can specify a timeout (in seconds) for a Request Server using the property timeout
. In this example, we set a timeout of 15 seconds.
requestReplies {
requestReply(INSTRUMENT_DETAILS) {
rowReturnLimit = 5
timeout = 15
request {
ALTERNATE_TYPE withAlias "ALTERNATE_TYPE"
}
reply {
INSTRUMENT_ID
INSTRUMENT_NAME
LAST_TRADED_PRICE
VWAP
SPREAD
TRADED_CURRENCY
EXCHANGE_ID
}
}
}
If timeout is not set for a given requestReply definition, the default value will take the following precedence:
- the timeout value of the outer reqestReplies block
- else the timeout value of the
ReqRepTimeout
system definition item - else 60 seconds
Ranges
You can specify ranges from the client of the requestReply server by post-fixing the request parameter names with _FROM and _TO. The example below shows a client building a GenesisSet request based upon the requestReplies defined from previous example. This example stipulates a price range between 1,000 and 10,000. Leaving out FROM will define a top-end range, leaving out TO will define a bottom-end range.
// client building request
val request = genesisSet {
"DETAILS" with genesisSet {
"LAST_TRADED_PRICE_FROM" to 1_000
"LAST_TRADED_PRICE_TO" to 10_000
}
}
reply(request)
Note that ranges that are not based on indexes perform more slowly than those that are.
Permissioning
You can use a permissioning block to define both dynamic permissions (AUTH) and permission codes (based on RIGHT_SUMMARY rights) on Request Servers, which is similar to Event Handler and Data Server.
Dynamic permissioning
Similar to a Data Server, you can provide dynamic permissioning on a Request Server by providing a table/view reference.
requestReply("MARKET_INSTRUMENTS", INSTRUMENT_DETAILS) {
permissioning {
auth("EXCHANGE") {
INSTRUMENT_DETAILS.EXCHANGE_ID // This cannot be done on Custom request-server see ex below
}
}
request {
...
}
reply {
...
}
}
When you use Custom Request Servers, permissioning is different; it is similar to permissioning for Event Handlers. You can use any class/DAO as input and output classes; however, you cannot use field syntax in an auth block: for example, Use instrumentId instead of INSTRUMENT_DETAILS.INSTRUMENT_ID
requestReply<AltInstrumentId.ByAlternateTypeAlternateCode, AltInstrumentId> {
permissioning {
auth("INSTRUMENT") {
field { instrumentId }
}
}
reply { byAlternateTypeAlternateCode ->
...
}
}
Permission codes
Here is a simple example of using a permission code:
requestReply(INSTRUMENT_DETAILS) {
permissioning {
permissionCodes = listOf("LICENSE_TO_KILL", "LICENSE_TO_BILL")
}
request {
...
}
reply {
...
}
}
You can find out more details in our section on authorisation.
Custom Request Servers
By defining your own Request Servers, you have maximum flexibility.
- You can specify any class for the input and output, similar to Event Handlers.
- For the request, optional fields should have a default value in the primary constructor.
- You cannot use native Kotlin classes. You should wrap these in custom input and output classes.
We recommend that you locate your classes within the messages module of your application. This is where we place all the custom message types for our application. You need to ensure that the app-name-script-config module has a dependency on the messages module.
api(project("{app-name}-messages"))
The requestReply
code blocks can be as simple or complex as your requirements. They are useful, for example, if you want to request data from a number of different tables and views that are not related. By nesting and joining all the relevant data in your requestReply
statement, you create your own metadata for the Request Server, so it can then be used anywhere in the module.
Syntax
// the name is optional, if none is provided, then request will be based on the
// output class, e.g. REQ_OUTPUT_CLASS
requestReply<[InputClass], [OutputClass]> ("{optional name}") {
// permissioning is optional
permissioning {
// multiple auth blocks can be combined with the and operator and the or operator
auth("{map name}") {
// use a single field of output_class
field { fieldName }
// or use multiple fields of output_class
fields { listof(fieldNameA, fieldNameB) }
// hide fields are supported
hideFields { userName ->
listOf("FIELD_NAME_A")
}
// predicates are supported
where {
}
}
}
// a reply tag is required; there are three types.
// the reply tag will have a single parameter, the request, which will be of type
// [input class]
// all three have these fields available:
// 1. db - readonly entity database
// 2. userName - the name of the user who made the request
// 3. LOG - logger with name: global.genesis.requestreply.pal.{request name}
// either:
reply { request ->
}
// or:
replySingle { request ->
}
// or:
replyList { request ->
}
}
Types of reply
There are three types of reply that you can use with custom request servers:
- reply expects a type of
Flow<OutputClass>
to be returned. - replySingle expects a return of the
<OutputClass>
type that you defined in yourrequestReply
. - replyList expects a return of the type
List<OutputClass>
.
For all of these, you can use the parameter request
. In our previous example, the request
is of the type InputClass
.
Examples
In this example, we define two data classes; Hello and World. We use these to create a Hello World request:
data class Hello(val name: String)
data class World(val message: String)
requestReply<Hello, World>("HELLO_WORLD") {
replySingle { hello: Hello ->
World("Hello ${hello.name}")
}
}
We can also check who made the request by accessing the userName
property:
requestReply<Hello, World>("HELLO_WORLD_CHECK") {
replySingle { hello: Hello ->
when (userName) {
hello.name -> World("Hello ${hello.name}")
else -> World("You're not ${hello.name}!")
}
}
}
In this next example, we use generated dao classes to get a single record from the INSTRUMENT_DETAILS
table using the ByInstrumentId
index. We use the db
property to access the entity db.
requestReply<InstrumentDetails.ByInstrumentId, InstrumentDetails> {
replySingle { byId->
db.get(byId)
}
}
Next is a more complex example.
- The first block checks that the user is authorised to view the instrument.
- The second block uses the ALT_INSTRUMENT_ID table. The index is used as the input, but we return either a
getBulk
, agetRange
or aget
, depending on the input.
requestReply<AltInstrumentId.ByAlternateTypeAlternateCode, AltInstrumentId> {
permissioning {
auth("INSTRUMENT") {
field { instrumentId }
}
}
reply { byAlternateTypeAlternateCode ->
when {
byAlternateTypeAlternateCode.alternateType == "*" ->
db.getBulk(ALT_INSTRUMENT_ID)
byAlternateTypeAlternateCode.alternateCode == "*" ->
db.getRange(byAlternateTypeAlternateCode, 1)
else -> db.get(byAlternateTypeAlternateCode).flow()
}
}
}
In the example below, we have defined a more complicated auth logic:
requestReply<AltInstrumentId.ByAlternateTypeAlternateCode, AltInstrumentId>("FANCY_INSTRUMENT") {
permissioning {
auth("INSTRUMENT") {
field { instrumentId }
where {
alternateType == "FOO"
}
} or auth("ALTERNATE_CODE") {
field { alternateCode }
where {
alternateType == "BAR"
}
}
}
reply { byAlternateTypeAlternateCode ->
db.getRange(byAlternateTypeAlternateCode, 1)
}
}
Helpers assist you to interact with the Kotlin Flow type, which is the return type within the reply block. These helpers are:
- T.flow() - Converts to the Flow type
- T.distinct() - Returns a Flow of all distinct values
- T.distinctBy(selector: (T) -> K?) - Returns a Flow of all distinct values given a selector
- T.sorted() - Returns a Flow of all sorted values
- T.sortedBy(selector: (T) -> K?) - Returns a Flow of all sorted values given a selector
Client-side (runtime) options
When a client initiates a request to a Request Server with a request message, there are several options that can be specified. None of these options is mandatory; you don't have to specify any to send a request. The features of the options are explained below.
Option | Default | Description |
---|---|---|
MAX_ROWS | Equal to the rowReturnLimit configuration value defined for the target Request Server | Maximum number of rows to be returned as part of the reply message |
CRITERIA_MATCH | Clients can send a Groovy expression to filter specific rows on the reply message provided by the Request Server. For example: Expr.dateIsBefore(TRADE_DATE,'20150518') or QUANTITY > 10000 |
You can find out more details about the CRITERIA_MATCH parameter on our Advanced page for Data Server.