# Publishing schemas from Spring Boot applications

> Learn how to automatically publish Taxi schemas from your Spring Boot microservices to Orbital

Source: https://orbitalhq.com/docs/sdks/java-and-kotlin/spring-boot-schema-publisher

_Available since 0.37.0-M3_

The Spring Boot Schema Publisher starter automatically publishes Taxi schemas from your Spring Boot applications to Orbital on startup.

This is useful for microservices architectures where individual services own their own schema definitions and need to automatically register them with a central Orbital instance.

## Adding the dependency

Add the starter dependency to your Spring Boot application's `pom.xml`:

```xml
<dependency>
    <groupId>com.orbitalhq</groupId>
    <artifactId>schema-publisher-spring-boot-starter</artifactId>
    <version>${orbital.version}</version>
</dependency>
```

You also need to declare the Orbital repository:

```xml
<repositories>
  <repository>
    <id>orbital-releases</id>
    <url>https://repo.orbitalhq.com/release</url>
  </repository>
  
  {/* snapshot is only required if you're using snapshots */}
  <repository>
    <id>orbital-snapshots</id>
    <url>https://repo.orbitalhq.com/snapshot</url>
    <snapshots>
      <enabled>true</enabled>
    </snapshots>
  </repository>
</repositories>
```

Or for Gradle (`build.gradle`):

```groovy
implementation 'com.orbitalhq:schema-publisher-spring-boot-starter:${orbitalVersion}'
```

Once added, the starter will automatically:
- Discover Taxi schemas in your application at startup
- Publish them to the configured Orbital server

## Configuration
The project uses reasonable defaults, so generally the only properties that requires configuration are:
 - The base URL of your server
 - The publisherId (which defaults to `${spring.application.name}`)  

Defaults can be overridden by configuring the publisher in your `application.yml`:

```yaml
orbital:
  schema:
    publisher:
      enabled: true                          # Enable/disable publishing (default: true)
      base-url: https://my-service-prod/     # Sets the base URL, where Orbital should try to call this application
      publisher-id: my-service               # Publisher ID (default: ${spring.application.name})
      orbital-url: http://localhost:9022             # Orbital server URL (default: http://localhost:9022)
      connection-timeout: 5000               # Connection timeout in ms (default: 5000)
```

Or in `application.properties`:

```properties
orbital.schema.publisher.enabled=true
orbital.schema.publisher.base-url=https://my-service-prod/
orbital.schema.publisher.publisher-id=my-service
orbital.schema.publisher.orbital-url=http://localhost:9022
orbital.schema.publisher.connection-timeout=5000
```

### Configuration properties

