๐๏ธ Architectural Revolution in Dolibarr ERP
A radical transformation that converts Dolibarr from a traditional PHP system into a pure client/server model where PostgreSQL is the brain that handles all business logic through automatic triggers and advanced functions.
Pure Client/Server
PostgreSQL as intelligent server, PHP as presentation client
Centralized Logic
All business rules executing automatically in the database
Guaranteed Consistency
Impossible to skip validations or calculations, automatic and transparent execution
๐ฏ Project Objectives
Main Objective
Completely transform Dolibarr ERP architecture from a traditional PHP system to a pure client/server model where all business logic resides in PostgreSQL through triggers and functions.
๐๏ธ Client/Server Architecture
Implement a model where PostgreSQL is the intelligent server that handles ALL business logic, and PHP acts only as a presentation client.
โก Total Elimination of PHP Logic
Completely remove validations, calculations and business rules from PHP code, transferring them to automatic PostgreSQL triggers.
๐ฏ PostgreSQL Exclusive
Abandon MySQL/MariaDB compatibility to take full advantage of PostgreSQL's advanced capabilities.
โ Consistency Guarantee
Ensure that business rules are always executed, regardless of which client accesses the database.
๐๏ธ Architectural Comparison
โ Dolibarr Original
PHP (Fat Client)
- Field validations
- Total calculations
- Code generation
- Business rules
- Flow control
MySQL/PostgreSQL (Dumb Storage)
- Data storage
- Basic constraints
- Simple relationships
โ Identified Problems:
- Scattered and duplicated logic
- Inconsistencies between accesses
- Easy to skip validations
- Complex maintenance
- Fragmented testing
โ Client/Server Model
PHP (Thin Client)
- Data presentation
- User interface
- Simple SQL commands
- No business logic
PostgreSQL (Smart Server)
- ๐ง Automatic triggers
- ๐ Validation functions
- ๐งฎ Guaranteed calculations
- ๐ท๏ธ Code generation
- ๐ Business rules
- ๐ Automatic auditing
โ Benefits Achieved:
- Centralized and consistent logic
- Always automatic validations
- Improved performance
- Simplified maintenance
- Unified testing
๐๏ธ Three Database Architecture
To ensure maximum precision in migration, we implement a testing architecture with three specialized databases:
dolibarrdev
DevelopmentMain development database
- Contains ALL PostgreSQL logic
- Triggers and functions implemented
- Real test data
- Main working environment
dolibarr_test_php
Test PHPReplica of original PHP behavior
- Table structure only
- No triggers or functions
- Simulates pure PHP behavior
- Comparison base for tests
dolibarr_test_postgresql
Test PostgreSQLExact copy of dolibarrdev
- Complete triggers and functions
- Pure PostgreSQL behavior
- Implementation validation
- Automated comparative tests
๐ Comparative Testing Flow
Capture PHP Behavior
Execute operations in dolibarr_test_php and record results
Execute in PostgreSQL
Perform the same operations in dolibarr_test_postgresql
Compare Results
Validate that both behaviors are identical
Certify Migration
Guarantee 100% functional parity
๐ Project Metrics
Progress by Category
๐ Project Timeline
Project Start
Initial analysis of Dolibarr, definition of objectives and establishment of client/server model as target architecture.
Societe Module (First Success)
Complete implementation of the first module, establishing patterns and methodology for the rest of the project.
Testing Infrastructure
Development of pgTAP testing and 3-database architecture for rigorous comparative tests.
Reached 17 Modules
Completed Societe, Product, User, Banque, Tax, Propale, Categories, Don, Contact, Commande, Facture, Stock, Bookkeeping, ExpenseReport, FournisseurCommande, Expeditions and Contracts with 100% tests passing.
Comprehensive Documentation
Creation of this interactive HTML book documenting the entire architectural transformation process.
Advanced Modules
Completion of the 4 remaining modules: Fichinter, Expedition, Contrat, and other specific modules.
๐ก Technical Justification
๐๏ธ Traditional Model Problems
- Scattered Logic: Validations and rules spread across multiple PHP files
- Inconsistency: Different behaviors depending on access point
- Complex Maintenance: Changes require modifying multiple files
- Fragmented Testing: Difficult to test all combinations
- Performance: Multiple roundtrips to the database
โ Client/Server Model Advantages
- Centralization: All logic in one controlled place
- Guarantees: Triggers always execute, without exceptions
- Performance: Optimized calculations in the database
- Consistency: Same behavior for all clients
- Auditability: Total control over operations
๐ Technical Transformation Example
โ Code PHP Original
public function verify() {
// Validate that name is not empty
if (empty($this->nom)) {
$this->errors[] = "Name is required";
return -1;
}
// Validate email if present
if (!empty($this->email)) {
if (!filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
$this->errors[] = "Invalid email";
return -1;
}
}
// Verify that code doesn't exist
$sql = "SELECT COUNT(*) as nb FROM llx_societe WHERE code_client = '".$this->code_client."'";
$resql = $this->db->query($sql);
if ($resql) {
$obj = $this->db->fetch_object($resql);
if ($obj->nb > 0) {
$this->errors[] = "Customer code already in use";
return -1;
}
}
return 0;
}
โ Trigger PostgreSQL
CREATE OR REPLACE FUNCTION llx_societe_before_insert()
RETURNS trigger AS $$
BEGIN
-- Validate required name
IF NEW.nom IS NULL OR trim(NEW.nom) = '' THEN
RAISE EXCEPTION 'Name is required';
END IF;
-- Validate email
IF NEW.email IS NOT NULL AND NEW.email != '' THEN
IF NOT (NEW.email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$') THEN
RAISE EXCEPTION 'Invalid email: %', NEW.email;
END IF;
END IF;
-- Generate code automatically if empty
IF NEW.code_client IS NULL OR trim(NEW.code_client) = '' THEN
NEW.code_client := llx_societe_get_next_code('C');
ELSE
-- Verify uniqueness
IF EXISTS (SELECT 1 FROM llx_societe WHERE code_client = NEW.code_client) THEN
RAISE EXCEPTION 'Customer code already in use: %', NEW.code_client;
END IF;
END IF;
-- Set defaults
NEW.entity := COALESCE(NEW.entity, 1);
NEW.status := COALESCE(NEW.status, 1);
NEW.datec := COALESCE(NEW.datec, NOW());
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
๐ฏ Transformation Result
Simplified PHP:
public function verify() {
return 0; // Triggers handle EVERYTHING automatically!
}