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.