| Property | Description | Default |
|----------|-------------|---------|
| `orbital.schema.publisher.enabled` | Enable or disable schema publishing | `true` |
| `orbital.schema.publisher.base-url` | The base url for this spring boot application | Not set |
| `orbital.schema.publisher.publisher-id` | Unique identifier for this publisher | Value of `spring.application.name` |
| `orbital.schema.publisher.orbital-url` | URL of the Orbital server | `http://localhost:9022` |
| `orbital.schema.publisher.connection-timeout` | Connection timeout in milliseconds | `5000` |
| `orbital.schema.publisher.auth.headers` | Map of HTTP headers to send on every schema submission. See [Authentication](#authentication). | Not set |

## Authentication

If your Orbital instance sits behind an authenticating proxy (or any reverse proxy that requires a token, API key, or tenant header), you can configure arbitrary headers to be sent on every schema submission.

Headers are configured as a map under `orbital.schema.publisher.auth.headers`, and apply to **both HTTP and WebSocket transports**. For WebSocket transport, headers are sent on the connection handshake, and are re-sent automatically on every reconnect.

```yaml
orbital:
  schema:
    publisher:
      auth:
        headers:
          Authorization: "Bearer eyJhbGc..."
          X-Tenant-Id: "acme-corp"
```

Or in `application.properties`:

```properties
orbital.schema.publisher.auth.headers.Authorization=Bearer eyJhbGc...
orbital.schema.publisher.auth.headers.X-Tenant-Id=acme-corp
```


Avoid checking real tokens into `application.yml`. Use environment variable substitution or a secrets manager:

```yaml
orbital:
  schema:
    publisher:
      auth:
        headers:
          Authorization: "Bearer ${ORBITAL_API_TOKEN}"
```


If no `auth` block is configured, no authentication headers are sent — this is the default behaviour.

## HTTP vs WebSocket transport

The transport protocol is selected automatically based on the URL scheme configured in `orbital-url`:

 - `http://` or `https://` -- uses HTTP transport
 - `ws://` or `wss://` -- uses WebSocket transport

### HTTP transport (default)

With HTTP, schemas are published as a one-shot POST request to Orbital on startup. This is the simplest option and works well for development and testing.

**Warning: Schemas may be lost if Orbital restarts**
Orbital is designed to be stateless and deployable from git, so published schemas are held in-memory rather than persisted to disk.

In a clustered deployment, published schemas are replicated across nodes, so they survive individual node restarts. However, if the entire cluster is restarted (or a single-node instance is restarted), published schemas are lost. The publisher is not notified and will not re-publish automatically.

For production deployments, consider using WebSocket transport instead.

### WebSocket transport (recommended for production)

_Available since 0.37.0-M4_

WebSocket transport maintains a persistent connection between your service and Orbital. If the connection is lost (e.g., Orbital restarts), the publisher automatically reconnects and re-publishes the schema.

To enable WebSocket transport, change the URL scheme to `ws://` or `wss://`:

```yaml
orbital:
  schema:
    publisher:
      orbital-url: ws://orbital.production.acme.com
```

The WebSocket connection uses the same port as Orbital's HTTP API -- no additional ports or configuration are required on the Orbital side.

If the connection cannot be established immediately (e.g., Orbital is not yet running), the publisher will retry automatically with a configurable interval:

```yaml
orbital:
  schema:
    publisher:
      orbital-url: ws://localhost:9022
      retry-interval: 5s   # How long to wait between reconnection attempts (default: 3s)
```

## Schema generation
At runtime, the plugin will introspect Spring `@RestController` and `@Service` components, and automatically generate Taxi `service` and `operation` schemas, along with the associated
models for return types an inputs.

If you're using Taxi annotations on your Java/Kotlin classes, these will be discovered and converted to Taxi schemas:

```java
import lang.taxi.annotations.DataType;

@DataType("com.acme.payments.PaymentRequest")
public class PaymentRequest {

    @DataType("com.acme.payments.PaymentId")
    private String paymentId;

    @DataType("com.acme.payments.Amount")
    private BigDecimal amount;

    // getters and setters...
}
```

### Using semantic types in code
When the schema is generated, type information is read at runtime. By default, only raw primitive types are available (such as `String`, `Int`, `Decimal`, etc).

To use richer semantic types, you can use the `@DataType` annotation (`lang.taxi.annotations.DataType`)

```java
import lang.taxi.annotations.DataType;

@DataType("com.acme.payments.PaymentRequest")
public class PaymentRequest {

    @DataType("com.acme.payments.PaymentId")
    private String paymentId;

    @DataType("com.acme.payments.Amount")
    private BigDecimal amount;
}
```

#### Importing vs Declaring types
By default, types declared with `@DataType` are declared as new types in the generated schema.

eg - the above code snippet would result in the following Taxi being generated:

```taxi
   namespace com.acme.payments

>   // Types are declared  
>   type PaymentId inherits String
>   type Amount inherits Decimal
   
   model PaymentRequest {
     paymentId: PaymentId
     amount: Amount
   }
```

If you want to use existing types already decalared in a shared taxonomy, then declare the `@DataType` as imported:

```java
@DataType("com.acme.payments.PaymentRequest")
public class PaymentRequest {

    @DataType(value = "com.acme.payments.PaymentId", imported = true)
    private String paymentId;

    @DataType(value = "com.acme.payments.Amount", imported = true)
    private BigDecimal amount;
}
```

This would generate the following:

```taxi
>  // Types are imported
>  import com.acme.payments.Amount
>  import com.acme.payments.PaymentId
   
   namespace com.acme.payments
   
   model PaymentRequest {
     paymentId: PaymentId
     amount: Amount
   }
```

A good rule of thumb is that **Types should be imported (shared), whereas models should be published (declared by your service)**. This
aligns with the Taxi best practice of share types, not models.

#### Kotlin Type Aliases
You can take advantage of Kotlin type aliases to make your code more semantic, and cleaner:

```kotlin
@DataType(value = "com.acme.films.RentalDuration", imported = true)
typealias RentalDuration = Int
 
 
// elsewhere
data class FilmRental(duration: RentalDuration) 
```

### Using generated Taxi types
Taxi supports generating Java and Kotlin code, using the [Kotlin plugin](https://taxilang.org/docs/taxi-cli/kotlin-plugin). 

This plugin will generate semantic types into a reusable library, which you can import into your spring boot microservice.

For example, given the following taxi code:

```taxi
namespace films

[[ The Id of a film ]]
type FilmId inherits Int

[[ The title of the film ]]
type Title inherits String
```

In a Taxi project with the Kotlin plugin enabled:

```hocon
name: com.petflix/films
version: 0.1.0
sourceRoot: src/
plugins: {
   taxi/kotlin: {
    maven: {
       groupId: "com.petflix"
       artifactId: "films-taxonomy"
    }
   }
}
```

When `taxi build` is run, a full Maven project is generated, which you can publish to your local Maven repository, and then
pull into your Spring boot project.

```kotlin
@RestController
class FilmsApi {

   @GetMapping("/films/{id}")
   fun findById(id: FilmId): Film {
      // implementation
   }
}
```

For more information, consult the docs on Taxi's [Kotlin plugin](https://taxilang.org/docs/taxi-cli/kotlin-plugin) 

## Disabling the publisher

To disable schema publishing (useful for local development or testing), set:

```yaml
orbital:
  schema:
    publisher:
      enabled: false
```

You can also use Spring profiles to control when publishing is enabled:

```yaml
# application-prod.yml
orbital:
  schema:
    publisher:
      enabled: true
      orbital-url: https://orbital.production.acme.com

# application-dev.yml
orbital:
  schema:
    publisher:
      enabled: false
```

## Environment-specific configuration

Use environment variables or Spring's configuration management to configure different Orbital servers per environment:

```yaml
orbital:
  schema:
    publisher:
      orbital-url: ${ORBITAL_SERVER_URL:http://localhost:9022}
      base-url: ${THIS_SERVICE_BASE_URL}
```

Then set the environment variable in your deployment:

```bash
export ORBITAL_SERVER_URL=https://orbital.production.acme.com
```

## Troubleshooting

### Publisher fails to connect on startup

If the Orbital server is not available when your application starts, the publisher will log an error but will not prevent your application from starting. 

The publisher continues to retry to publish to Orbital.

To adjust the connection timeout:

```yaml
orbital:
  schema:
    publisher:
      connection-timeout: 10000  # 10 seconds
```

## Next steps

Once your schemas are published to Orbital, you can:
- [Query your services using Taxi](/docs/querying/writing-queries)
- [Combine data from multiple microservices](/docs/guides/composing-api-and-database)
- [Set up streaming queries](/docs/querying/streaming-data)
- [Publish queries as HTTP endpoints](/docs/querying/queries-as-endpoints)
