Protocol
ZAP schema design and message format
Protocol
ZAP uses a zero-copy binary format for message serialization. This page covers schema design, best practices, and the wire format.
ZAP Format Basics
ZAP's data serialization format requires no parsing. Data is laid out in memory exactly as it appears on the wire.
Why Zero-Copy?
| Feature | Benefit |
|---|---|
| Zero-copy | No parsing overhead |
| Forward/backward compatible | Safe schema evolution |
| Strongly typed | Compile-time safety |
| Pointer-based | Efficient for nested structures |
Schema Definition
Basic Types
ZAP provides these primitive types:
struct Example
# Integers
int8Val Int8
int16Val Int16
int32Val Int32
int64Val Int64
uint8Val UInt8
uint16Val UInt16
uint32Val UInt32
uint64Val UInt64
# Floating point
float32Val Float32
float64Val Float64
# Other primitives
boolVal Bool
textVal Text
dataVal DataStructs
Define complex types with structs:
struct Person
name Text
age Int32
email Text
address Address
struct Address
street Text
city Text
country Text
zipCode TextLists
Lists can contain any type:
struct Order
items List(Item)
quantities List(Int32)
tags List(Text)Enums
Define enumerated types:
enum Status
pending
active
completed
cancelled
struct Task
title Text
status StatusUnions
Use unions for variant types:
struct Result
union
success Data
error Text
struct Shape
union
circle Circle
rectangle Rectangle
triangle TriangleInterface Definitions
Basic Interface
interface Calculator
add (a Float64, b Float64) -> (result Float64)
subtract (a Float64, b Float64) -> (result Float64)
multiply (a Float64, b Float64) -> (result Float64)
divide (a Float64, b Float64) -> (result Float64)Streaming Methods
Use stream keyword for streaming responses:
interface DataService
# Unary method
getData (id Text) -> (data Data)
# Server streaming
streamData (query Query) -> stream (chunk DataChunk)
# Client streaming
uploadData stream (chunk DataChunk) -> (status UploadStatus)
# Bidirectional streaming
processStream stream (input InputChunk) -> stream (output OutputChunk)Nested Interfaces
Interfaces can return other interfaces:
interface Database
openTable (name Text) -> (table Table)
interface Table
get (key Data) -> (value Data)
put (key Data, value Data) -> ()
delete (key Data) -> ()
scan (prefix Data) -> stream (entry Entry)Schema Evolution
Adding Fields
Add new fields with new ordinals:
# Version 1
struct User
name Text
email Text
# Version 2 (compatible)
struct User
name Text
email Text
phone Text # New field
verified Bool # New fieldDefault Values
Provide defaults for optional fields:
struct Config
timeout UInt32 = 30
retries UInt32 = 3
debugMode Bool = falseDeprecation
Mark fields as deprecated:
struct User
name Text
email Text
oldField Text $deprecated # Don't use in new codeWire Format
Message Structure
┌─────────────────────────────────────┐
│ Segment Table │
│ ┌─────────────────────────────────┐ │
│ │ Segment Count (4 bytes) │ │
│ │ Segment 0 Size (4 bytes) │ │
│ │ Segment 1 Size (4 bytes) │ │
│ │ ... │ │
│ └─────────────────────────────────┘ │
├─────────────────────────────────────┤
│ Segment 0 Data │
├─────────────────────────────────────┤
│ Segment 1 Data │
├─────────────────────────────────────┤
│ ... │
└─────────────────────────────────────┘ZAP Header
ZAP adds a header before the message body:
┌───────────────────────────────────────────────────┐
│ ZAP Header (40 bytes) │
├─────────────┬─────────────┬───────────────────────┤
│ Magic │ Version │ Message ID │
│ (8 bytes) │ (4 bytes) │ (8 bytes) │
├─────────────┼─────────────┼───────────────────────┤
│ Method ID │ Flags │ Payload Len │
│ (4 bytes) │ (4 bytes) │ (4 bytes) │
├─────────────┴─────────────┴───────────────────────┤
│ Payload Pointer + Length (8 bytes) │
└───────────────────────────────────────────────────┘Compression
Enable compression for large messages:
server := zap.NewServer(
zap.WithCompression(zap.CompressionZstd),
zap.WithCompressionThreshold(1024), // Compress if > 1KB
)Code Generation
Generate Code
# Generate all languages
zap generate schema.zap --out=./gen
# Generate specific language
zap generate schema.zap --lang=go --out=./gen/go
zap generate schema.zap --lang=rust --out=./gen/rust
zap generate schema.zap --lang=ts --out=./gen/tsGenerated Types
For this schema:
struct Person
name Text
age Int32Go generates:
type Person struct {
zap.Struct
}
func (p Person) Name() (string, error)
func (p Person) SetName(v string) error
func (p Person) Age() int32
func (p Person) SetAge(v int32)Rust generates:
pub struct Person<'a> {
reader: zap::StructReader<'a>,
}
impl<'a> Person<'a> {
pub fn get_name(&self) -> zap::Result<&'a str>;
pub fn get_age(&self) -> i32;
}Best Practices
1. Use Meaningful Field Numbers
Field numbers are permanent. Plan for future expansion:
struct User
# Core fields: 0-9
id UInt64
name Text
email Text
# Profile fields: 10-19
avatar Data
bio Text
# Settings: 20-29
theme Text
language Text2. Prefer Structs Over Lists of Primitives
# Avoid
struct Point
coords List(Float64) # What do indices mean?
# Prefer
struct Point
x Float64
y Float64
z Float643. Use Groups for Logical Organization
struct Employee
name Text
contact group
email Text
phone Text
employment group
title Text
department Text
startDate UInt644. Document Your Schema
# User represents a registered user in the system.
# Users are created via the Registration service and
# authenticated via the Auth service.
struct User
# Unique identifier, assigned by the system
id UInt64
# Display name, must be 1-100 characters
name Text
# Email address, must be unique across all users
email TextNext Steps
- Transports - Network transport options
- Gateway - HTTP/REST gateway
- API Reference - Complete API documentation