Guides

Generating Taxi from code

This guide explores generating Taxi schemas directly from your application code. This approach is well suited to teams following a Code-First approach (versus a Design-First, sometimes referred to as a Spec-First approach).

Currently, this guide focuses on JVM languages (Java and Kotlin). We also showcase a Spring Boot application.

If you’d like to see a version for your preferred language or framework, please reach out to us on Slack.

Adding dependencies

First, you’ll need to add the following dependencies to your project:

<properties>
  <orbital.version>0.32.0</orbital.version>
  <taxi.version>1.53.0</taxi.version>
</properties>
<dependencies>
  <dependency>
    <groupId>com.orbitalhq</groupId>
    <artifactId>schema-rsocket-publisher</artifactId>
    <version>${orbital.version}</version>
  </dependency>
  <dependency>
    <groupId>org.taxilang</groupId>
    <artifactId>java2taxi</artifactId>
    <version>${taxi.version}</version>
  </dependency>
  <dependency>
    <groupId>org.taxilang</groupId>
    <artifactId>java-spring-taxi</artifactId>
    <version>${taxi.version}</version>
  </dependency>    
</dependencies>
<repositories>
  <repository>
    <id>orbital-releases</id>
    <url>https://repo.orbitalhq.com/release</url>
  </repository>
  <!-- Snapshot repository - only required if using a snapshot version -->
  <repository>
    <id>orbital-snapshots</id>
    <url>https://repo.orbitalhq.com/snapshot</url>
    <snapshots>
      <enabled>true</enabled>
    </snapshots>
  </repository>
</repositories>
ext {
  orbitalVersion = '0.32.0'
  taxiVersion = '1.53.0'
}

repositories {
  maven {
      url "https://repo.orbitalhq.com/release"
  }
  // Only required if you're using a snapshot dependency
  maven {
      url "https://repo.orbitalhq.com/snapshot"
      mavenContent {
          snapshotsOnly()
      }
  }
}

dependencies {
  implementation "com.orbitalhq:schema-rsocket-publisher:${orbitalVersion}"
  implementation "org.taxilang:java2taxi:${taxiVersion}"
  implementation "org.taxilang:java-spring-taxi:${taxiVersion}"
}

A quick explanation of the dependencies:

  • schema-rsocket-publisher provides Orbital specific schema publication code
  • Taxi’s java2taxi is responsible for generating Taxi code from Java code
  • java-spring-taxi contains Spring specific extensions to java2taxi.

Both Taxi and Orbital dependencies are hosted in the Orbital maven repository, so be sure to configure your repositories section, as shown above.

Publishing to Orbital

Next, we’ll configure our Spring Boot application to publish to Orbital on startup.

First, we’ll define the transport responsible for sending code to Orbital:

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class OrbitalConfig {

  @Bean
  fun schemaPublisher(): SchemaPublisherService =
      SchemaPublisherService(
          // Provide a unique id for this publisher.
          // Often, the name of your Spring Boot app (as defined in 
          // spring.application.name
          // or even the ArtifactId of your project is fine -- so long as it's unique
          "films-listings", 
          RSocketSchemaPublisherTransport(
              // The address of your Orbital instance.
              // We'll assume this is running on localhost.
              // The default RSocket port for Orbital is 7655
              TcpAddress("localhost", 7655)
          )
      )
}

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrbitalConfig {

  @Bean
  public SchemaPublisherService schemaPublisher() {
      return new SchemaPublisherService(
          // Provide a unique id for this publisher.
          // Often, the name of your Spring Boot app (as defined in 
          // spring.application.name
          // or even the ArtifactId of your project is fine -- so long as it's unique
          "films-listings", 
          new RSocketSchemaPublisherTransport(
              // The address of your Orbital instance.
              // We'll assume this is running on localhost.
              // The default RSocket port for Orbital is 7655
              new TcpAddress("localhost", 7655) 
          )
      );
  }
}

Next, we’ll configure a Taxi generator - which introspects our Spring Boot application, and generates corresponding Taxi code.


