Day three
Day two recap
This day covers:
Views
When you set up a data model, it implies relationships between tables. For example, a TRADE has a COUNTERPARTY_ID and an INSTRUMENT_ID. That means it has a relationship with the COUNTERPARTY and INSTRUMENTS tables.
Views enable you to join related tables to create a single holistic view.
In short, Views are the genesis equivalent of SQL select queries. Unlike tables, views do not have any data of their own, they are read-only, but present a view based on one or more tables.
A view always starts with a single table, the root table. Other tables can be joined onto the root table to present composite data.
Views are very powerful and in this training we're going to cover just the basics. When you have a chance, try to look at the documentation.
Entities
During code generation, view and index entities will be generated from the definitions in your application's view-dictionary.kts file. The name of each entity will be the same as the definition, but it is converted from snake case to camel case; for example, VIEW_NAME becomes ViewName.
The generated entities are kotlin data classes and can be built using the primary constructor (so you can also import Views in your Java/Kotlin code as well). Just before the object is built, it is validated to make sure all required fields have been set.
Usage
Create an alpha-view-dictionary.kts file inside the folder server/alpha-app/src/main/genesis/cfg/.
The example below creates a view called TRADE_VIEW
, which joins the TRADE
table to the INSTRUMENT
table. Edit the alpha–view-dictionary.kts file and add a view on the TRADE table:
views {
view("TRADE_VIEW", TRADE) {
joins {
joining(INSTRUMENT) {
on(TRADE.INSTRUMENT_ID to INSTRUMENT { INSTRUMENT_ID })
}
}
fields {
TRADE.allFields()
INSTRUMENT.INSTRUMENT_NAME
INSTRUMENT.MARKET_ID withPrefix INSTRUMENT
INSTRUMENT.CURRENCY_ID withAlias "CURRENCY"
}
}
}
withPrefix
and withAlias
withPrefix
adds a prefix to the standard field name. For example, INSTRUMENT.MARKET_ID withPrefix SYMBOL
becomes SYMBOL_MARKET_ID
.
withAlias
gives the field an alternative name for the view.
More info here.
Now go to the Data Server definition (open alpha-dataserver.kts). Replace the ALL_TRADES
query in the Data Server with the new TRADE_VIEW
.
dataServer {
query("ALL_TRADES", TRADE_VIEW)
...
}
In the example above, you are exposing a view through a Data Server query. It's also possible to inject a view into a Request Server or even your Event Handler code. This makes it easier to access complex data from multiple tables in your Kotlin or Java code. Look at package global.genesis.gen.view.repository.
Run build and deploy, and test the view with Postman or Console.
Exercise 3.1: using views
30 mins
Extend the TRADE_VIEW to connect TRADE to COUNTERPARTY:
- Add the respective join (as we did with
INSTRUMENT
). - Add the field
COUNTERPARTY.COUNTERPARTY_NAME
. - Test it.
Extending our application further
Moving on, for our app to be able to keep positions based on the trades, we now need to extend our data model.
Adding new fields
Let's add new fields to the Trade table.
fields {
...
field("TRADE_DATE", type = DATE)
field("ENTERED_BY", type = STRING)
field(name = "TRADE_STATUS", type = ENUM("NEW", "ALLOCATED", "CANCELLED", default = "NEW"))
}
tables {
table (name = "TRADE", id = 2000) {
...
TRADE_DATE
ENTERED_BY
TRADE_STATUS
primaryKey {
TRADE_ID
}
}
...
}
And new fields to create the POSITION and INSTRUMENT_PRICE tables:
fields {
...
field("POSITION_ID", type = STRING)
field("NOTIONAL", type = DOUBLE)
field("LAST_PRICE", type = DOUBLE)
field("VALUE", type = DOUBLE)
field("PNL", type = DOUBLE)
}
When you finish, remember to run generatefields.
Updating the schemas.ts
After creating these new fields, go back to the schemas.ts
and add this code blocks to it. so we can interact with them.
const conditionalSchemaEntry = (predicate: boolean, entry) => {
return predicate ? [entry] : [];
};
export const tradeFormSchema = (editing?: boolean) => ({
...
{
"type": "Control",
"label": "Counterparty",
"scope": "#/properties/COUNTERPARTY_ID",
"options": {
allOptionsResourceName: "ALL_COUNTERPARTIES",
valueField: "COUNTERPARTY_ID",
labelField: "COUNTERPARTY_NAME",
data: null,
},
},
...
{
"type": "Control",
"label": "Trade Date",
"scope": "#/properties/TRADE_DATE"
},
{
"type": "Control",
"label": "Status",
"scope": "#/properties/TRADE_STATUS"
},
{
"type": "Control",
"label": "Entered By",
"scope": "#/properties/ENTERED_BY"
}
],
});
export const tradeFormCreateSchema = tradeFormSchema(false);
export const tradeFormUpdateSchema = tradeFormSchema(true);
Extending the Trade table and adding a Position table
-
Add the new fields into the TRADE table.
-
Then create the POSITION and INSTRUMENT_PRICE tables.
tables {
...
table(name = "POSITION", id = 2003) {
sequence(POSITION_ID, "PS") //autogenerated sequence
INSTRUMENT_ID not null
QUANTITY
NOTIONAL
VALUE
PNL
primaryKey {
POSITION_ID
}
indices {
unique {
INSTRUMENT_ID
}
}
}
}
tables {
...
table(name = "INSTRUMENT_PRICE", id = 2004) {
INSTRUMENT_ID
LAST_PRICE
primaryKey {
INSTRUMENT_ID
}
}
}
When you finish, remember to run generatedao and build and deploy.
As we have previously generated the fields, autocompletion helps you to define the tables more quickly, and with fewer errors. Also note that Genesis provides several autogenerated primary keys: sequence, uuid, autoIncrement.
Automated testing
So far we have been testing our work manually, using Genesis Console or some HTTP client. Now the time has come to start writing some automated tests for our application. We are going to test our TradeView and the Trade insert method we created.