Architettura del Sistema

Panoramica dell'architettura tecnica di UniMsg, inclusi stack tecnologico, pattern di design e flusso delle richieste.

Stack Tecnologico

Componente Tecnologia Versione
Framework CodeIgniter 4.x
Linguaggio PHP 8.1+
Database MySQL 8.0+
Cache Redis 6.x
Queue Redis / Database -
Web Server Nginx / Apache -

Pattern Architetturali

Service Layer Pattern

La logica di business è incapsulata in servizi dedicati, separando controller (che gestiscono HTTP) dalla logica applicativa.

app/
├── Controllers/
│   └── Api/
│       └── V1/
│           ├── SmsController.php
│           ├── WhatsAppController.php
│           └── EmailController.php
├── Services/
│   ├── SmsService.php
│   ├── WhatsAppService.php
│   ├── EmailService.php
│   └── TelegramService.php
└── Models/
    ├── MessageModel.php
    ├── ClientModel.php
    └── CreditModel.php

Factory Pattern (Gateway)

I gateway di invio sono gestiti tramite Factory pattern, permettendo di switchare provider senza modificare la logica di business.

// GatewayFactory.php
class GatewayFactory
{
    public static function create(string $type, string $provider): GatewayInterface
    {
        return match ($type) {
            'sms' => match ($provider) {
                'twilio' => new TwilioSmsGateway(),
                'nexmo' => new NexmoSmsGateway(),
                'skebby' => new SkebbySmsGateway(),
                default => throw new InvalidGatewayException()
            },
            'whatsapp' => new WhatsAppBusinessGateway(),
            'email' => match ($provider) {
                'sendgrid' => new SendGridGateway(),
                'mailgun' => new MailgunGateway(),
                default => new SmtpGateway()
            },
            'telegram' => new TelegramBotGateway(),
            default => throw new InvalidChannelException()
        };
    }
}

Repository Pattern

L'accesso ai dati è astratto tramite repository, facilitando test e manutenzione.

interface MessageRepositoryInterface
{
    public function find(string $id): ?Message;
    public function findByClient(int $clientId, array $filters): array;
    public function create(array $data): Message;
    public function updateStatus(string $id, string $status): bool;
}

Flusso Richiesta API

Ecco il flusso completo di una richiesta API per l'invio di un SMS:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Client    │────▶│   Nginx     │────▶│  PHP-FPM    │
└─────────────┘     └─────────────┘     └──────┬──────┘
                                               │
                    ┌──────────────────────────┼──────────────────────────┐
                    │                          ▼                          │
                    │  ┌─────────────┐   ┌─────────────┐   ┌──────────┐  │
                    │  │   Filter    │──▶│ Controller  │──▶│ Service  │  │
                    │  │  (Auth)     │   │             │   │          │  │
                    │  └─────────────┘   └─────────────┘   └────┬─────┘  │
                    │                                           │        │
                    │  ┌─────────────┐   ┌─────────────┐       │        │
                    │  │   Redis     │◀──│   Queue     │◀──────┘        │
                    │  │  (Cache)    │   │   Job       │                │
                    │  └─────────────┘   └──────┬──────┘                │
                    │                          │                        │
                    │                          ▼                        │
                    │                   ┌─────────────┐                 │
                    │                   │   Gateway   │                 │
                    │                   │  (Twilio)   │                 │
                    │                   └──────┬──────┘                 │
                    │                          │                        │
                    │                          ▼                        │
                    │                   ┌─────────────┐                 │
                    │                   │   MySQL     │                 │
                    │                   │  (Update)   │                 │
                    │                   └─────────────┘                 │
                    │                                                   │
                    └───────────────────────────────────────────────────┘

Passi Dettagliati

  1. Richiesta HTTP - Il client invia POST a /v1/sms/send
  2. Auth Filter - Valida il Bearer token OAuth2
  3. Controller - Valida input e chiama il Service
  4. Service - Verifica crediti, crea record messaggio
  5. Queue - Il job di invio viene accodato
  6. Response - Ritorna immediatamente con status: queued
  7. Worker - Processa il job dalla coda
  8. Gateway - Invia il messaggio via provider (es: Twilio)
  9. Update - Aggiorna lo stato in database
  10. Webhook - Notifica il client (se configurato)

Schema Database

Tabelle principali del sistema:

-- Clients (account API)
CREATE TABLE clients (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    client_id VARCHAR(64) UNIQUE NOT NULL,
    client_secret VARCHAR(255) NOT NULL,
    credits DECIMAL(10,2) DEFAULT 0,
    status ENUM('active', 'suspended') DEFAULT 'active',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- Messages
CREATE TABLE messages (
    id VARCHAR(64) PRIMARY KEY,
    client_id INT NOT NULL,
    channel ENUM('sms', 'whatsapp', 'email', 'telegram') NOT NULL,
    recipient VARCHAR(255) NOT NULL,
    content TEXT,
    status ENUM('queued', 'sent', 'delivered', 'read', 'failed') DEFAULT 'queued',
    gateway VARCHAR(64),
    gateway_message_id VARCHAR(255),
    credits_used DECIMAL(10,2) DEFAULT 0,
    error_code VARCHAR(64),
    error_message TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    sent_at TIMESTAMP NULL,
    delivered_at TIMESTAMP NULL,
    FOREIGN KEY (client_id) REFERENCES clients(id)
);

-- Access Tokens
CREATE TABLE oauth_access_tokens (
    id VARCHAR(64) PRIMARY KEY,
    client_id INT NOT NULL,
    scopes JSON,
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (client_id) REFERENCES clients(id)
);

-- Webhooks Configuration
CREATE TABLE webhooks (
    id INT PRIMARY KEY AUTO_INCREMENT,
    client_id INT NOT NULL,
    url VARCHAR(500) NOT NULL,
    secret VARCHAR(255) NOT NULL,
    events JSON,
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (client_id) REFERENCES clients(id)
);

-- Webhook Logs
CREATE TABLE webhook_logs (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    webhook_id INT NOT NULL,
    event VARCHAR(64) NOT NULL,
    payload JSON,
    response_code INT,
    response_body TEXT,
    attempts INT DEFAULT 1,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (webhook_id) REFERENCES webhooks(id)
);

Gestione Code

I messaggi vengono processati in modo asincrono tramite job queue:

// SendMessageJob.php
class SendMessageJob extends BaseJob
{
    protected string $messageId;

    public function handle(): void
    {
        $message = $this->messageRepository->find($this->messageId);

        if (!$message) {
            return;
        }

        try {
            $gateway = GatewayFactory::create(
                $message->channel,
                $message->client->getGatewayFor($message->channel)
            );

            $result = $gateway->send($message);

            $this->messageRepository->updateStatus(
                $message->id,
                'sent',
                ['gateway_message_id' => $result->externalId]
            );

        } catch (GatewayException $e) {
            $this->messageRepository->updateStatus(
                $message->id,
                'failed',
                [
                    'error_code' => $e->getCode(),
                    'error_message' => $e->getMessage()
                ]
            );

            // Refund credits
            $this->creditService->refund($message->client_id, $message->credits_used);
        }
    }
}

Scalabilità

Componenti Scalabili

Metriche Chiave

Metrica Target
API Response Time (p95) < 200ms
Message Processing Time < 5 secondi
Uptime 99.9%
Throughput 1000+ msg/sec