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. Ranges can be used with numeric values and Date/Datetime values. You must specify both _FROM and _TO to get a result. Specifying only one end will result in an error.
// 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)
- Ranges must be based on indexes. Post-fixing the request parameter targeting records without an index, will process the request ignoring the range.
- It is not possible to get a range using unix timestamps when targeting a GenesisFlake field. The epoch time (millis) is in the most significant bits of the raw GenesisFlake value, so you need to shift once right to decode into epoch millis (GenesisFlake to Timestamp) and once left to do the same thing for encoding (Timestamp to GenesisFlake). This is described in opur page on timestamps.
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") {
authKey {
key(data.exchangeId)
}
}
}
request {
...
}
reply {
...
}
}
When you use Custom Request Servers, permissioning works the same way, the only difference being the fact you can use any class/DAO as input and output classes.
requestReply<AltInstrumentId.ByAlternateTypeAlternateCode, AltInstrumentId> {
permissioning {
auth("INSTRUMENT") {
authKey {
key(data.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.
Within a simplified project, we recommend that you locate your classes within the main/kotlin messages folder of your project structure. Ensure that the app-name-reqrep.kts file has a dependency on the app-name-app ScriptModule.
@file:ScriptModules("{app-name}-app")
Within a legacy project, 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
authKey {
key(data.fieldName)
}
// or use multiple fields of output_class
authKey {
key(data.fieldNameA, data.fieldNameB)
}
// or use multiple fields of output_class as well as the username
authKeyWithUserName {
key(data.fieldNameA, data.fieldNameB, userName)
}
// hide fields are supported
hideFields { userName ->
listOf("FIELD_NAME_A")
}
// predicates are supported
filter {
}
}
}
// 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") {
authKey {
key(data.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") {
authKey {
key(data.instrumentId)
}
filter {
data.alternateType == "FOO"
}
} or auth("ALTERNATE_CODE") {
authKey {
key(data.alternateCode)
}
filter {
data.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.