# Composing APIs and a Database

Source: https://orbitalhq.com/docs/guides/composing-api-and-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.

**Note: Get the source...**
There's code for this guide available on [Github](https://github.com/orbitalapi/demos/tree/main/composing-apis-and-db)

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
_Image: 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:

```taxi 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:

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](https://github.com/orbitalapi/demos/blob/main/composing-apis-and-db/services/api-docs.yaml).


```yaml 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
```
```taxi services.taxi
model StreamingProvider {
  name : StreamingProviderName
  pricePerMonth: StreamingProviderPrice
}

service StreamingProviderService {
  @HttpOperation(method = "GET", url = "/films/{filmId}/streamingProviders")
  operation findStreamingService(FilmId):StreamingProvider
}
```


### 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:

```hocon taxi.conf
plugins: {
   taxi/kotlin: {
      maven: {
         groupId: "com.petflix"
         artifactId: "films"
      }
   }
}
```

2. Run the Taxi build, which generates Kotlin classes for all our types into the `dist` folder:

```bash
taxi build
```

3. Add the dependency to the `pom.xml` of our newly created project:

```xml pom.xml
<dependency>
    <groupId>com.petflix</groupId>
    <artifact>films</artifact>
    <version>0.1.0</version>
</dependency>
```

4. Update our data classes to use the semantic types generated in step 2:

```kotlin StreamingProvider.kt
data class StreamingProvider(
    val name: StreamingProviderName,
    val pricePerMonth: StreamingProviderPrice
)
```

5. And our services:

```kotlin 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:


```hocon OpenAPI
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"
         }
      }
   ]
}
```
```hocon Taxi
file {
   projects = [
      {path: "taxi/taxi.conf"},
   ]
}
```


### 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`:

```xml pom.xml
<dependency>
    <groupId>com.orbitalhq</groupId>
    <artifact>schema-rsocket-publisher</artifact>
    <version>${orbital.version}</version>
</dependency>
```

2. Generate the schema on startup and publish it to Orbital:

```kotlin App.kt
@Component
class 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.

```taxi
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.

_Image: 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:

_Image: 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
 
_Image: 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

_Image: 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)

```bash
curl http://localhost:9022/api/q/films-and-reviews | jq
```

```json
  {
    "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
  },
```
_Image: Curly._
