A compact serialization library for Elixir using context-scoped schema registries. Eliminates global index collisions while maintaining wire format compatibility.
Here is a short pitch: PITCH
defmodule User do
use ElixirProto.Schema, name: "myapp.user"
defschema [:id, :name, :email, :age]
end
defmodule Product do
use ElixirProto.Schema, name: "myapp.product"
defschema [:id, :sku, :price, :category]
end
defmodule MyApp.UserManagement.PayloadConverter do
use ElixirProto.PayloadConverter,
mapping: [
{1, "myapp.user"},
{2, "myapp.user.profile"},
{3, "myapp.user.session"}
]
end
defmodule MyApp.Inventory.PayloadConverter do
use ElixirProto.PayloadConverter,
mapping: [
{1, "myapp.product"}, # Same index, different context!
{2, "myapp.product.variant"},
{3, "myapp.inventory.stock"}
]
end
user = %User{id: 1, name: "Alice", email: "[email protected]", age: 30}
product = %Product{id: 1, sku: "ABC123", price: 29.99, category: "electronics"}
# Each context manages its own indices independently
user_data = MyApp.UserManagement.PayloadConverter.encode(user)
product_data = MyApp.Inventory.PayloadConverter.encode(product)
# Decode with the correct context
decoded_user = MyApp.UserManagement.PayloadConverter.decode(user_data)
decoded_product = MyApp.Inventory.PayloadConverter.decode(product_data)
defmodule User do
use ElixirProto.TypedSchema, name: "myapp.user"
typedschema do
field :id, pos_integer(), index: 1, enforce: true
field :name, String.t(), index: 2
field :email, String.t() | nil, index: 3
field :age, pos_integer(), index: 4, default: 0
end
end
- Context-Scoped Registries: Each PayloadConverter manages its own index namespace
- Centralized Index Mapping: All indices for a context visible in single location
- Index Collision Elimination: Same indices can be safely used across different contexts
- Wire Format Preservation: Context information is compile-time only, not stored in binary
- Fixed Tuples: Eliminates per-field overhead (1-3 bytes vs 20+ bytes for module names)
- Space Savings: 34-56% smaller than standard serialization
- Context Isolation: No more global index collisions between teams/domains
- Centralized Management: All context indices managed in single PayloadConverter mapping
- Wire Compatibility: Maintains
{schema_index, payload_tuple}
format - Domain Organization: Organize schemas by business context following DDD principles
- Two Schema Approaches: Simple Schema or TypedSchema with compile-time type safety
- Nested Structs: Automatic deep serialization with context awareness
- Schema Evolution: Safe field additions and renaming within contexts
- Built-in Compression: Automatic zlib compression for optimal storage
Struct Type | Standard | ElixirProto | Savings |
---|---|---|---|
User (5 fields) | 79 bytes | 52 bytes | 34.2% |
Complex (50 fields) | 229 bytes | 101 bytes | 55.9% |
# Safe operations within a PayloadConverter context:
- Add new schemas with new indices to mapping
- Add new fields to existing schemas (with new field indices)
- Rename fields (keep same field index)
- Reorder field definitions in schemas
- Remove unused schemas from mapping (if no legacy data)
# Never change within a context:
- Existing schema indices in PayloadConverter mapping
- Existing field indices within schemas
- Schema names once in production
# Context isolation allows:
- Same schema indices across different PayloadConverters
- Independent evolution of different domain contexts
- Team autonomy without coordination overhead
# Before: Initial PayloadConverter
defmodule MyApp.Users.PayloadConverter do
use ElixirProto.PayloadConverter,
mapping: [
{1, "myapp.user"},
{2, "myapp.user.profile"}
]
end
# After: Adding new schemas safely
defmodule MyApp.Users.PayloadConverter do
use ElixirProto.PayloadConverter,
mapping: [
{1, "myapp.user"}, # Keep existing
{2, "myapp.user.profile"}, # Keep existing
{3, "myapp.user.session"}, # Add new schema
{4, "myapp.user.preferences"} # Add new schema
]
end
def deps do
[{:elixir_proto, "~> 0.1.0"}]
end