import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

import com.orbitalhq.PackageMetadata
import com.orbitalhq.schema.publisher.SchemaPublisherService
import lang.taxi.generators.java.TaxiGenerator
import lang.taxi.generators.java.spring.SpringMvcExtension

@Component
class RegisterSchemaTask(
  // Inject the publisher we created earlier
  publisher: SchemaPublisherService,
  // The server port - by default this is 8080
  @Value("\${server.port:8080}") private val serverPort: String
) {
  init {
      // When the server starts, publish the generated code to Orbital
      publisher.publish(
          // Each Taxi project (including the one we're about to generate)
          // needs a unique package identifier - similar to how a pom.xml
          // or package.json needs a project id.
          // Here, we're defining an organisation of io.petflix.demos
          // and a project of films-listings
          // Change these to suit your own project.
          // Typically, re-using your maven co-ordinates here is fine
          PackageMetadata.from("io.petflix.demos", "films-listings"),
          // The Spring generator looks for Spring specific annotations
          // on our Spring Boot application and generates the corresponding
          // Taxi schema.
          // You need to pass the base url of your project here.
          // We'll assume this is running on localhost, but typically
          // this is provided by Spring config
          SpringTaxiGenerator.forBaseUrl("http://localhost:${serverPort}")
            // The generator will scan for anything found
            // under this package
            .forPackage(FilmsListingApp::class.java)
            .generate()
      ).subscribe()
  }
}
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

import com.orbitalhq.PackageMetadata;
import com.orbitalhq.schema.publisher.SchemaPublisherService;
import lang.taxi.generators.java.TaxiGenerator;
import lang.taxi.generators.java.spring.SpringMvcExtension;


@Component
public class RegisterSchemaTask {

  private final SchemaPublisherService publisher;
  private final String serverPort;

  public RegisterSchemaTask(SchemaPublisherService publisher, @Value("${server.port:8080}") String serverPort) {
      this.publisher = publisher;
      this.serverPort = serverPort;
  }

  @PostConstruct
  public void init() {
      publisher.publish(
          // Each Taxi project (including the one we're about to generate)
          // needs a unique package identifier - similar to how a pom.xml
          // or package.json needs a project id.
          // Here, we're defining an organisation of io.petflix.demos
          // and a project of films-listings
          // Change these to suit your own project.
          // Typically, re-using your maven coordinates here is fine
          PackageMetadata.from("io.petflix.demos", "films-listings"),
          // The Spring generator looks for Spring specific annotations
          // on our Spring Boot application and generates the corresponding
          // Taxi schema.
          // You need to pass the base url of your project here.
          // We'll assume this is running on localhost, but typically
          // this is provided by Spring config
          SpringTaxiGenerator.forBaseUrl("http://localhost:${serverPort}")
          // The generator will scan for anything found
          // under this package
          .forPackage(FilmsListingApp.class)
          .generate()
      ).subscribe();
  }
}

Now, if you start your Spring Boot application, you should see it register within Orbital’s Project view.

The project registered in Orbital
The project registered in Orbital

Generating service code

To start, we’ll publish an API. We don’t need to do anything special other than standard Spring Boot things, as we’re already scanning for any Spring Boot services within the same package as the FilmsListingApp

Here’s a standard Spring Boot REST API endpoint:

package com.petflix.films

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class FilmListingsService {

    data class Film(
        val id: Int,
        val title: String,
    )

    @GetMapping("/films")
    fun listFilms(): List<Film> = listOf(
        Film(1, "A New Hope"),
        Film(2, "Empire Strikes Back"),
        Film(3, "Return of the Jedi"),
    )
}

If we restart our Spring Boot application now, we’ll see it publish Taxi code for our service to Orbital.

To see the published source code, head to the Projects panel, click the films-listings project, and then click Source.

The service has been published to Orbital
The service has been published to Orbital

