Architecture
This page explains how Contract works internally. Understanding the architecture helps you make better decisions when building your APIs and troubleshoot issues when they arise.The Big Picture
At its core, Contract does one simple thing: it takes your plain Go code and makes it accessible via different network protocols. Hereβs how the pieces fit together:Three Layers Explained
Layer 1: Your Code (The Service)
This is the Go code you write. It contains your business logic with no knowledge of HTTP, JSON, or any protocol:Layer 2: The Contract (The Bridge)
When you callcontract.Register(), Contract inspects your service and creates a data structure that describes it:
- Discovers methods using Goβs reflection
- Parses signatures to understand inputs and outputs
- Generates JSON schemas from your Go types
- Creates invokers for fast method calling
Service struct that knows everything about your API:
Layer 3: Transports (The Protocols)
Transports are HTTP handlers that speak different protocols. They:- Receive HTTP requests in their specific format
- Find the right method in the contract
- Call the method using the invoker
- Return the response in their specific format
| Transport | Request Format | Response Format |
|---|---|---|
| REST | HTTP verbs + paths | JSON body |
| JSON-RPC | JSON with method name | JSON-RPC envelope |
| MCP | JSON-RPC with tool calls | MCP content blocks |
How a Request Flows Through the System
Letβs trace a REST request from start to finish:Step 1: Client Sends Request
Step 2: REST Handler Receives It
The REST handler (mounted byrest.Mount()) receives the HTTP request:
Step 3: Handler Determines the Method
Based on the HTTP method (POST) and path (/todos), the handler knows to callCreate:
Step 4: Invoker Calls Your Method
The invoker unmarshals the JSON input and calls your method:Step 5: Handler Sends Response
The handler marshals your response back to JSON:The Complete Request Flow
Hereβs the detailed flow for any transport:Core Components Explained
Service
TheService struct is the central data structure that holds everything about your API:
Method
Each method on your service becomes aMethod struct:
Invoker is the key to performance - itβs created once at registration and used for every request.
TypeRegistry
The TypeRegistry holds all your types and their JSON schemas:- OpenAPI documentation
- MCP tool definitions
- Input validation (future)
- Client code generation
Invoker
An invoker is a pre-compiled way to call a method. Without Contract, calling a method via reflection on every request would be slow:Package Organization
The contract v2 package is organized into:Design Principles
1. Reflection Only at Startup
Contract uses reflection (Goβs way of inspecting types at runtime) only once when you callRegister(). After that, all method calls use pre-compiled invokers. This means:
- Startup: Slightly slower (milliseconds) due to reflection
- Runtime: Fast method calls with no reflection overhead
2. Protocol Agnostic Errors
Errors use codes (likeNOT_FOUND, INVALID_ARGUMENT) that map to appropriate representations in each protocol:
| Protocol | Representation |
|---|---|
| REST | HTTP 404 + message body |
| JSON-RPC | Error code -32601 |
| MCP | {"isError": true, "content": [...]} |
3. No Magic, Just Functions
Contract doesnβt use:- Code generation
- Build-time processing
- Special comments
- Interface implementations
Common Questions
Why not use code generation instead of reflection?
Code generation (like protobuf) requires extra build steps and generated files to maintain. Contractβs reflection-based approach:- Works with any Go struct immediately
- No extra build steps
- No generated files to keep in sync
- Faster iteration during development
How does Contract know which HTTP verb to use?
For REST, Contract uses naming conventions:Create*β POSTGet*β GETList*β GETUpdate*β PUTDelete*β DELETE- Other names β POST
Can I use Contract with gRPC?
Not directly yet, but the error codes are aligned with gRPC status codes for future compatibility. JSON-RPC provides similar RPC semantics over HTTP.What happens if I change my method signature?
Re-register your service (which happens automatically on server restart). Contract will discover the new signature and update the schemas.See Also
- Service Definition - How to write services
- Registration - The registration process in detail
- Transports Overview - Compare all transports
- Middleware - Add cross-cutting concerns