GDPR Cookie Consent by Free Privacy Policy Generator
post thumb
Asset Control
by Matthias Hanitzsch-Moonlight/ on 29 Sep 2020

A GraphQL service for Asset Control

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:

FX rates in AC

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 the findAdosByBaseCurrency 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:

AC GraphQL

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.

comments powered by Disqus