Architecture Overview
This guide provides a comprehensive overview of CQL's architecture, explaining how the different components work together to provide a powerful, type-safe ORM for Crystal applications.
ποΈ High-Level Architecture
CQL follows a layered architecture pattern that separates concerns and provides flexibility while maintaining performance:
π§© Core Components
1. Schema Definition Layer
The schema layer is the foundation of CQL, providing type-safe database structure definition:
# Schema defines the database structure
AcmeDB = CQL::Schema.define(
:acme_db,
"postgresql://localhost/myapp",
CQL::Adapter::Postgres
) do
table :users do
primary :id, Int64
column :name, String
column :email, String
timestamps
end
end
Key Responsibilities:
Database connection management
Table and column definitions
Index and constraint management
Migration execution
Transaction handling
2. Query Builder
The query builder provides a fluent interface for constructing SQL queries:
# Fluent query interface
query = AcmeDB.query
.from(:users)
.where(active: true)
.order(name: :asc)
.limit(10)
Features:
Type-safe query construction
Method chaining
Complex joins and subqueries
Aggregate functions
Raw SQL integration
3. Expression Builder
The expression builder translates Crystal expressions into SQL:
# Crystal expressions become SQL
query.where { |q| q.users.age > 18 }
# Generates: WHERE users.age > ?
Capabilities:
Filter builders for where clauses
Having builders for aggregate conditions
Type-safe comparisons
Complex boolean logic
Aggregate function support
4. Database Adapters
Adapters handle database-specific SQL generation and features:
# Each adapter handles database-specific SQL
case adapter
when CQL::Adapter::Postgres
"SELECT ... LIMIT $1 OFFSET $2"
when CQL::Adapter::MySql
"SELECT ... LIMIT ? OFFSET ?"
when CQL::Adapter::SQLite
"SELECT ... LIMIT ? OFFSET ?"
end
Adapter Features:
Database-specific SQL dialects
Connection pooling
Transaction management
Error handling
Feature detection
π Design Patterns
Active Record Pattern
CQL implements the Active Record pattern where models represent database tables:
class User
include CQL::ActiveRecord::Model(Int64)
db_context AcmeDB, :users
property id : Int64?
property name : String
property email : String
# Active Record methods are automatically available
# user.save!, user.update!, user.destroy!, etc.
end
Pattern Flow:
Repository Pattern
For applications that prefer separation of concerns:
class UserRepository < CQL::Repository(User, Int64)
def initialize
super(AcmeDB, :users)
end
def find_active_users
query.where(active: true).all(User)
end
def create_user(name : String, email : String)
create(name: name, email: email)
end
end
Pattern Flow:
π Data Flow Architecture
Query Execution Flow
Model Lifecycle
π― Type Safety Architecture
Compile-Time Type Checking
CQL leverages Crystal's macro system for compile-time type safety:
# Macros generate type-safe methods
macro included
include CQL::ActiveRecord::Validations
include CQL::ActiveRecord::Queryable
include CQL::ActiveRecord::Insertable
include CQL::ActiveRecord::Updateable
include CQL::ActiveRecord::Deleteable
include CQL::ActiveRecord::Persistence
end
Runtime Type Validation
π Performance Architecture
Connection Management
# Connection pooling strategy
class Schema
private getter db : DB::Database
private getter? active_connection : DB::Connection?
def exec_query(&)
if conn = @active_connection
yield conn # Use transaction connection
else
@db.using_connection do |conn|
yield conn # Use pooled connection
end
end
end
end
Query Optimization
π§ Extension Points
Custom Validators
class EmailValidator < CQL::ActiveRecord::Validations::CustomValidator
def valid? : Array(CQL::ActiveRecord::Validations::Error)
errors = [] of CQL::ActiveRecord::Validations::Error
unless @record.email.includes?("@")
errors << CQL::ActiveRecord::Validations::Error.new(
:email, "must be a valid email address"
)
end
errors
end
end
Custom Column Types
# Add support for custom types
enum Status
Active
Inactive
Suspended
end
# Extend type mapping for adapters
CQL::DB_TYPE_MAPPING[CQL::Adapter::Postgres][Status] = "VARCHAR(20)"
π Debugging and Introspection
Query Logging
# Enable query logging
Log.setup do |config|
config.level = Log::Severity::Debug
config.backend = Log::IOBackend.new(STDOUT)
end
# All SQL queries will be logged with parameters
user = User.where(active: true).first
# Logs: SQL queries with parameters
Schema Introspection
# Inspect schema at runtime
schema = AcmeDB
puts "Tables: #{schema.tables.keys}"
puts "Users columns: #{schema.tables[:users].columns.keys}"
# Generate schema documentation
dumper = CQL::SchemaDump.from_schema(schema)
schema_code = dumper.generate_schema_content(:MySchema, :my_schema)
π Performance Characteristics
Benchmarks
Simple SELECT
~50,000 ops/sec
Low
Complex JOIN
~10,000 ops/sec
Medium
Bulk Insert
~100,000 records/sec
Medium
Transaction
~20,000 ops/sec
Low
Optimization Tips
Use prepared statements - CQL automatically uses parameterized queries
Leverage connection pooling - Reuse database connections
Batch operations - Use transactions for multiple operations
Index wisely - Add indexes for frequently queried columns
Query optimization - Use
select
to limit returned columns
πͺ Advanced Patterns
Multi-Database Support
# Different databases for different purposes
ReadDB = CQL::Schema.define(
:read_db,
"postgresql://readonly@localhost/myapp",
CQL::Adapter::Postgres
) do
# Read-only replica configuration
end
WriteDB = CQL::Schema.define(
:write_db,
"postgresql://user:pass@localhost/myapp",
CQL::Adapter::Postgres
) do
# Primary database configuration
end
Migration Support
# Create a migration
class CreateUsersMigration < CQL::Migration(20241201001)
def up
schema.table :users do
primary :id, Int64
column :name, String
column :email, String
timestamps
end
end
def down
schema.exec("DROP TABLE users")
end
end
# Run migrations
migrator = schema.migrator
migrator.up
π‘οΈ Security Architecture
SQL Injection Prevention
# All queries use parameterized statements
query.where(name: user_input) # Safe - parameterized
# Generates: WHERE name = ? with parameters [user_input]
# Raw SQL requires explicit parameter binding
query.where("name = ?", user_input) # Safe - explicitly parameterized
Connection Security
# SSL/TLS support for database connections
secure_uri = "postgresql://user:pass@host:5432/db?sslmode=require"
SecureDB = CQL::Schema.define(:secure_db, secure_uri, CQL::Adapter::Postgres)
π§ Navigation
This architecture provides a solid foundation for building scalable, maintainable, and performant Crystal applications while maintaining type safety and developer productivity.
For more detailed information, see:
Last updated
Was this helpful?