Guides

Composing APIs and a Database

This guide focuses on stitching together APIs and a Database, to create a custom API for our needs. You’d typically use this in a Backend-for-frontend (BFF) pattern.

Get the source...

There's code for this guide available on Github

This demo takes place in a fictitious film studio. Our goal is to stitch together data about our Films catalog, along with data from REST APIs, like the reviews of each film, and where we can watch it.

Our demo services

The architecture for this demo
The architecture for this demo

This demo deploys the following:

  • A database exposing a catalog of Films
  • A REST Endpoint that returns which streaming services are playing each film
  • A REST Endpoint that returns reviews for each film
  • A REST Endpoint that resolves ids.
    • Our services have different ID schemes - specifically the IDs used in our DB are not the same IDs used by our FilmReviews API. Therefore, we need to map one set of Ids to another.

Describing our data and services

First, we’ll define a handful of Taxi types to describe the data returned from our services:

films.taxi
type StreamingProviderName inherits String
type StreamingProviderPrice inherits Decimal
type FilmId inherits Int
type Description inherits String
type Title inherits String

Then we’ll embed those types in our REST APIs.

We’ve shown this approach in a few different ways, depending on your preferred approach for describing APIs:

OpenAPI is pretty verbose. To keep things clear, we're just showing the relevant extracts here. The full OpenAPI spec is available on Github

OpenAPI.yml
paths: /reviews/{filmId}: get: parameters: - name: filmId in: path schema: type: string x-taxi-type: name: films.reviews.SquashedTomatoesFilmId /films/{filmId}/streamingProviders: get: parameters: - name: filmId in: path schema: type: integer format: int32 x-taxi-type: name: films.FilmId components: schemas: StreamingProvider: type: object properties: name: type: string x-taxi-type: name: films.StreamingProviderName pricePerMonth: type: number x-taxi-type: name: films.StreamingProviderPrice FilmReview: type: object properties: filmId: type: string x-taxi-type: name: films.reviews.SquashedTomatoesFilmId score: type: number x-taxi-type: name: films.reviews.FilmReviewScore filmReview: type: string x-taxi-type: name: films.reviews.ReviewText

Publish our API specs

Now that the API specs have taxi metadata, we can publish them to Orbital

workspace.conf
file { projects = [ {path: "taxi/taxi.conf"}, { path: "services/api-docs.yaml", loader: { packageType: OpenApi identifier: { organisation: "com.petflix" name: "PetflixServices" version: "0.1.20" }, defaultNamespace: "com.petflix" } } ] }

Composing APIs

Our APIs are now described and published to Orbital, so we can start writing queries to ask for data.

In the Query Editor, write a query to ask for data coming from the 3 APIs.

find { Film[] } as {
    id : FilmId
    title : Title

    review: FilmReviewScore
    reviewText: ReviewText

    availableOn: StreamingProviderName
    price: StreamingProviderPrice
}[]

Notice that as you’re typing, you get nice code completion.

Auto completing like a boss.
Auto completing like a boss.

Run this query, and you’ll get the results back, linking together data from our Database, and 3 different REST APIs.

Exploring the profiler

Click on the Profiler tab, and you’ll see an architecture diagram, showing all the services that were called for each field:

The profiler shows the services invoked to execute our query.
The profiler shows the services invoked to execute our query.

Note that

  • To fetch our serviceName and price, we passed data from the Db to a REST API
  • To fetch the review data, we had to take a trip to an additional API to resolve the Ids

How does this work?

There’s no resolver or glue code written here, so how does this all work?

Orbital uses the types in our query (FilmReviewScore, ReviewText, etc), and looks up the services that expose these values, then builds an integration plan to load the required data.

Exposing a composite API

Now we have the data we want to expose, we can publish this on an API.

  • First click “Save”
  • In the popup, for the project, select “films”
  • For the query name, enter filmsAndReviews (or any name you choose)
  • Click Save
Saving a query writes it to disk in developer mode, so you can commit to git.
Saving a query writes it to disk in developer mode, so you can commit to git.

If you take a look in the source code, a new file has appeared at taxi/src/filmsAndReviews.taxi.

Next, let’s expose this saved query as an HTTP endpoint.

  • In the top menu, click the 3-dots menu item
  • Click Publish query as HTTP Endpoint
  • In the popup, enter a URL for the query - eg: films-and-reviews
  • Click Update
  • Click Save
Publish your query as an HTTP endpoint, to consume from UIs.
Publish your query as an HTTP endpoint, to consume from UIs.

Now, send a request to the endpoint you selected. (As we’re getting JSON back, we’ll pipe it to jq so it’s nicely formatted)

curl http://localhost:9022/api/q/films-and-reviews | jq
  {
    "id": 904,
    "title": "TRAIN BUNCH",
    "review": 4.6,
    "reviewText": "This is not one of those awful dark, depressing films about an impending genetic apocalypse, although it could have easily been turned into that with a few minor tweaks. This is an entertaining romp, loaded with action, nostalgia and special effects.",
    "availableOn": "Netflix",
    "price": 9.99
  },
  {
    "id": 905,
    "title": "TRAINSPOTTING STRANGERS",
    "review": 3.9,
    "reviewText": "For a while it seems it wants to be the franchise’s ‘Mission: Impossible.’ Instead, it’s the anti–‘Top Gun: Maverick’.My co-worker Ali has one of these. He says it looks towering.",
    "availableOn": "Now TV",
    "price": 13.99
  },
Curly.
Curly.
Previous
Installing & First Project
Next
APIs, DBs and Kafka