In this blog post I would like to show how to implement a GraphQL service for FX rates stored in Asset Control. In a subsequent article I will then make use of the GraphQL data in a small React app to generate charts for these FX rates.
You can find the source code here: https://github.com/mhmtio/ac-graphql
Firstly, what is GraphQL?
What is GraphQL?
According to https://graphql.org/, GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.
Most of you will be familiar with REST APIs where a request to a (potentially parameterised) URL returns a response in JSON format. While a GraphQL service still returns JSON, the beauty and relative strength lies in its - yes - query language. In a fast-forward to what we are going to build, please look at the below query:
{
fxRatesByBaseCurrency(baseCurrency: "GBP") {
quoteCurrency
timeseries {
date
rate
}
}
}
We are interested in FX rates which we query by their base currency. And we specify that the result should contain the quote currency as well as the timeseries with its date and rate values.
GraphQL service for Asset Control data
Now, I want to show how to implement a GraphQL service for Asset Control using FX rates as an example.
FX Rates in Asset Control
We are going to need some data. For this purpose we have a small set of FX rates in Asset Control that are only characterised by their base and quote currencies, plus timeseries:
Implementing a GraphQL service
Now we can look at implementing a GraphQL service for Asset Control using the FX rates as an example. As mentioned, the full source code can be found here: https://github.com/mhmtio/ac-graphql
Let’s define the necessary dependencies in our pom.xml (Gradle users, please adjust as necessary):
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>11.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-spring-boot-starter-webmvc</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>io.terrafino</groupId>
<artifactId>adetta-api-ac</artifactId>
<version>1.0.0</version>
</dependency>
The first two libraries are necessary for GraphQL, the third for enabling CORS (so we can use the service from our React app later) and the last one for accessing Asset Control data using functions we already utilise in Adetta, our test automation solution for Asset Control.
Central to any GraphQL is the schema, so let’s define that under resources/schema.graphqls
:
type Query {
fxRatesByBaseCurrency(baseCurrency: String): [Ado!]
}
type Ado {
id: ID
name: String
baseCurrency: String
quoteCurrency: String
timeseries: [TimeseriesRow!]
}
type TimeseriesRow {
date: Int
rate: Float
}
We define a query fxRatesByBaseCurrency
that will allow us to find FX rates given a base currency. This query returns
an array of type Ado.
The type Ado holds some static data, above all the base and quote currency of our FX rates and their timeseries. The timeseries is made up of an array of TimeseriesRow which in turn holds a date and a rate.
Next we implement a Spring @Component
that we call GraphQLProvider
:
@Component
public class GraphQLProvider {
private GraphQL graphQL;
@Autowired
GraphQLDataFetchers graphQLDataFetchers;
@Bean
public GraphQL graphQL() {
return graphQL;
}
@PostConstruct
public void init() throws IOException {
URL url = Resources.getResource("schema.graphqls");
String sdl = Resources.toString(url, Charsets.UTF_8);
GraphQLSchema graphQLSchema = buildSchema(sdl);
this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
}
private GraphQLSchema buildSchema(String sdl) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
RuntimeWiring runtimeWiring = buildWiring();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring()
.type(newTypeWiring("Query")
.dataFetcher("fxRatesByBaseCurrency", graphQLDataFetchers.getFxRatesByBaseCurrency()))
.type(newTypeWiring("Ado")
.dataFetcher("timeseries", graphQLDataFetchers.getTimeseriesDataFetcher()))
.build();
}
}
This class gives us access to a GraphQL
bean which we initialise based on schema.graphqls
and the GraphQLDataFetchers
.
You will notice that there are two data fetchers:
- one to implement the
fxRatesByBaseCurrency
query - another one to fetch the timeseries of a given Ado.
Let’s look at both data fetchers in GraphQLDataFetchers
:
@Component
public class GraphQLDataFetchers {
@Autowired
private AcAccess ac;
public DataFetcher<List<Map<String, String>>> getFxRatesByBaseCurrency() {
return dataFetchingEnvironment -> {
String ccy = dataFetchingEnvironment.getArgument("baseCurrency");
return ac.findAdosByBaseCurrency(ccy).stream().map(ado -> getAdoData(ado)).collect(Collectors.toList());
};
}
private Map<String, String> getAdoData(Ado ado) {
return ImmutableMap.of(
"id", ado.getId(),
"name", ado.getLongname(),
"baseCurrency", getAsString(ado, "C0#SA010"),
"quoteCurrency", getAsString(ado, "C0#SA011")
);
}
...
public DataFetcher<List<TimeseriesRow>> getTimeseriesDataFetcher() {
return dataFetchingEnvironment -> {
Map<String, Object> ado = dataFetchingEnvironment.getSource();
String adoId = (String) ado.get("id");
List<TsRecord> records = ac.getTimeseriesFor(adoId, "CONSOLIDATION_C0");
return records.stream().map(record ->
new TimeseriesRow(record.getDate(), record.get(0).toDouble())
).collect(Collectors.toList());
};
}
}
Here is what happens:
- the
AcAccess
instance gives us convenient methods to interact with Asset Control (see below) getFxRatesByBaseCurrency
uses the baseCurrency parameter of the GraphQL query and thefindAdosByBaseCurrency
method to return a list of Ados.- using the
getAdoData
method we create a map for each Ado’s data that aligns to the properties defined in the schema: id, name, baseCurrency and quoteCurrency. - the
getTimeseriesDataFetcher
data fetcher is run in the context of an Ado and we access the corresponding Map with the Ado data. We retrieve the Ado ID and use the AcAccess instance to get the timeseries. - Here we wrap our result in instances of
TimeseriesRow
(see below). This is to show that we are not limited to generic Maps but can use custom types as well.
A quick look at TimeseriesRow
which is just a wrapper for date and rate:
public class TimeseriesRow {
private Integer date;
private Double rate;
public TimeseriesRow(Integer date, Double rate) {
this.date = date;
this.rate = rate;
}
public Integer getDate() {
return date;
}
public Double getRate() {
return rate;
}
}
Now we can look at how we actually access our data in Asset Control. The class AcAccess
is a thin layer over the
Asset Control API we utilise in Adetta.
@Component
public class AcAccess {
private AcConnection conn;
private AcService ac;
private AcQueryService acQueryService;
public AcAccess() {
try {
this.conn = AcConnection.getDefaultConnection();
this.ac = new AcService(conn);
this.acQueryService = new AcQueryService(conn);
} catch (AcException e) {
// ...
}
}
public List<Ado> findAdosByBaseCurrency(String ccy1) throws AcException {
return acQueryService.adoBrowse(
String.format("symbol like 'C0.FXS%%' and attribute('C0#SA010') = '%s' ", ccy1));
}
public List<TsRecord> getTimeseriesFor(String adoId, String tree) throws AcException {
return acQueryService.loadTimeseries(ac.createAdo(adoId), tree, 0, 0,
Attributes.attributes("CLOSE")).getRecords();
}
}
findAdosByBaseCurrency
wraps an adoBrowse call for C0.FXS Ados with the given base currency.getTimeseriesFor
returns all CLOSE prices for the given Ado and datafile tree.
Trying it out
I think if you are still reading you deserve to see a result, so let’s get to it!
We will wrap this up as a Spring Boot application and run it:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
To see our newly created Asset Control GraphQL service in action - while we don’t have our React app yet - we can use GraphQL Playground:
You can see the query on the left and the result on the right. Amending the query to include timeseries works as expected.
In the second part of this little project we will build a React app that will use this GraphQL service and then chart the FX rates. Coming soon …
Thank you for reading.