Chuyển đến nội dung chính

Kiến trúc

Hướng dẫn này giải thích về kiến trúc, các mẫu thiết kế và cách các thành phần của TablePro hoạt động cùng nhau.

Tổng quan

TablePro được xây dựng với:
  • SwiftUI cho giao diện người dùng
  • AppKit cho tích hợp macOS cấp thấp
  • Swift Concurrency (async/await, actors) cho các thao tác đồng thời
  • Thư viện database native cho kết nối database

Dependencies

TablePro sử dụng Swift Package Manager (SPM) cho các dependencies bên thứ ba:
PackagePhiên bảnMục đích
CodeEditSourceEditor0.15.2+Component code editor dựa trên tree-sitter cho trình soạn thảo SQL
Sparkle2.xFramework cập nhật tự động với chữ ký EdDSA
CodeEditSourceEditor đi kèm một plugin SwiftLint yêu cầu -skipPackagePluginValidation cho CLI builds. Xem hướng dẫn Build để biết chi tiết.

Cấu trúc thư mục

TablePro
Extensions
Resources

Các mẫu thiết kế

Kiến trúc MVVM

TablePro sử dụng mô hình Model-View-ViewModel (MVVM): Models: Cấu trúc dữ liệu thuần túy (structs, enums)
struct DatabaseConnection: Codable, Identifiable {
    let id: UUID
    var name: String
    var host: String
    var port: Int
    var type: DatabaseType
}
ViewModels: Container trạng thái có thể quan sát
@MainActor
class DatabaseManager: ObservableObject {
    @Published var sessions: [DatabaseSession] = []
    @Published var activeSessionId: UUID?

    func connect(to connection: DatabaseConnection) async throws {
        // Logic nghiệp vụ
    }
}
Views: UI khai báo SwiftUI
struct ConnectionFormView: View {
    @StateObject private var dbManager = DatabaseManager.shared

    var body: some View {
        Form {
            // Các thành phần UI
        }
    }
}

Thiết kế hướng Protocol

Database drivers tuân theo một protocol:
protocol DatabaseDriver: AnyObject {
    var connection: DatabaseConnection { get }
    var status: ConnectionStatus { get }

    func connect() async throws
    func disconnect()
    func execute(query: String) async throws -> QueryResult
    func fetchTables() async throws -> [TableInfo]
    // ...
}
Các triển khai:
class MySQLDriver: DatabaseDriver { ... }
class PostgreSQLDriver: DatabaseDriver { ... }
class SQLiteDriver: DatabaseDriver { ... }

Actor Isolation

Các thao tác đồng thời sử dụng Swift actors:
actor SSHTunnelManager {
    static let shared = SSHTunnelManager()

    private var tunnels: [UUID: SSHTunnel] = [:]

    func createTunnel(
        connectionId: UUID,
        sshHost: String,
        // ...
    ) async throws -> Int {
        // Quản lý tunnel an toàn luồng
    }
}

Factory Pattern

Tạo driver sử dụng factory:
enum DatabaseDriverFactory {
    static func createDriver(for connection: DatabaseConnection) -> DatabaseDriver {
        switch connection.type {
        case .mysql, .mariadb:
            return MySQLDriver(connection: connection)
        case .postgresql:
            return PostgreSQLDriver(connection: connection)
        case .sqlite:
            return SQLiteDriver(connection: connection)
        }
    }
}

Các thành phần chính

DatabaseManager

Bộ quản lý trung tâm cho các thao tác database:
  • Quản lý các session đang hoạt động
  • Điều phối kết nối/ngắt kết nối
  • Xử lý vòng đời SSH tunnel
  • Công bố thay đổi trạng thái cho UI
@MainActor
class DatabaseManager: ObservableObject {
    static let shared = DatabaseManager()

    @Published var sessions: [DatabaseSession] = []
    @Published var activeSessionId: UUID?

    func connectToSession(_ connection: DatabaseConnection) async throws
    func disconnectSession(_ id: UUID) async
    func executeQuery(_ query: String) async throws -> QueryResult
}

Database Drivers

Mỗi driver đóng gói logic đặc thù cho database:
DriverLibraryProtocol
MySQLDriverlibmariadbMySQL wire protocol
PostgreSQLDriverlibpqPostgreSQL protocol
SQLiteDriverBuilt-in SQLite3File-based

Autocomplete Engine

Hệ thống autocomplete:
  • CompletionEngine: Điểm vào chính
  • SQLContextAnalyzer: Phân tích ngữ cảnh truy vấn
  • SQLSchemaProvider: Cung cấp thông tin schema
  • SQLKeywords: Định nghĩa từ khóa SQL

SSH Tunnel Manager

Quản lý SSH tunnels như một actor:
actor SSHTunnelManager {
    private var tunnels: [UUID: SSHTunnel] = [:]

    func createTunnel(...) async throws -> Int
    func closeTunnel(connectionId: UUID) async throws
    func hasTunnel(connectionId: UUID) -> Bool
}
Tính năng:
  • Chuyển tiếp port qua ssh hệ thống
  • Xác thực bằng password và key
  • Giám sát trạng thái
  • Tự động dọn dẹp

Luồng dữ liệu

Luồng kết nối

Luồng thực thi truy vấn

Quản lý trạng thái

Published Properties

Trạng thái UI được quản lý với @Published:
@MainActor
class DatabaseManager: ObservableObject {
    @Published var sessions: [DatabaseSession] = []
    @Published var activeSessionId: UUID?
    @Published var isConnecting = false
}

App Storage

Settings sử dụng @AppStorage để lưu trữ:
@AppStorage("appearance.theme") var theme: AppTheme = .system
@AppStorage("editor.fontSize") var fontSize: Int = 13

Environment

Trạng thái chia sẻ qua SwiftUI environment:
@main
struct TableProApp: App {
    @StateObject private var dbManager = DatabaseManager.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(dbManager)
        }
    }
}

Xử lý lỗi

Driver Errors

Mỗi driver định nghĩa các lỗi cụ thể:
enum MySQLError: Error, LocalizedError {
    case connectionFailed(String)
    case queryFailed(String)
    case authenticationFailed

    var errorDescription: String? {
        switch self {
        case .connectionFailed(let msg): return "Connection failed: \(msg)"
        case .queryFailed(let msg): return "Query failed: \(msg)"
        case .authenticationFailed: return "Authentication failed"
        }
    }
}

Error Propagation

Lỗi được truyền lên qua async/await:
func executeQuery(_ query: String) async throws -> QueryResult {
    guard let driver = activeDriver else {
        throw DatabaseError.notConnected
    }
    return try await driver.execute(query: query)
}

Testing

Unit Tests

Tests nằm trong TableProTests/:
final class MySQLDriverTests: XCTestCase {
    func testConnectionString() throws {
        let connection = DatabaseConnection(...)
        let driver = MySQLDriver(connection: connection)
        XCTAssertEqual(driver.connectionString, "expected")
    }
}

Integration Tests

Cho các test database:
func testExecuteQuery() async throws {
    let driver = MySQLDriver(connection: testConnection)
    try await driver.connect()
    defer { driver.disconnect() }

    let result = try await driver.execute(query: "SELECT 1")
    XCTAssertEqual(result.rowCount, 1)
}

Bước tiếp theo