PrimeLoader System Architecture

Multi-Tenant Hydrology Sensor SaaS Platform

DelphiSpring4DXDataOAuth2Linux Daemon

System Overview

PrimeLoader is a Linux daemon-based multi-tenant application server built with Delphi. It serves as the core backend for a hydrology sensor SaaS platform, managing multiple tenant databases, dynamic plugin loading, and OAuth2 authentication.

Key Architectural Patterns:

  • Dependency Injection — Spring4D container for IoC
  • Multi-Tenancy — Separate database per tenant with connection pooling
  • Plugin Architecture — Dynamic .so library loading at runtime
  • Factory Pattern — Tenant and Plugin factories for object creation
  • Thread-Safe Pooling — Critical sections protecting shared resources

Class Diagram

%%{init: {'theme': 'dark'}}%%
classDiagram
    direction TB
    class PrimeLoader_Apache {
        <<Linux Daemon>>
        -SID: Integer
        -ControllerModule: TMainServer
        -TerminateRequested: Boolean
        +RunDaemon()
        +Daemonize()
        +StartServer()
        +CleanupAndShutdown()
    }
    class IServer {
        <<Interface>>
        +HttpServer: TIndySparkleHTTPServer
        +TenantFactory: ITenantFactory
        +PluginFactory: IPluginFactory
        +Logger: ILinuxLog
        +TenantPoolManager: TTenantPoolManager
        +CreateServer()
        +StartServer()
        +StopServer()
        +AddModule()
    }
    class TServer {
        <<Implementation>>
        -FHTTPServer: TIndySparkleHTTPServer
        -FTenantFactory: ITenantFactory
        -FPluginFactory: IPluginFactory
        +CreateServer()
        +DoOnCommand()
        +DoOnError()
    }
    class TMainServer {
        <<DataModule>>
        +AdminServer: TXDataServer
        +AdminDatabase: TUniConnection
        +PrimeRadiantSphinxServer: TSphinxServer
        +InitialiseServer()
        +LoadPlugins()
        +InstallTenants()
    }
    class ITenantFactory {
        <<Interface>>
        +TenantList: TDictionary
        +Add()
        +FindTenant()
        +Remove()
    }
    class TTenantPoolManager {
        <<Singleton>>
        -FPools: TDictionary
        -FConfigs: TDictionary
        -FLock: TCriticalSection
        +Initialize()
        +GetPool(): TTenantConnectionPool
        +Shutdown()
    }
    class TTenantConnectionPool {
        <<Thread-Safe>>
        -FPool: TList
        -FInUse: TList
        -FLock: TCriticalSection
        +Acquire(): TUniConnection
        +Release()
    }
    class IPluginFactory {
        <<Interface>>
        +PluginList: TDictionary
        +ManagePluginLibraries()
        +FindPlugin()
        +RemovePlugin()
    }
    class IPlugin {
        <<Interface>>
        +LibraryName: String
        +LibraryHandle: HMODULE
        +BaseURL: String
        +IsLoaded: Boolean
        +StartDataModule()
    }
    class TSphinxServer {
        <<OAuth2>>
        +CreateModule()
        +CreateContext()
    }
    class TXDataServer {
        <<REST API>>
        +BaseURL: String
        +CreateModule()
    }
    PrimeLoader_Apache --> IServer : creates
    PrimeLoader_Apache --> TMainServer : creates
    IServer <|.. TServer : implements
    TServer --> ITenantFactory
    TServer --> IPluginFactory
    TServer --> TTenantPoolManager
    TMainServer --> TXDataServer
    TMainServer --> TSphinxServer
    TMainServer --> TTenantPoolManager : initializes
    ITenantFactory o-- ITenant
    IPluginFactory o-- IPlugin
    TTenantPoolManager o-- TTenantConnectionPool

Daemon Startup Sequence

%%{init: {'theme': 'dark'}}%%
sequenceDiagram
    participant OS as Linux OS
    participant D as PrimeLoader
    participant IC as DI Container
    participant S as IServer
    participant MS as TMainServer
    participant TPM as TenantPoolManager
    participant PF as PluginFactory
    OS->>D: Start Process
    D->>D: Daemonize()
    D->>D: SetupSignalHandlers()
    D->>IC: RegisterClassesAndInterfaces()
    D->>IC: Resolve IServer
    IC-->>S: TServer instance
    D->>MS: Create TMainServer
    MS->>MS: LoadConfigs()
    MS->>MS: ConnectPrimaryServers()
    D->>S: CreateServer()
    S->>S: Create HTTP Server
    D->>MS: InitialiseServer()
    MS->>MS: SetupServerModules()
    MS->>TPM: Initialize(LoadSitesProc)
    TPM->>TPM: Create connection pools
    MS->>PF: ManagePluginLibraries()
    PF->>PF: Load plugin .so files
    PF->>PF: DoInject() for each
    D->>S: StartServer()
    Note over D: Enter main loop

HTTP Request Flow

