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.

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

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 Stringtype StreamingProviderPrice inherits Decimaltype FilmId inherits Inttype Description inherits Stringtype 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:

If you describe your APIs in a spec file, embedding the types is a matter of annotating the spec. OpenAPI is pretty verbose, so only the relevant extracts are shown - the full spec is on GitHub.

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.FilmIdcomponents:   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

Generating the spec from Spring Boot and Kotlin

Teams who prefer to generate specs from code can have Spring Boot publish a schema automatically. It takes a little more up-front setup, but it is a one-time activity.

  1. Update taxi.conf to add the Kotlin code generator:
taxi.conf
plugins: {   taxi/kotlin: {      maven: {         groupId: "com.petflix"         artifactId: "films"      }   }}
  1. Run the Taxi build, which generates Kotlin classes for all our types into the dist folder:
taxi build
  1. Add the dependency to the pom.xml of our newly created project:
pom.xml
<dependency>    <groupId>com.petflix</groupId>    <artifact>films</artifact>    <version>0.1.0</version></dependency>
  1. Update our data classes to use the semantic types generated in step 2:
StreamingProvider.kt
data class StreamingProvider(    val name: StreamingProviderName,    val pricePerMonth: StreamingProviderPrice)
  1. And our services:
StreamingProviderController.kt
@GetMapping("/films/{filmId}/streamingProviders")fun whereCanIWatch(  @PathVariable("filmId") filmId: FilmId): StreamingProvider

Publish our API specs

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

For a spec file, that is a workspace.conf entry pointing at it:

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"         }      }   ]}

Publishing from Spring Boot and Kotlin

Our Spring Boot services are now self-describing; they just need to publish their schema on startup.

  1. Add the publisher dependency to pom.xml:
pom.xml
<dependency>    <groupId>com.orbitalhq</groupId>    <artifact>schema-rsocket-publisher</artifact>    <version>${orbital.version}</version></dependency>
  1. Generate the schema on startup and publish it to Orbital:
App.kt
@Componentclass RegisterSchemaOnStartup(    @Value("${server.port}")    private val serverPort: String,    @Value("${spring.application.name}")    private val appName: String) {  init {    val publisher = SchemaPublisherService(        appName,        RSocketSchemaPublisherTransport(            TcpAddress("localhost", 7655)        )    )    publisher.publish(        PackageMetadata.from("io.petflix.demos", appName),        SpringTaxiGenerator.forBaseUrl("http://localhost:${serverPort}")            .forPackage(StreamingMoviesProvider::class.java)            .generate()    ).subscribe()  }}

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.

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.

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.

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.

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.

Curly.