This guide introduces the fundamental concepts of Treaty, including treaties (contracts), versions, strategies, requests, responses, and service delegation. Understanding these core building blocks will help you design robust API contracts.
A Treaty (contract) is a formal definition of the data structure for an API endpoint. A contract describes:
- What data the endpoint accepts (request)
- What data the endpoint returns (response)
- How data is validated and transformed
- Which service handles the request
Inherits from Treaty::Action::Base and defines the contract for a specific action.
class Posts::CreateTreaty < ApplicationTreaty
# Version definitions
endNaming:
- Located in
app/treaties/[namespace]/[controller]/[action]_treaty.rb - Class named
[Action]Treaty - Example:
Posts::CreateTreatyforposts#create
Each contract can have multiple versions. A version defines a specific contract implementation.
version 1 do
# Version 1 definition
end
version 2, default: true do
# Version 2 definition (default)
endVersion formats:
- Numeric:
1,2,3 - Semantic:
"1.0.0","2.1.0" - With labels:
"1.0.0.rc1",[1, 0, 0, :rc1]
Defines incoming data structure. Can be defined using a block or an Entity class.
Using a block:
request do
object :post do
string :title
string :content
end
endUsing an Entity class:
request Posts::Create::RequestEntityFeatures:
- Attributes are required by default
- Supports multiple objects
- Validates types and options
- Can be defined inline or as reusable Entity classes
Internal Architecture:
When using a block, Treaty creates an anonymous Request::Entity class dynamically and evaluates the block within it. This means request blocks and Entity classes use the same underlying system, ensuring consistent behavior.
Defines outgoing data structure for a specific HTTP status. Can be defined using a block or an Entity class.
Using a block:
response 200 do
array :posts do
string :id
string :title
end
end
response 201 do
object :post do
string :id
end
endUsing an Entity class:
response 201, Posts::Create::ResponseEntityFeatures:
- Attributes are optional by default
- Each status has its own structure
- Supports default values
- Can be defined inline or as reusable Entity classes
Internal Architecture:
When using a block, Treaty creates an anonymous Response::Entity class dynamically and evaluates the block within it. This unified architecture means both blocks and Entity classes share the same validation and transformation logic.
Groups related attributes.
object :post do
string :title
string :content
end
object :meta do
integer :count
integer :page
endSpecial :_self object:
object :_self do
string :signature
endAttributes from :_self are merged into parent level (root).
Reusable data structure definitions that can be used across multiple treaties and versions.
module Posts
module Create
class ResponseEntity < Treaty::Entity::Base
string :id
string :title
string :content
time :created_at
end
end
endUse in treaties:
class Posts::CreateTreaty < ApplicationTreaty
version 1 do
request Posts::Create::RequestEntity
response 201, Posts::Create::ResponseEntity
end
endFeatures:
- Attributes are required by default (like request blocks)
- Reusable across multiple versions and treaties
- Better code organization and maintainability
- Support all attribute types and options
See Entity Classes for detailed documentation.
Specifies where to pass request processing.
Service:
delegate_to Posts::CreateService
delegate_to "Posts::CreateService"
delegate_to "posts/create_service"Lambda:
delegate_to(lambda do |params:|
# Process locally
params
end)With options:
delegate_to Posts::CreateService => :call, return: lambda(&:data)Passes controller-specific data to services.
class PostsController < ApplicationController
treaty :index do
provide :current_user
provide :posts, from: :load_posts
end
endSources:
- Symbol - calls controller method
- Proc/Lambda - evaluates in controller context
- Direct value - passes data unchanged
See Inventory System for detailed documentation.
1. HTTP Request → Controller
2. Treaty determines version
3. Inventory Preparation (prepare controller data)
4. Request Validation (validate incoming data)
5. Request Transformation (transform incoming data)
6. Delegate To Service (pass to service with inventory)
7. Service Execution (service processes request)
8. Response Validation (validate service output)
9. Response Transformation (transform output data)
10. HTTP Response → Client
Marking a version as deprecated:
version 1 do
deprecated true
# or
deprecated { Time.current > Time.zone.parse("2024-12-31") }
# or
deprecated(lambda do
ENV["RELEASE_VERSION"] >= "2.0.0"
end)
endAdding description to a version:
version 2 do
summary "Added author support to posts"
# ...
end# Client sends:
{ "filters" => { "title" => "Ruby" } }
# Treaty validates against request definition
# Treaty passes to service:
{ filters: { title: "Ruby" } }
# Service returns:
{ posts: [...], meta: { count: 10, page: 1 } }
# Treaty validates against response definition
# Treaty transforms and returns:
{ "posts" => [...], "meta" => { "count" => 10, "page" => 1, "limit" => 12 } }Understanding how Treaty works internally can help you use it more effectively.
Treaty uses a unified entity-based architecture for all attribute definitions:
- Treaty::Entity::Base - Base class for user-defined entities (required by default)
- Treaty::RequestEntity - Internal class for request blocks (required by default)
- Treaty::ResponseEntity - Internal class for response blocks (optional by default)
All three share the same DSL (Treaty::Entity::Attribute::DSL) and attribute system, ensuring consistent behavior.
When you write:
request do
string :title
endTreaty automatically:
- Creates an anonymous
RequestEntityclass - Evaluates your block in that class's context
- Uses the resulting attribute collection for validation
This means blocks and Entity classes are equivalent under the hood!
Treaty::Entity::Attribute::DSL
↓
Treaty::Entity::Base (required: true)
↓
├── Your Entities
├── RequestEntity (for request blocks)
└── ResponseEntity (for response blocks)
Request and Response factories:
- Accept blocks (creates anonymous entity)
- Accept Entity classes (uses directly)
- Provide
collection_of_attributesfor validators - Ensure consistent validation regardless of definition method
- Contract First - Define the contract before implementation
- Version Everything - Every change gets a new version
- Validate Early - Catch errors at the contract level
- Transform Safely - Ensure data consistency between versions
- Deprecate Gracefully - Mark old versions as deprecated, don't delete
- Unified System - Blocks and Entity classes use the same underlying architecture
- Defining Contracts - Detailed treaty creation and configuration
- Attributes - Attribute types and options
- Versioning - Working with multiple API versions