Describing schemas

Services

A service in Taxi describes APIs and data sources in your system. Services tell Taxi where to find data, whether that’s in REST APIs, databases, or message queues.

Here’s a basic example:

service CustomerApi {
   // Get a customer by their ID
   operation getCustomer(CustomerId):Customer
   
   // List all customers
   operation listCustomers():Customer[]
}

Why use services?

Services define data locations for Taxi queries. When querying for data like customers or orders, Taxi uses these definitions to determine which APIs to call.

Service Types

REST APIs

HTTP services can be defined using @HttpService, with a base URL that applies to all operations:

@HttpService(baseUrl = "https://api.example.com/v1")
service CustomerApi {
   // Resolves to GET https://api.example.com/v1/customers
   @HttpOperation(method = "GET", url = "/customers")
   operation listCustomers():Customer[]
   
   // Resolves to GET https://api.example.com/v1/customers/{id}
   @HttpOperation(method = "GET", url = "/customers/{id}")
   operation getCustomer( @PathVariable(name = "id") id: CustomerId ):Customer
}

Databases

Databases can be described using the table keyword:

service CustomerDb {
   // Defines a table containing Customer records
   table customers : Customer[]
   
   // Defines a table containing Order records
   table orders : Order[]
}

Database Details

Taxi is database-agnostic. Use annotations to specify connection details and database-specific features.

Event Streams

Streaming data sources can be defined using the stream keyword:

service EventHub {
   // Customer login event stream
   stream loginEvents(): Stream<CustomerLoginEvent>
   
   // Order update event stream
   stream orderUpdates(): Stream<OrderUpdateEvent>
}

Operations

Operations define the available methods on a service. Here are common operation types:

HTTP Operations

HTTP operations define REST endpoints:

service OrderApi {
   // GET request
   @HttpOperation(method = "GET", url = "/orders")
   operation listOrders():Order[]
   
   // POST with request body
   @HttpOperation(method = "POST", url = "/orders")
   operation createOrder( @HttpRequestBody order: CreateOrderRequest ):Order
   
   // PUT with path parameter
   @HttpOperation(method = "PUT", url = "/orders/{orderId}/status")
   operation updateStatus(
      @PathVariable(name = "orderId") id: OrderId,
      @HttpRequestBody status: OrderStatus
   ):Order
}

Operation Contracts

Operations can specify contracts about their behavior:

model Money {
   currency : CurrencyCode
   amount : Decimal
}

// Contract ensures output currency matches targetCurrency
operation convertCurrency(
   input: Money,
   targetCurrency: CurrencyCode
) : Money(currency = targetCurrency)

// Contract specifies output derives from input
operation normalizeAddress(
   input: Address
) : NormalizedAddress( from input )

Contract Validation

Contracts declare expected behavior. Implementation and validation are handled by the underlying systems, not by Taxi.

Write operations (Mutations)

TaxiQL distinguishes between read operations (which fetch data) and write operations (which modify data).

Write operations must be explicitly declared and called, providing more control over when modifications occur.

Declaring write operations

Write operations are declared using the write operation keyword in a service:

service PersonService {
   // Read operation - can be called implicitly during queries
   operation findPerson(PersonId):Person
   
   // Write operation - must be explicitly called
   write operation updatePerson(Person):Person
}

Write vs Read Operations

Standard operations can be called automatically by TaxiQL when resolving queries. Write operations require explicit calls to ensure mutations happen only when intended.

Using write operations

Write operations are invoked explicitly by using a call statement:

Basic call

query UpdatePerson {
   given { person : Person = { id: "123", name: "Alice" } }
   call PersonService::updatePerson
}

After finding data

query UpdatePeople {
   find { Person }
   call PersonService::updatePerson
}

Write operation parameters

TaxiQL engines automatically construct operation inputs using available data from the query context.

This means you don’t need to exactly match the operation’s parameter structure in your given or find statements:

// Operation signature
write operation updatePerson(Person):Person

query UpdatePersonName {
   // TaxiQL will construct a Person object using available fields
   given { 
      id: PersonId = "123"
      name: PersonName = "Alice"
   }
   call PersonService::updatePerson
}

Data Matching

The TaxiQL engine looks at the write operation's parameter types and constructs the required input using any matching data available from previous `given` or `find` statements.

Additional Write Operation Control

TaxiQL engines like Orbital use annotations to provide additional control over write operations:

service CustomerDatabase {
   // Specify database operation type
   @UpsertOperation
   write operation saveCustomer(Customer):Customer
}

Common annotations include:

  • Database operation types (insert/update/upsert/delete)
  • Message queue destinations
  • Batch processing configurations

Implementation Specific

Available annotations and their effects depend on your TaxiQL engine. Consult your engine's documentation for supported annotations.

Service Lineage

Services can document their dependencies and data persistence:

service CustomerService {
   lineage {
      // Declares operation dependency
      consumes operation addresses.AddressService.validateAddress
      
      // Declares persisted data types
      stores Customer
      stores CustomerPreferences
   }
   
   operation findCustomer(CustomerId):Customer
}

Lineage Benefits

Service lineage helps track data flow, maintain documentation, and integrate with data catalogs.
Previous
Models
Next
Querying