Describing schemas
Models
Overview
Models in Taxi define specific data structures used by services or applications. Unlike semantic types which define meaning, models describe how data is organized.
Share Semantics, Not Structure
Basic syntax
model Person {
id: PersonId
firstName: FirstName
lastName: LastName
email: EmailAddress? // Optional field
friends: Person[] // Array of Person
}
Key features:
- Fields can use semantic types
?
marks optional (nullable) fields- Arrays are supported using
[]
syntax - Documentation can be added using
[[ ]]
Parameter and closed models
Taxi lets you control how models can be constructed and returned using the parameter
and closed
modifiers.
Parameter models
Parameter models explicitly indicate that an object can be constructed as input to a service:
parameter model CreateCustomerRequest {
firstName: FirstName
lastName: LastName
email: EmailAddress
}
service CustomerService {
operation createCustomer(CreateCustomerRequest): Customer
}
Closed models
Closed models can only be returned from services - they cannot be constructed by combining fields from other sources:
closed model CustomerRecord {
id: CustomerId
internalId: InternalId // System-generated
lastModified: Timestamp // System-managed
}
service CustomerService {
operation getCustomer(CustomerId): CustomerRecord
}
Parameter and closed combined
Some models need to be both parameter and closed. This is common with database tables, where the model:
- Can be used as input for insert/update operations (parameter)
- Can be returned from queries (closed)
- Should not be constructed by combining fields from other sources (closed)
parameter closed model Customer {
id: CustomerId
name: CustomerName
email: EmailAddress
}
service CustomerDatabase {
// Can be used as input
operation insertCustomer(Customer): CustomerId
// Can be returned from queries
operation findCustomer(CustomerId): Customer
// Declared as a table
table customers: Customer[]
}
Model Resolution
Partial Models
A partial model creates a new type where all fields from an existing model become optional. This is particularly useful for PATCH operations in REST APIs, where you want to update only specific fields of a resource.
Basic Usage
model Person {
name: Name inherits String
age: Age inherits Int
}
partial model PartialPerson from Person
The generated PartialPerson
will have all fields made optional (nullable):
model PartialPerson {
name: Name?
age: Age?
}
Nested types in partials
When a model contains references to other models or arrays, the compiler automatically creates partial versions of those types too:
model Pet {
name: PetName inherits String
age: Age inherits Int
parent: Pet // Self-referential field
}
model Person {
name: Name inherits String
pet: Pet // Reference to another model
deadPets: Pet[] // Array of models
}
partial model PartialPerson from Person
This expands to:
model PartialPerson {
name: Name? // Primitive types become nullable
pet: PartialPet? // Model references become partial and nullable
deadPets: PartialPet[]? // Arrays become nullable arrays of partial types
}
model PartialPet {
name: PetName?
age: Age?
parent: PartialPet? // Self-references become partial too
}
Inheritance and Modifiers
Partial models:
- Do not inherit from their reference type
- Inherit all annotations and modifiers from the reference type
- Can add new annotations and modifiers
- Cannot modify the structure beyond making fields optional
- Cannot be declared as
closed
(the compiler will remove anyclosed
modifiers on the created Partial model)
Example with additional modifiers:
model Person {
name: Name inherits String
}
@OmitNulls
partial parameter model PartialPerson from Person
Field types
Basic fields
model Customer {
// Required fields
id: CustomerId
name: CustomerName
// Optional field
nickname: Nickname?
// Array field
orders: Order[]
// Map field
preferences: Map<PreferenceKey, PreferenceValue>
}
Nested objects
model Order {
orderId: OrderId
// Inline object definition
address: {
street: StreetAddress
city: City
country: CountryCode
}
// Alternative: reference a defined model
shippingAddress: Address
}
Inheritance
While possible, model inheritance should be used sparingly:
model PersonBase {
id: PersonId
firstName: FirstName
lastName: LastName
}
model Employee inherits PersonBase {
employeeId: EmployeeId
department: DepartmentName
}
Model Inheritance
Best practices
Models are typically defined by producing systems - such as REST APIs, databases, or message queues.
The best practices here focus on describing those models using Taxi, rather than the practices of designing good APIs or event payloads.
Use semantic types
// Good - uses semantic types
model Customer {
id: CustomerId
email: EmailAddress
}
// Bad - uses primitive types
model Customer {
id: String // What kind of ID?
email: String // Is this really an email?
}
Document nullability
model User {
// Required fields - core identity
id: UserId
email: EmailAddress
// Optional fields - additional information
middleName: MiddleName?
phoneNumber: PhoneNumber?
}
Organization
// Service-specific models together
model CreateOrderRequest {
customerId: CustomerId
items: OrderItem[]
}
model CreateOrderResponse {
orderId: OrderId
status: OrderStatus
}
// Service definition with its models
service OrderService {
operation createOrder(CreateOrderRequest): CreateOrderResponse
}
Common patterns
Request/response models
parameter model SearchRequest {
query: SearchQuery
maxResults: MaxResults?
offset: Offset?
}
closed model SearchResponse {
results: SearchResult[]
totalCount: TotalCount
hasMore: Boolean
}
Event models
closed model CustomerEvent {
eventId: EventId
timestamp: EventTimestamp
customerId: CustomerId
type: EventType
payload: CustomerEventPayload
}
View models
closed model CustomerSummary {
id: CustomerId
name: CustomerName
status: CustomerStatus
// Computed field
isActive: Boolean by (this.status == CustomerStatus.ACTIVE)
}