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

Models represent structural contracts and should not be shared between systems. Instead, share semantic types and let each system define its own models using those types. This enables loose coupling between systems.

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

By default (without parameter or closed), TaxiQL will: 1. Look for an exact instance of the model 2. Try to construct it by combining known values 3. Look for services that can return it

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 any closed 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

Prefer composition over inheritance for models. Inheritance can create tight coupling between models, which goes against Taxi's principle of loose coupling.

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)
}
Previous
Semantic types
Next
Services