%%{init: {'theme': 'dark'}}%%
sequenceDiagram
    participant C as Client
    participant H as HTTP Server
    participant JWT as JWT Middleware
    participant M as XData Module
    participant TPM as TenantPoolManager
    participant Pool as ConnectionPool
    participant Svc as Service
    C->>H: HTTP Request
    H->>JWT: Validate Token
    JWT->>JWT: Extract tenant_id
    JWT->>M: Forward Request
    M->>TPM: GetPool(tenantURL)
    TPM-->>M: TTenantConnectionPool
    M->>Pool: Acquire()
    Pool-->>M: TUniConnection
    M->>Svc: Execute Service
    Svc-->>M: Response
    M->>Pool: Release(conn)
    M-->>C: HTTP Response

Plugin Loading Process

%%{init: {'theme': 'dark'}}%%
sequenceDiagram
    participant MS as TMainServer
    participant PF as PluginFactory
    participant DB as Database
    participant SO as .so Library
    participant S as IServer
    MS->>PF: ManagePluginLibraries()
    PF->>DB: GetActiveModuleList
    DB-->>PF: Plugin records
    loop Each plugin
        PF->>PF: Create IPlugin
        PF->>SO: LoadLibrary(name.so)
        SO-->>PF: HMODULE handle
        PF->>DB: RegisterModuleHandle()
        PF->>SO: GetProcAddress('Inject')
        PF->>SO: Inject(AppServer, Plugin)
        Note over SO: Plugin registers services
    end

Connection Pool Management

%%{init: {'theme': 'dark'}}%%
sequenceDiagram
    participant Svc as Service
    participant TPM as TenantPoolManager
    participant Pool as TTenantConnectionPool
    participant Conn as TUniConnection
    Svc->>TPM: GetPool(url)
    TPM-->>Svc: Pool instance
    Svc->>Pool: Acquire()
    Pool->>Pool: Lock
    alt Has available
        Pool->>Pool: Validate connection
        Pool-->>Svc: Connection
    else Empty, under max
        Pool->>Conn: Create new
        Conn->>Conn: Connect to DB
        Pool-->>Svc: Connection
    else Pool exhausted
        Pool-->>Svc: Exception
    end
    Note over Svc: Use connection
    Svc->>Pool: Release(conn)
    Pool->>Pool: Validate & return to pool

OAuth2 Authentication Flow

%%{init: {'theme': 'dark'}}%%
sequenceDiagram
    participant C as Client
    participant S as Sphinx Server
    participant Cfg as SphinxConfig
    participant UM as UserManager
    C->>C: Generate PKCE codes
    C->>S: /authorize (code_challenge)
    S->>Cfg: Validate client
    S->>C: Redirect to login
    C->>S: POST credentials
    S->>UM: Validate user
    UM-->>S: Valid
    S->>S: Generate auth code
    S->>C: Redirect with code
    C->>S: POST /token (code_verifier)
    S->>S: Verify PKCE
    S->>S: Generate JWT
    S-->>C: access_token, id_token

Component Reference

Core Components

PrimeLoader_Apache

Main daemon entry point. Handles daemonization, signal handling, and lifecycle management.

Framework/PRIMELOADER/PrimeLoader_Apache.dpr

TServer (IServer)

Core server implementation managing HTTP server, factories, and modules.

Framework/COMMON/SERVER/OBJECTS/Obj.Server.pas

TMainServer

Primary controller DataModule handling initialization and database connections.

Framework/COMMON/DATAMODULES/BackOffice.Data.Main.pas

Multi-Tenancy Components

TTenantPoolManager

Singleton managing all tenant connection pools. Thread-safe initialization and access.

Framework/COMMON/USERS/OBJECTS/Obj.Tenant.PoolManager.pas

TTenantConnectionPool

Per-tenant thread-safe connection pool with acquire/release pattern.

Framework/COMMON/USERS/OBJECTS/Obj.Tenant.ConnectionPool.pas

TTenant (ITenant)

Tenant entity with database configuration and data manager.

Framework/COMMON/USERS/OBJECTS/Obj.Tenant.pas

TTenantFactory (ITenantFactory)

Factory for creating and managing tenant instances.

Framework/COMMON/FACTORIES/OBJECTS/Obj.Factory.Tenant.pas

Plugin System Components

TPluginFactory (IPluginFactory)

Manages plugin lifecycle: loading, injection, and removal.

Framework/COMMON/FACTORIES/OBJECTS/Obj.Factory.Plugin.pas

TPlugin (IPlugin)

Plugin entity representing a dynamically loaded .so library.

Framework/COMMON/PLUGIN/OBJECTS/Obj.Plugin.pas

Interface Summary

InterfaceImplementationPurpose
IServerTServerMain application server abstraction
ITenantTTenantTenant entity with database config
ITenantFactoryTTenantFactoryTenant creation and lookup
IPluginTPluginPlugin entity with module info
IPluginFactoryTPluginFactoryPlugin loading and management
IDataManagerTDataManagerDatabase operations abstraction
ILinuxLogTLinuxLogLogging service

Key Data Flows

FlowEntry PointKey ComponentsOutput
StartupPrimeLoader_Apache.MainInjectionContainer → TServer → TMainServerRunning HTTP server with modules
Tenant InitInstallTenants()LoadSitesProc → TenantPoolManager → ConnectionPoolsPools ready for all tenants
Plugin LoadLoadPlugins()GetActiveModuleList → PluginFactory → LoadLibraryPlugins injected and active
RequestHTTP RequestJWT Middleware → XData Module → ServiceHTTP Response
Auth/authorizeSphinxServer → SphinxConfig → UserManagerJWT tokens