myCRM/docs/PLUGIN_SYSTEM.md
olli 42e7bc7e10 feat(billing-module): Implementieren der Modulstruktur und Lizenzverwaltung
- Hinzufügen der DependencyInjection-Konfiguration für das Billing-Modul.
- Erstellen der Invoice-Entity mit API-Ressourcen und Berechtigungen.
- Konfigurieren der Services in services.yaml für das Billing-Modul.
- Implementieren von CLI-Commands zur Verwaltung von Modul-Lizenzen und zur Auflistung installierter Module.
- Erstellen eines API-Controllers zur Verwaltung von Modulen und Lizenzen.
- Hinzufügen eines EventListeners für das Booten von Modulen.
- Definieren von Interfaces für Lizenzvalidierung und Modul-Plugins.
- Implementieren der ModuleRegistry zur Verwaltung und Booten von Modulen.
- Erstellen eines LicenseValidator-Services zur Validierung und Registrierung von Lizenzen.
2025-12-03 15:14:07 +01:00

624 lines
14 KiB
Markdown

# Plugin-System für lizenzierbare Module
## Übersicht
Das myCRM Plugin-System ermöglicht die Installation und Verwaltung von optionalen, lizenzierbaren Modulen als separate Composer-Packages. Module können unabhängig vom Core entwickelt, installiert und lizenziert werden.
## Architektur-Prinzipien
### 1. **Lose Kopplung**
- Core kennt nur Interfaces, keine konkreten Module
- Module registrieren sich selbst über Symfony's Service-Tagging
- Keine Hard-Dependencies im Core-Code
### 2. **Lizenzbasierte Aktivierung**
- Module werden nur gebootet, wenn eine gültige Lizenz vorhanden ist
- Online-Validierung mit Offline-Fallback (Grace Period: 7 Tage)
- Caching zur Performance-Optimierung
### 3. **Plugin als Symfony Bundle**
- Jedes Modul ist ein vollständiges Symfony Bundle
- Eigene Entities, Controller, Services, Routes
- Optionale Frontend-Komponenten (Vue.js)
### 4. **Composer-basierte Installation**
- Module sind private Composer-Packages
- `composer require mycrm/modul-name` installiert das Modul
- Automatische Service-Registrierung über Symfony Flex
## Komponenten
### Core-Komponenten (im Hauptprojekt)
```
src/Plugin/
├── ModulePluginInterface.php # Interface für alle Module
├── LicenseValidatorInterface.php # Interface für Lizenzvalidierung
├── ModuleRegistry.php # Registry für alle installierten Module
src/Service/
└── LicenseValidator.php # Standard-Implementierung
src/EventListener/
└── ModuleBootListener.php # Bootet Module beim Request
src/Command/
├── ModuleListCommand.php # CLI: Module auflisten
└── ModuleLicenseCommand.php # CLI: Lizenzen verwalten
src/Controller/Api/
└── ModuleManagementController.php # API für Module-Verwaltung
config/
└── services_plugin.yaml # Service-Konfiguration
```
### Modul-Struktur (externes Package)
```
mycrm-modulname/
├── composer.json
├── src/
│ ├── ModulnameModulePlugin.php # Implementiert ModulePluginInterface
│ ├── ModulnameBundle.php # Symfony Bundle
│ ├── DependencyInjection/
│ │ ├── ModulnameExtension.php
│ │ └── Configuration.php
│ ├── Entity/ # Modul-spezifische Entities
│ ├── Controller/ # API-Controller
│ ├── Service/ # Business Logic
│ ├── Security/Voter/ # Permission-Voter
│ └── EventListener/
├── config/
│ ├── services.yaml
│ ├── routes.yaml
│ └── packages/
│ └── doctrine.yaml
├── assets/ # Frontend (optional)
│ └── js/
│ └── components/
└── migrations/ # Modul-spezifische Migrations
```
## Installation und Konfiguration
### 1. Core-System vorbereiten
Die Core-Komponenten sind bereits implementiert. Konfiguration aktivieren:
```bash
# services_plugin.yaml in services.yaml importieren
# config/services.yaml
imports:
- { resource: services_plugin.yaml }
```
Environment-Variablen setzen:
```bash
# .env.local
LICENSE_SERVER_URL=https://license.mycrm.local
INSTANCE_ID=unique-instance-identifier-here
# Beispiel-Lizenz für ein Modul
LICENSE_BILLING=your-license-key-here
```
### 2. Modul installieren
```bash
# Private Repository konfigurieren
composer config repositories.mycrm-billing vcs https://github.com/your-org/mycrm-billing-module
# Modul installieren
composer require mycrm/billing-module
# Bundle registrieren (falls nicht automatisch via Flex)
# config/bundles.php
return [
// ...
MyCRM\BillingModule\BillingBundle::class => ['all' => true],
];
# Migrations ausführen
php bin/console doctrine:migrations:migrate
```
### 3. Lizenz registrieren
**Via CLI:**
```bash
# Interaktiv
php bin/console app:module:license billing
# Direkt mit Key
php bin/console app:module:license billing YOUR_LICENSE_KEY
# Lizenz validieren
php bin/console app:module:license billing --validate
# Lizenz widerrufen
php bin/console app:module:license billing --revoke
# Alle Module auflisten
php bin/console app:module:list
```
**Via API (für Admin-UI):**
```bash
# Module auflisten
curl -X GET http://localhost:8000/api/modules \
-H "Authorization: Bearer YOUR_TOKEN"
# Lizenz registrieren
curl -X POST http://localhost:8000/api/modules/billing/license \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"license_key": "YOUR_LICENSE_KEY"}'
# Lizenz widerrufen
curl -X DELETE http://localhost:8000/api/modules/billing/license \
-H "Authorization: Bearer YOUR_TOKEN"
```
**Via Frontend (Vue.js):**
Die Komponente `ModuleManagement.vue` bietet eine Admin-Oberfläche zur Verwaltung:
```javascript
// In router.js hinzufügen
{
path: '/admin/modules',
name: 'module-management',
component: () => import('@/views/ModuleManagement.vue'),
meta: {
requiresAuth: true,
requiresRole: 'ROLE_ADMIN'
}
}
```
### 4. Cache leeren und testen
```bash
php bin/console cache:clear
php bin/console app:module:list
```
## Modul entwickeln
### Schritt 1: Projekt-Struktur erstellen
```bash
mkdir mycrm-mymodule
cd mycrm-mymodule
composer init
```
### Schritt 2: composer.json konfigurieren
```json
{
"name": "mycrm/mymodule",
"description": "Mein Custom CRM-Modul",
"type": "symfony-bundle",
"require": {
"php": ">=8.3",
"symfony/framework-bundle": "^7.1"
},
"autoload": {
"psr-4": {
"MyCRM\\MyModule\\": "src/"
}
}
}
```
### Schritt 3: Plugin-Klasse implementieren
```php
<?php
// src/MyModulePlugin.php
namespace MyCRM\MyModule;
use App\Plugin\LicenseValidatorInterface;
use App\Plugin\ModulePluginInterface;
class MyModulePlugin implements ModulePluginInterface
{
public function __construct(
private readonly LicenseValidatorInterface $licenseValidator
) {}
public function getIdentifier(): string
{
return 'mymodule';
}
public function getDisplayName(): string
{
return 'Mein Modul';
}
public function getVersion(): string
{
return '1.0.0';
}
public function getDescription(): string
{
return 'Beschreibung meines Moduls';
}
public function isLicensed(): bool
{
$info = $this->getLicenseInfo();
return $info['valid'];
}
public function getLicenseInfo(): array
{
return $this->licenseValidator->validate($this->getIdentifier());
}
public function boot(): void
{
if (!$this->isLicensed()) {
throw new \RuntimeException('Modul nicht lizenziert');
}
// Modul-spezifische Boot-Logik
}
public function getPermissionModules(): array
{
return ['mymodule'];
}
public function canInstall(): array
{
return ['success' => true, 'errors' => []];
}
}
```
### Schritt 4: Bundle-Klasse erstellen
```php
<?php
// src/MyModuleBundle.php
namespace MyCRM\MyModule;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class MyModuleBundle extends Bundle
{
public function getPath(): string
{
return \dirname(__DIR__);
}
}
```
### Schritt 5: Services konfigurieren
```yaml
# config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
# Plugin registrieren
MyCRM\MyModule\MyModulePlugin:
tags: ['app.module_plugin']
# Weitere Services...
MyCRM\MyModule\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
```
### Schritt 6: Entities mit ModuleAwareInterface
```php
<?php
// src/Entity/MyEntity.php
namespace MyCRM\MyModule\Entity;
use ApiPlatform\Metadata\ApiResource;
use App\Entity\ModuleAwareInterface;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ApiResource(
security: "is_granted('VIEW', 'mymodule')"
)]
class MyEntity implements ModuleAwareInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
// ... weitere Properties
public function getModuleName(): string
{
return 'mymodule';
}
}
```
### Schritt 7: Voter für Permissions (optional)
```php
<?php
// src/Security/Voter/MyEntityVoter.php
namespace MyCRM\MyModule\Security\Voter;
use App\Security\Voter\ModuleVoter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class MyEntityVoter extends Voter
{
public function __construct(
private readonly ModuleVoter $moduleVoter
) {}
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, ['VIEW', 'EDIT', 'DELETE'])
&& ($subject instanceof MyEntity || $subject === 'mymodule');
}
protected function voteOnAttribute(
string $attribute,
mixed $subject,
TokenInterface $token
): bool {
// An ModuleVoter delegieren
return $this->moduleVoter->vote($token, $subject, [$attribute]) === Voter::ACCESS_GRANTED;
}
}
```
## Lizenzserver-Integration
### Lizenzserver-Endpunkt
Der Lizenzserver muss folgenden Endpunkt bereitstellen:
```
POST /api/validate
Content-Type: application/json
{
"license_key": "YOUR-LICENSE-KEY",
"module": "billing",
"instance_id": "unique-instance-id",
"version": "1.0.0"
}
Response (200 OK):
{
"valid": true,
"expires_at": "2026-12-31T23:59:59Z",
"licensed_to": "Firma GmbH",
"features": ["feature1", "feature2"],
"message": "Lizenz gültig"
}
Response (400/403):
{
"valid": false,
"message": "Lizenz abgelaufen / ungültig / etc."
}
```
### Lizenzschlüssel-Format (JWT)
Empfohlenes Format als JWT mit folgenden Claims:
```json
{
"sub": "billing",
"customer_id": "CUST-12345",
"licensed_to": "Firma GmbH",
"expires_at": "2026-12-31T23:59:59Z",
"features": ["invoicing", "recurring_billing"],
"iat": 1704278400,
"exp": 1767350399
}
```
## Permission-System Integration
### Automatische Modul-Registrierung
Module, die `getPermissionModules()` implementieren, werden automatisch im Permission-System verfügbar:
```php
public function getPermissionModules(): array
{
return [
'billing', // Hauptmodul
'invoices', // Sub-Modul
'payments', // Weiteres Sub-Modul
];
}
```
Diese Module können dann in der Role-Verwaltung zugewiesen werden.
### Permission-Checks in Modul-Code
```php
// In Controller
$this->denyAccessUnlessGranted('VIEW', 'billing');
// In Voter
$user->hasModulePermission('billing', 'edit');
// In API Platform
#[ApiResource(
security: "is_granted('VIEW', 'billing')"
)]
```
## Frontend-Integration
### Modul-Status im Frontend verfügbar machen
```javascript
// In Symfony Twig-Template (oder API-Endpunkt)
<script>
window.myCRM = {
modules: {{ modules_status|json_encode|raw }}
}
</script>
```
### Bedingte Route-Registrierung
```javascript
// router.js
import { moduleRegistry } from './moduleRegistry'
const routes = [
// Core-Routes...
]
// Modul-Routes nur hinzufügen, wenn aktiv
if (moduleRegistry.isActive('billing')) {
routes.push({
path: '/billing',
component: () => import('@/views/BillingDashboard.vue')
})
}
```
### Modul-Komponenten lazy-loaden
```javascript
// In App.vue oder Layout
import { computed } from 'vue'
import { useModules } from '@/composables/useModules'
const { isModuleActive } = useModules()
const showBillingMenu = computed(() => isModuleActive('billing'))
```
## Testing
### Unit-Tests für Plugin
```php
<?php
namespace MyCRM\MyModule\Tests;
use MyCRM\MyModule\MyModulePlugin;
use PHPUnit\Framework\TestCase;
class MyModulePluginTest extends TestCase
{
public function testGetIdentifier(): void
{
$licenseValidator = $this->createMock(LicenseValidatorInterface::class);
$plugin = new MyModulePlugin($licenseValidator);
$this->assertSame('mymodule', $plugin->getIdentifier());
}
public function testIsLicensedReturnsTrueWithValidLicense(): void
{
$licenseValidator = $this->createMock(LicenseValidatorInterface::class);
$licenseValidator->method('validate')
->willReturn(['valid' => true]);
$plugin = new MyModulePlugin($licenseValidator);
$this->assertTrue($plugin->isLicensed());
}
}
```
## Troubleshooting
### Modul wird nicht erkannt
1. Bundle in `config/bundles.php` registriert?
2. Composer-Autoloading aktualisiert? (`composer dump-autoload`)
3. Cache geleert? (`php bin/console cache:clear`)
4. Service-Tag `app.module_plugin` gesetzt?
### Lizenz wird nicht validiert
1. Environment-Variablen korrekt gesetzt?
2. Lizenzserver erreichbar? (Netzwerk, Firewall)
3. Cache-Problem? Cache löschen und neu validieren
4. Logs prüfen: `tail -f var/log/dev.log | grep -i license`
### Modul bootet nicht
1. Lizenz gültig? (`php bin/console app:module:license mymodule --validate`)
2. Abhängigkeiten erfüllt? (`php bin/console app:module:list`)
3. Doctrine-Mappings korrekt? (`php bin/console doctrine:schema:validate`)
4. Fehler in `boot()`-Methode? Logs prüfen
## Best Practices
### 1. Versionierung
- Semantic Versioning verwenden (Major.Minor.Patch)
- Breaking Changes nur in Major-Versionen
- Migrations mit Rollback-Fähigkeit
### 2. Fehlerbehandlung
- Keine Exceptions in `isLicensed()` werfen
- Detaillierte Fehlermeldungen in `getLicenseInfo()`
- Graceful Degradation bei Netzwerkfehlern
### 3. Performance
- Lizenz-Status cachen (bereits implementiert)
- Lazy-Loading für Frontend-Komponenten
- Doctrine-Queries optimieren
### 4. Sicherheit
- Nie Lizenzschlüssel im Frontend exponieren
- API-Endpunkte mit `ROLE_ADMIN` schützen
- Input-Validierung in allen Controllern
### 5. Dokumentation
- README.md mit Installation-Guide
- API-Dokumentation (OpenAPI/Swagger)
- Changelog pflegen
## Beispiel: Komplettes Mini-Modul
Ein vollständiges Mini-Beispiel findet sich in:
- `/docs/example-module/` - Backend-Code
- `/docs/EXAMPLE_MODULE_STRUCTURE.md` - Struktur-Übersicht
## Support und Weiterentwicklung
### Geplante Features
- [ ] Multi-Tenancy-Support (verschiedene Instanzen, eine Lizenz)
- [ ] Offline-Lizenzierung (Air-Gapped Systeme)
- [ ] Automatische Updates über Composer
- [ ] Modul-Marketplace Integration
- [ ] Rollback-Mechanismus bei fehlgeschlagenen Installations
### Beitragen
Weitere Module können nach diesem Pattern entwickelt werden. Core-Interfaces sind stabil und abwärtskompatibel.