Documentation Index
Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
Use this file to discover all available pages before exploring further.
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:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β YOUR CODE (Plain Go) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β package todo β β
β β type Service struct { ... } β β
β β func (s *Service) Create(...) (*Todo, error) β β
β β func (s *Service) List(...) ([]*Todo, error) β β
β β β β
β β No HTTP. No JSON. No framework. Just business logic. β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β contract.Register[todo.API](todo.NewService())
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CONTRACT (The Bridge) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Service { β β
β β Name: "todo" β β
β β Methods: [Create, List, Get, Delete] β β
β β Types: {Todo, CreateInput, ListOutput, ...} β β
β β } β β
β β β β
β β Each method has an "Invoker" - a fast way to call it. β β
β β Each type has a "Schema" - a description for documentation. β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β REST Handler β β JSON-RPC Handlerβ β MCP Handler β
β β β β β β
β POST /todos β β method: "Create"β β tool: "Create" β
β GET /todos β β method: "List" β β tool: "List" β
β GET /todos/{id} β β method: "Get" β β tool: "Get" β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β β β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β HTTP Requests β
β from Clients β
βββββββββββββββββββ
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:
// todo/service.go
package todo
type Service struct {
db *sql.DB // Your dependencies go here
}
func NewService(db *sql.DB) *Service {
return &Service{db: db}
}
func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
// This is pure business logic
// No http.Request, no JSON marshaling, no protocol concerns
todo := &Todo{
ID: generateID(),
Title: in.Title,
}
// Save to database
_, err := s.db.ExecContext(ctx, "INSERT INTO todos...")
if err != nil {
return nil, err
}
return todo, nil
}
Why this matters: Your code is easy to test (no HTTP mocking needed), easy to understand (just Go functions), and can be reused in different contexts (CLI tools, background jobs, etc.).
Layer 2: The Contract (The Bridge)
When you call contract.Register(), Contract inspects your service and creates a data structure that describes it:
impl := todo.NewService(db)
svc := contract.Register[todo.API](impl)
This registration process:
- 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
The result is a Service struct that knows everything about your API:
svc.Name // "todo"
svc.Methods // [Create, List, Get, Delete]
svc.Types // TypeRegistry with JSON schemas
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
Each transport does this differently:
| 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
curl -X POST http://localhost:8080/todos \
-H "Content-Type: application/json" \
-d '{"title": "Buy milk"}'
Step 2: REST Handler Receives It
The REST handler (mounted by rest.Mount()) receives the HTTP request:
Method: POST
Path: /todos
Body: {"title": "Buy milk"}
Step 3: Handler Determines the Method
Based on the HTTP method (POST) and path (/todos), the handler knows to call Create:
// Inside the REST handler:
// POST /todos -> Create method
// GET /todos -> List method
// GET /todos/{id} -> Get method
// DELETE /todos/{id} -> Delete method
methodName := "Create" // Derived from HTTP method + path
method := svc.Resolve(methodName) // Find the Method struct
Step 4: Invoker Calls Your Method
The invoker unmarshals the JSON input and calls your method:
// What the invoker does (simplified):
input := &CreateInput{}
json.Unmarshal(requestBody, input) // {"title": "Buy milk"} -> CreateInput
result, err := yourService.Create(ctx, input) // Actually calls your code!
Step 5: Handler Sends Response
The handler marshals your response back to JSON:
// Your method returned: &Todo{ID: "todo_1", Title: "Buy milk"}
// Handler converts it to JSON and sends:
HTTP/1.1 200 OK
Content-Type: application/json
{"id":"todo_1","title":"Buy milk","completed":false}
The Complete Request Flow
Hereβs the detailed flow for any transport:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HTTP Request β
β POST /todos β
β Body: {"title": "Buy milk"} β
βββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Transport Handler β
β β
β 1. Parse protocol-specific request β
β REST: Parse HTTP method + path + body β
β JSON-RPC: Parse JSON-RPC envelope β
β MCP: Parse tool call β
β β
β 2. Resolve method name β
β resolver.Resolve("Create") β *Method β
β β
β 3. Get invoker and call method β
β invoker.Invoke(ctx, method, inputBytes) β
β β β
β ββ json.Unmarshal(inputBytes, &CreateInput{}) β
β ββ method.Invoker.Call(ctx, input) β
β ββ return result or error β
β β
β 4. Format protocol-specific response β
β REST: JSON body with HTTP status β
β JSON-RPC: JSON-RPC envelope with result/error β
β MCP: Content block with isError flag β
βββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β HTTP Response β
β 200 OK β
β {"id":"todo_1","title":"Buy milk","completed":false} β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Core Components Explained
Service
The Service struct is the central data structure that holds everything about your API:
type Service struct {
Name string // "todo"
Description string // Optional description
Version string // API version
Methods []*Method // All discovered methods
Types *TypeRegistry // All types and their schemas
}
Each method on your service becomes a Method struct:
type Method struct {
Name string // "Create"
Description string // From doc comments (if any)
InputType reflect.Type // *CreateInput
OutputType reflect.Type // *Todo
Invoker MethodInvoker // Fast caller
}
The 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:
// Your Go type:
type CreateInput struct {
Title string `json:"title"`
}
// Becomes this JSON Schema:
{
"type": "object",
"properties": {
"title": {"type": "string"}
},
"required": ["title"]
}
Schemas are used for:
- 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:
// Slow: reflection on every call
method := reflect.ValueOf(service).MethodByName("Create")
result := method.Call([]reflect.Value{ctxValue, inputValue})
With Contract, the reflection happens once at registration, and subsequent calls are fast:
// Fast: compiled invoker
result, err := method.Invoker.Call(ctx, input)
Package Organization
The contract v2 package is organized into:
contract/v2/
βββ contract.go # Core: Register, RegisteredService
βββ types.go # Type discovery, JSON schema generation
βββ errors.go # Error types and codes
β
βββ transport/ # Transport implementations
βββ rest/ # REST transport
β βββ rest.go # Mount, NewHandler
β
βββ jsonrpc/ # JSON-RPC 2.0
β βββ jsonrpc.go # Mount, NewHandler, Request/Response types
β
βββ mcp/ # Model Context Protocol
β βββ mcp.go # Mount, NewHandler
β
βββ openapi/ # OpenAPI spec generation
βββ openapi.go # Mount, Generate
Design Principles
1. Reflection Only at Startup
Contract uses reflection (Goβs way of inspecting types at runtime) only once when you call Register(). 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 (like NOT_FOUND, INVALID_ARGUMENT) that map to appropriate representations in each protocol:
return nil, contract.ErrNotFound("user not found")
| 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
Everything is standard Go: structs, methods, and function calls. If your code compiles, it works with Contract.
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* β POST
Get* β GET
List* β GET
Update* β PUT
Delete* β 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