Skip to main content

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:
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:
This registration process:
  1. Discovers methods using Go’s reflection
  2. Parses signatures to understand inputs and outputs
  3. Generates JSON schemas from your Go types
  4. Creates invokers for fast method calling
The result is a Service struct that knows everything about your API:

Layer 3: Transports (The Protocols)

Transports are HTTP handlers that speak different protocols. They:
  1. Receive HTTP requests in their specific format
  2. Find the right method in the contract
  3. Call the method using the invoker
  4. Return the response in their specific format
Each transport does this differently:

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 by rest.Mount()) receives the HTTP request:

Step 3: Handler Determines the Method

Based on the HTTP method (POST) and path (/todos), the handler knows to call Create:

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

The Service struct is the central data structure that holds everything about your API:

Method

Each method on your service becomes a Method struct:
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:
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:
With Contract, the reflection happens once at registration, and subsequent calls are fast:

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 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:

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