Describing schemas
Semantic Types
Core Principle
Share Semantics, Not Structure
Types vs Models: Understanding the Distinction
Semantic Types for Meaning
Semantic types in Taxi are designed to communicate meaning:
type EmailAddress inherits String
type CustomerId inherits Int
These types describe what the data means, without enforcing how it should be structured.
Models for Structure
Models, on the other hand, define specific data structures:
model Customer {
id: CustomerId
email: EmailAddress
}
Avoid Fields in Types
Why This Matters
Consider two systems exchanging customer data:
Tightly Coupled (Wrong Way):
// Shared structural contract - both systems must agree on exact structure
model Customer {
id: String
email: String
firstName: String
lastName: String
}
Loosely Coupled (Taxi Way):
// Shared semantic types
type CustomerId inherits String
type EmailAddress inherits String
type FirstName inherits String
type LastName inherits String
// System A's model
model CustomerV1 {
id: CustomerId
contact: EmailAddress
name: FirstName
}
// System B's model
model CustomerRecord {
customerId: CustomerId
firstName: FirstName
lastName: LastName
emailAddress: EmailAddress
}
In the loosely coupled example:
- Systems agree on what the data means (semantics)
- Systems can evolve their structures independently
- Data can still be mapped between systems based on semantic meaning
- Changes to one system’s model don’t force changes in other systems
Key Takeaway
Basic Concepts
What Makes a Type Semantic?
A semantic type adds meaning to a primitive type through inheritance:
// Basic semantic type definition
type CustomerName inherits String
// More specific semantic types can inherit from other semantic types
type PreferredCustomerName inherits CustomerName
In this example:
String
describes how the data is representedCustomerName
describes what the data meansPreferredCustomerName
further refines the meaning
Best Practice: Avoid fields in types
Why Use Semantic Types?
Consider these two operation signatures:
// Without semantic typing - unclear what the String represents
operation getCustomer(String):String
// With semantic typing - clear contract of meaning
operation getCustomer(EmailAddress):CustomerName
The semantic version clearly shows:
- Input is specifically an email address
- Output is specifically a customer’s name
- Tools can use this information for validation and discovery
Creating Semantic Types
Basic Syntax
// Simple type definition
type Age inherits Int
// With documentation
[[ The legal name of a person as it appears on official documents ]]
type LegalName inherits String
// With format specification (for dates)
type DateOfBirth inherits Date(@format = "yyyy-MM-dd")
Type Inheritance Hierarchies
Building Type Hierarchies
// Base type for any kind of identifier
type Identifier inherits String
// More specific identifier types
type CustomerId inherits Identifier
type OrderId inherits Identifier
// Even more specific
type PreferredCustomerId inherits CustomerId
Best Practices
Keep Types Focused
Create types that represent single, clear concepts:
// Good - clear, single concept
type EmailAddress inherits String
// Bad - mixing concepts
type EmailAndPhone {
email: String
phone: String
}
Single Responsibility
Avoid fields in types
While Taxi allows types to contain fields, it’s generally discouraged for semantic types.
Types are intended for sharing between systems. When systems share models (which define structure), they become tightly coupled.
However, if systems use semantics to communicate requirements, then they can consume data without becoming tightly coupled to a producer’s contract. This means that as producers change, consumers don’t need updating. If you find yourself adding fields to a type, consider using a model instead.
Use Inheritance Meaningfully
Build hierarchies that reflect real semantic relationships:
// Good - meaningful hierarchy
type PhoneNumber inherits String
type MobilePhoneNumber inherits PhoneNumber
type WorkPhoneNumber inherits PhoneNumber
// Bad - inheritance doesn't add meaning
type CustomerData inherits String // What does this mean?
Add documentation
Add documentation to explain the purpose and usage:
[[
ISO 3166-1 alpha-2 country code.
Example: "US", "GB", "FR"
]]
type CountryCode inherits String
Common Patterns
Identifiers
// Base identifier type
type Id inherits String
// Specific identifier types
type OrderId inherits Id
type CustomerId inherits Id
type ProductId inherits Id
Examples
Services
Services can specify exact semantic types they accept and return:
service PaymentService {
operation processPayment(
amount: Money,
currency: Currency,
accountId: AccountId
): TransactionId
}
Queries
TaxiQL queries can leverage semantic types for precise data discovery:
find { Transaction[] } as {
id: TransactionId
amount: Money
// System knows to look for Currency conversion if needed
amountInUsd: Money(currency = "USD")
}[]