Type System and Extension
In the previous section, we introduced the basic concept of types in Blue. Now, let's explore the type system more deeply, seeing how it enables powerful inheritance and extension models while maintaining strict compatibility.
For clarity, most examples in this documentation omit the blue:
directive that would typically be required in practice. Learn more about this directive in the Blue Directive section.
Building Type Hierarchies
Blue's type system allows you to create sophisticated inheritance hierarchies where more specialized types extend general ones:
name: Product
price:
amount:
type: Number
contracts:
amountValidator:
type: Schema Validator
minimum: 0
currency:
type: ISO-4217 Currency Code
sku:
type: Text
The contracts
section introduces an important Blue concept: contracts define special behaviors for documents or fields. Here, the Schema Validator contract enforces constraints on the price.amount
field. You'll learn more about contracts in the Contracts section.
Note that all fields in Blue are optional by default. If you want to make a field required, you need to add that constraint explicitly:
name: Electronics
type: Product
manufacturer:
type: Text
contracts:
requiredFieldValidator:
type: Schema Validator
required: true # This field must be present
Specialization with Constraints
Let's create more specialized product types by adding both fields and constraints:
name: Smartphone
type: Electronics
screenSize:
type: Number
contracts:
screenSizeValidator:
type: Schema Validator
minimum: 3.5
maximum: 7.5
operatingSystem:
type: Text
contracts:
osOptionsValidator:
type: Schema Validator
options: ['iOS', 'Android']
You can also define field types separately for reuse:
name: Operating System
type: Text
contracts:
osOptionsValidator:
type: Schema Validator
options: ['iOS', 'Android']
---
name: Smartphone
type: Electronics
screenSize:
type: Number
contracts:
screenSizeValidator:
type: Schema Validator
minimum: 3.5
maximum: 7.5
operatingSystem:
type: Operating System
Both approaches achieve the same result, giving you flexibility in how you organize your types.
Let's continue building our hierarchy:
name: iPhone
type: Smartphone
operatingSystem: iOS # Fixed value for all iPhones
model:
type: Text
contracts:
modelValidator:
type: Schema Validator
required: true
Inheriting and Adding Contracts
When you extend a type, you inherit all its contracts. To add additional constraints to fields, use new contracts with descriptive names:
name: Premium Smartphone
type: Smartphone
price:
amount:
contracts:
premiumPriceValidator:
type: Schema Validator
minimum: 599.99 # Additional constraint on price amount
When resolved, the price.amount
field will have both the amountValidator
from Product
(requiring ≥ 0) and the premiumPriceValidator
from Premium Smartphone
(requiring minimum 599.99). Together, these effectively restrict price to be minimum 599.99 if present.
This ability to progressively add constraints is powerful for modeling domain-specific rules that build upon more general ones.
Multi-Level Inheritance
Blue supports multi-level inheritance, allowing you to create deeply specialized types:
name: iPhone 14
type: iPhone
model: '14' # Fixed value for all iPhone 14s
storage:
type: Text
contracts:
storageOptionsValidator:
type: Schema Validator
options: ['128GB', '256GB', '512GB', '1TB']
When we create an instance:
name: iPhone 14 128GB
type: iPhone 14
storage: '128GB'
price:
amount: 799.99
currency: USD
color: Blue
The document inherits all properties from the entire inheritance chain, including the fixed values operatingSystem: iOS
from the iPhone
type and model: "14"
from the iPhone 14
type.
Type Enforcement Rules
The Blue type system enforces strict rules during inheritance:
- No Overriding Values: If a parent type defines a field value, child types inherit exactly that value and cannot change it
- Type Compatibility: If a parent type defines a field's type, child types must use the same or a compatible subtype
- Additive Properties Only: Child types can add new properties but cannot remove inherited ones
For example, these would be invalid:
# INVALID - trying to change inherited fixed value
name: InvalidiPhone
type: iPhone
operatingSystem: Android # Cannot override 'iOS' value from iPhone type
# INVALID - trying to change the type of 'price.amount'
name: InvalidProduct
type: Product
price:
amount:
type: Text # Cannot override Number type from Product
These strict rules ensure that a fundamental principle holds true: Every instance of a subtype is also a valid instance of its parent type. This means every "iPhone 14" is an "iPhone", every "iPhone" is a "Smartphone", and so on up the type hierarchy.
This principle—known as the Liskov Substitution Principle in object-oriented design—is strictly enforced in Blue's type system.
Working with Collections
Blue supports two primary collection types—lists and dictionaries—that let you build complex data structures:
# A product bundle with a list of items
name: Starter Tech Bundle
bundlePrice:
amount: 1299.99
currency: USD
items:
type: List
itemType: Product
- type: Smartphone
name: iPhone 14
model: "14"
storage: "128GB"
price:
amount: 799.99
currency: USD
- type: Electronics
name: Wireless Earbuds
manufacturer: Apple
price:
amount: 249.99
currency: USD
- type: Electronics
name: Wireless Charger
manufacturer: Belkin
price:
amount: 49.99
currency: USD
# Inventory management with product counts
name: Store Inventory
stock:
type: Dictionary
keyType: Text
valueType: Integer
'iPhone 14 128GB Black': 23
'iPhone 14 256GB Black': 15
'iPhone 14 Pro 128GB Silver': 8
'Samsung Galaxy S23': 12
These examples demonstrate how collections enable complex data modeling while maintaining Blue's strict typing:
- The
items
list can contain anyProduct
subtype - The
stock
dictionary maps product names to their inventory counts
Programming Language Integration
Blue types map cleanly to object-oriented programming concepts:
// Define classes mapped to Blue types
@TypeBlueId("Price-BlueId")
public class Price {
private Double amount;
private String currency;
}
@TypeBlueId("Product-BlueId")
public class Product {
private String name;
private String description;
private Price price;
private String sku;
}
// Specialization with enum for type-safe values
public enum OperatingSystem {
IOS, ANDROID
}
@TypeBlueId("Smartphone-BlueId")
public class Smartphone extends Product {
private Double screenSize;
private OperatingSystem operatingSystem;
}
// Converting between YAML and Java objects
String phoneYaml = """
name: iPhone 14
type: Smartphone
price:
amount: 799.99
currency: USD
screenSize: 6.1
operatingSystem: IOS
""";
// Convert YAML to Java object
Blue blue = new Blue(nodeProvider);
Smartphone phone = blue.yamlToObject(phoneYaml, Smartphone.class);
// Verify the conversion
assert phone.getName().equals("iPhone 14");
assert phone.getPrice().getAmount() == 799.99;
assert phone.getPrice().getCurrency().equals("USD");
assert phone.getOperatingSystem() == OperatingSystem.IOS;
This bidirectional mapping ensures that Blue documents can be seamlessly converted to strongly-typed language objects and back again.
Resolving Documents
When a Blue processor encounters a document with type references, it performs "resolution" to fully expand all types:
# Before resolution
name: iPhone 14 Pro 256GB Black
type: iPhone 14
storage: "256GB"
color: Black
price:
amount: 1099.99
currency: USD
# After resolution (blueId: 9pq5Enj1LFqQskQShJwK9ERU8u2CLfnP2KbeaufYJyNm)
name: iPhone 14 Pro 256GB Black
type:
name: iPhone 14
type:
name: iPhone
type:
name: Smartphone
type:
name: Electronics
type:
name: Product
price:
amount:
type: Number
contracts:
amountValidator:
type: Schema Validator
minimum: 0
currency:
type: ISO-4217 Currency Code
sku:
type: Text
manufacturer:
type: Text
contracts:
requiredFieldValidator:
type: Schema Validator
required: true
screenSize:
type: Number
contracts:
screenSizeValidator:
type: Schema Validator
minimum: 3.5
maximum: 7.5
operatingSystem: iOS
model: "14"
storage:
type: Text
contracts:
storageOptionsValidator:
type: Schema Validator
options: ["128GB", "256GB", "512GB", "1TB"]
storage: "256GB"
color: Black
price:
amount: 1099.99
currency: USD
Resolution creates a fully expanded document that contains all inherited properties and constraints. This ensures consistent validation and behavior across different processors.
You can perform resolution using the Blue library:
// Parse the document
Blue blue = new Blue(nodeProvider);
Node node = blue.yamlToNode(yaml);
// Resolve types
Node resolved = blue.resolve(node);
// Convert back to YAML
String resolvedYaml = blue.nodeToYaml(resolved);
Verifying Type Relationships
You can check if one document is a valid subtype of another:
boolean isIPhone = blue.nodeMatchesType(myPhone, iPhone); // true
boolean isSmartphone = blue.nodeMatchesType(myPhone, smartphone); // true
boolean isProduct = blue.nodeMatchesType(myPhone, product); // true
This enables type-safe operations in your applications when working with Blue documents.
Next Steps
Now that you understand Blue's type system, let's explore how BlueId creates a universal web of interconnected documents.