Architecture of IbkrApi
View SourceThis document explains the architecture and design principles of the IbkrApi library, providing context for developers who want to understand how the library works internally.
Overview
IbkrApi is designed as a clean, idiomatic Elixir wrapper around Interactive Brokers' Client Portal API. The library follows these key design principles:
- Simplicity: Providing a straightforward interface to the IBKR API
- Consistency: Maintaining a consistent API design across all modules
- Error Handling: Comprehensive error handling with meaningful messages
- Type Safety: Using structs and typespecs for better code quality
System Architecture
The library is organized into several layers:
┌─────────────────────────────────────────────────┐
│ Client Code │
└───────────────────────┬─────────────────────────┘
│
┌───────────────────────▼─────────────────────────┐
│ IbkrApi.ClientPortal │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────┐ │
│ │ Auth │ │ Account │ │ Order │ │ ... │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────┘ │
└───────────────────────┬─────────────────────────┘
│
┌───────────────────────▼─────────────────────────┐
│ IbkrApi.HTTP │
└───────────────────────┬─────────────────────────┘
│
┌───────────────────────▼─────────────────────────┐
│ Finch │
└───────────────────────┬─────────────────────────┘
│
┌───────────────────────▼─────────────────────────┐
│ IBKR Client Portal Gateway │
└─────────────────────────────────────────────────┘
Core Components
- IbkrApi: The main module that serves as the entry point to the library
- IbkrApi.Application: Manages the application lifecycle and dependencies
- IbkrApi.Config: Handles configuration for the library
- IbkrApi.HTTP: Low-level HTTP client for making requests to the API
Domain-Specific Modules
The IbkrApi.ClientPortal
namespace contains domain-specific modules that correspond to different areas of the IBKR API:
- IbkrApi.ClientPortal.Auth: Authentication and session management
- IbkrApi.ClientPortal.Account: Account information and operations
- IbkrApi.ClientPortal.Contract: Financial instruments and contracts
- IbkrApi.ClientPortal.Order: Order placement and monitoring
- IbkrApi.ClientPortal.Profile: User profile information
- IbkrApi.ClientPortal.Trade: Trade executions and history
Data Flow
A typical request through the library follows this flow:
- Client code calls a function in one of the domain-specific modules
- The domain module constructs the appropriate request parameters
- The request is passed to
IbkrApi.HTTP
which handles the HTTP communication IbkrApi.HTTP
uses Finch to make the actual HTTP request to the IBKR Gateway- The response is received and parsed (typically from JSON to Elixir structs)
- The domain module returns the structured response to the client code
Error Handling
The library uses the ErrorMessage
module for consistent error handling:
defmodule ErrorMessage do
@type t :: %__MODULE__{
code: String.t() | nil,
message: String.t() | nil,
description: String.t() | nil
}
@type t_res :: {:ok, any()} | {:error, t()}
defstruct [:code, :message, :description]
end
All API functions return either {:ok, result}
on success or {:error, %ErrorMessage{}}
on failure, allowing for consistent error handling throughout the application.
Data Structures
The library uses Elixir structs to represent API responses. Each domain module defines its own set of structs that match the structure of the API responses. For example:
defmodule IbkrApi.ClientPortal.Auth.CheckAuthStatusResponse do
defstruct authenticated: nil, competing: nil, connected: nil,
fail: nil, hardware_info: nil, mac: nil, message: nil,
server_info: %IbkrApi.ClientPortal.Auth.ServerInfo{}
end
This approach provides type safety and makes it clear what data is available from each API call.
Configuration
The library is configured through the standard Elixir configuration system:
config :ibkr_api,
base_url: "http://localhost:5000/v1/api",
timeout: 30_000
The IbkrApi.Config
module provides functions to access this configuration:
defmodule IbkrApi.Config do
def base_url, do: Application.get_env(:ibkr_api, :base_url, "http://localhost:5000/v1/api")
def timeout, do: Application.get_env(:ibkr_api, :timeout, 30_000)
end
HTTP Client
The IbkrApi.HTTP
module handles all HTTP communication with the IBKR API. It uses Finch for making HTTP requests and Jason for JSON encoding/decoding:
defmodule IbkrApi.HTTP do
@spec get(String.t()) :: ErrorMessage.t_res()
def get(url) do
# Implementation...
end
@spec post(String.t(), map()) :: ErrorMessage.t_res()
def post(url, body) do
# Implementation...
end
end
Design Decisions
Why Finch?
Finch was chosen as the HTTP client because:
- It's a modern, fast HTTP client for Elixir
- It provides connection pooling for better performance
- It's well-maintained and widely used in the Elixir community
Why Structs for API Responses?
Structs were chosen to represent API responses because:
- They provide a clear structure for the data
- They enable type checking with dialyzer
- They make it clear what data is available from each API call
- They allow for pattern matching in client code
Why the ClientPortal Namespace?
The IbkrApi.ClientPortal
namespace was created to:
- Separate the Client Portal API from potential future APIs
- Group related functionality together
- Allow for easy discovery of available modules
Testing Strategy
The library is designed to be testable at multiple levels:
- Unit Tests: Test individual functions in isolation
- Integration Tests: Test interaction between modules
- API Tests: Test actual communication with the IBKR API (requires a running gateway)
Mock modules can be used to simulate API responses for testing without a live connection to the IBKR gateway.
Future Considerations
Potential future enhancements to the architecture include:
- Streaming Support: Adding support for WebSocket streaming for real-time data
- Rate Limiting: Implementing intelligent rate limiting to avoid API throttling
- Caching: Adding a caching layer for frequently accessed data
- Retry Logic: Implementing automatic retries for transient failures
- Async Operations: Supporting asynchronous operations for long-running requests