At this point, Orbital’s catalog contains information about our Service, and it’s rest endpoint. For example, heading to the services diagram will show our API and it’s returned model:

The service is shown in Orbital's service diagrams
The service is shown in Orbital's service diagrams

Creating a second service

Orbital’s real strength is in orchestrating multiple services together, so let’s add a second service - this time, that provides film reviews. The full code is listed here. Again - there’s nothing special here (yet), other than standard Spring Boot stuff.

package com.petflix.films

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RestController
import kotlin.random.Random

@RestController
class ReviewsService {

    data class FilmReview(
        val score: Int,
        val review: String
    )

    @GetMapping("/films/{filmId}/review")
    fun getFilmReview(@PathVariable("filmId") filmId: Int): FilmReview {
        return FilmReview(
            score = Random.nextInt(1,5),
            review = listOf("Good","Bad","Meh").random()
        )
    }
}

If we restart our Spring Boot application now, we’ll see both services published:

Both our services are now registered with Orbital
Both our services are now registered with Orbital

So far, we have two services:

  • One that exposes a list of films
  • One that takes a filmId (Int) and returns a FilmReview

What we’d like to do is be able to automatically link between these services - indicating that the filmId property on our Film object can be passed to the getFilmReview method.

To do this, we’re going to indicate that both these values mean the same thing. This is where Taxi starts to come in. Taxi is semantic type system, which allows us to say “This Thing is the same as That Thing”.

Taxi is semantic type system, which allows us to say “This Thing is the same as That Thing”.

First, let’s declare a type on our Films object:

import lang.taxi.annotations.DataType

data class Film(
    @field:DataType("FilmId")
    val id: Int,
    val title: String,
)

Then, let’s indicate on the getFilmReview method that the argument accepts the FilmId property:

import lang.taxi.annotations.DataType

@GetMapping("/films/{filmId}/review")
fun getFilmReview(@PathVariable("filmId") @DataType("FilmId") filmId: Int): FilmReview {
    return FilmReview(
        score = Random.nextInt(1,5),
        review = listOf("Good","Bad","Meh").random()
)
Orbital now understands the relationship of data between two services
Orbital now understands the relationship of data between two services

Given this, we can run a query fetching data automatically from both services. Orbital uses TaxiQL - a query language for asking for data declaratively.

Here’s a TaxiQL query asking for Films data, enriched with Reviews:

Play with this snippet by editing it here, or edit it on Taxi Playground

This query, in TaxiQL asks for data from two different sources. Orbital works out how to orchestrate our two APIs together, enriching our Films data with reviews.

Using typealias to keep our code DRY

Inside our Kotlin code, we just added a two @DataType("FilmId") annotations indicating that the two pieces of information were the same.

In Kotlin, we can extract those out to a type alias:

FilmId.kt
import lang.taxi.annotations.DataType

@DataType("FilmId")
typealias FilmId = Int

This lets us clean up our code, and make it more descriptive:

data class Film(
    val id: FilmId,
    val title: String,
)

and in our controller:


@GetMapping("/films/{filmId}/review")
fun getFilmReview(@PathVariable("filmId") filmId: FilmId): FilmReview {
    return FilmReview(
        score = Random.nextInt(1,5),
        review = listOf("Good","Bad","Meh").random()
)

Going further - Extracting shared types to a library

At this point, we’re successfully generating Taxi code directly from our Spring Boot services, and publishing to Orbital.

As you start to grow and scale, you need to think about how to structure common types for sharing across teams.

One of the principals of Taxi is to share types, not models, as this keeps systems decoupled, meaning when one API changes it’s model, other APIs are protected.

Therefore, you have a couple of options to help you grow:

  • If your team is entirely JVM based, you can extract your shared types out to a JAR, which teams depend on
  • More commonly, teams choose to move their core taxonomy types (the scalars - not the domain classes) into a dedicated Taxi project, and generate Java, Kotlin and other classes from there. Read more about generating app code from Taxi in our dedicated guide
Previous
Streaming data
Next
Generating app code from Taxi