News

Magento Modul programmieren (Architektur): Setup, DI, Observer, Composer & Marketplace-ready

Von Erol Demirkoparan
7 min
Magento Modul programmieren (Architektur): Setup, DI, Observer, Composer & Marketplace-ready - Cloudox Software Agentur Blog

1) Zielbild: Architektur für ein wartbares Magento-2-Modul

Ein solides Magento-Modul trennt Verantwortlichkeiten: API/Service Layer (Business-Logik), Infrastructure (Persistence, Magento-Adapter), Presentation (Controller/Blocks/UI) und Integration (Events/Observer, Plugins). So bleibt das Modul updatefähig, testbar und leichter für den Magento Marketplace vorbereitbar (Qualität, Composer-Metadaten, saubere Dependencies).

Empfohlene Minimal-Struktur

  • app/code/Vendor/Module/ (oder als Composer-Package unter vendor/)
  • etc/module.xml, registration.php
  • etc/di.xml (Dependency Injection Konfiguration)
  • etc/events.xml (Observer Pattern)
  • Setup/Patch/Schema und/oder Setup/Patch/Data
  • Model/, Api/, Api/Data, Service/ (je nach Komplexität)

2) Modul-Bootstrap: Registrierung & Moduldeklaration

Magento lädt Module über registration.php und etc/module.xml. In einer Marketplace- bzw. Composer-Verteilung liegt das Modul meist als Package vor; in Projekten startet man oft unter app/code.

Screenshot der Ordnerstruktur in app/code/Vendor/Module mit etc/, Setup/, Model/
Screenshot der Ordnerstruktur in app/code/Vendor/Module mit etc/, Setup/, Model/

3) Dependency Injection (DI): Kernprinzip für saubere Abhängigkeiten

Dependency Injection bedeutet: Klassen erstellen ihre Abhängigkeiten nicht selbst (kein new für Services), sondern bekommen diese vom Magento Object Manager (DI-Container) bereitgestellt. Vorteile:

  • Testbarkeit (Mocks/Stubs für Abhängigkeiten)
  • Austauschbarkeit über preference, virtualType oder Argumente
  • Klare Architektur: Services konsumieren Interfaces statt konkreter Implementierungen

DI-Architektur-Empfehlung

  • Definiere Interfaces in Api/
  • Implementierungen in Model/ oder Service/
  • Verknüpfung per etc/di.xml (Preference) oder Constructor Type-Hints

4) Observer Pattern: Events nutzen statt Core zu verändern

Magento setzt stark auf das Observer Pattern: Ein Event wird „ausgelöst“ (dispatch), und ein oder mehrere Observer reagieren darauf. Das ist ideal, um Verhalten zu erweitern, ohne Core-Dateien anzupassen. Typische Events: Checkout, Customer Login, Order Place, Product Save.

Wann Observer statt Plugin?

  • Observer: Reaktion auf ein Event, oft „fire-and-forget“, mehrere Listener möglich.
  • Plugin (Interceptor): Methodeneingriff (before/after/around) gezielt auf eine PHP-Methode.

5) Datenbank & Setup: Patches statt Install/Upgrade Scripts

Für Magento 2 sind Schema/Data Patches (unter Setup/Patch) der moderne Weg. Sie sind versionierbar, wiederholbar kontrolliert und besser wartbar als klassische InstallSchema/UpgradeSchema-Skripte.

PHP Script for Module Setup (Schema Patch + Beispiel-Tabelle)

<?php
declare(strict_types=1);

namespace Vendor\Module\Setup\Patch\Schema;

use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\PatchVersionInterface;
use Magento\Framework\Setup\Patch\SchemaPatchInterface;

class CreateExampleEntityTable implements SchemaPatchInterface, PatchVersionInterface
{
    private ModuleDataSetupInterface $moduleDataSetup;

    public function __construct(ModuleDataSetupInterface $moduleDataSetup)
    {
        $this->moduleDataSetup = $moduleDataSetup;
    }

    public function apply(): self
    {
        $connection = $this->moduleDataSetup->getConnection();
        $this->moduleDataSetup->startSetup();

        $tableName = $this->moduleDataSetup->getTable('vendor_module_entity');

        if (!$connection->isTableExists($tableName)) {
            $table = $connection->newTable($tableName)
                ->addColumn(
                    'entity_id',
                    Table::TYPE_INTEGER,
                    null,
                    [
                        'identity' => true,
                        'unsigned' => true,
                        'nullable' => false,
                        'primary' => true
                    ],
                    'Entity ID'
                )
                ->addColumn(
                    'external_reference',
                    Table::TYPE_TEXT,
                    64,
                    ['nullable' => false],
                    'External Reference'
                )
                ->addColumn(
                    'payload',
                    Table::TYPE_TEXT,
                    '2M',
                    ['nullable' => true],
                    'JSON Payload'
                )
                ->addColumn(
                    'created_at',
                    Table::TYPE_TIMESTAMP,
                    null,
                    ['nullable' => false, 'default' => Table::TIMESTAMP_INIT],
                    'Created At'
                )
                ->addIndex(
                    $this->moduleDataSetup->getIdxName(
                        $tableName,
                        ['external_reference'],
                        AdapterInterface::INDEX_TYPE_UNIQUE
                    ),
                    ['external_reference'],
                    ['type' => AdapterInterface::INDEX_TYPE_UNIQUE]
                )
                ->setComment('Vendor Module - Example Entity');

            $connection->createTable($table);
        }

        $this->moduleDataSetup->endSetup();
        return $this;
    }

    public static function getDependencies(): array
    {
        return [];
    }

    public function getAliases(): array
    {
        return [];
    }

    public static function getVersion(): string
    {
        return '1.0.0';
    }
}

6) Composer & Distribution: Modul paketieren (Marketplace-/CI-ready)

Für professionelle Auslieferung (Kundenprojekte, CI/CD, oder Magento Marketplace) solltest du das Modul als Composer-Package pflegen. Composer übernimmt Autoloading, Versionschema, Abhängigkeiten und erleichtert Updates.

JSON Config for Composer (composer.json für ein Magento-2-Modul)

{
  "name": "vendor/module",
  "description": "Vendor Module - Example entity + observer + DI",
  "type": "magento2-module",
  "version": "1.0.0",
  "license": [
    "proprietary"
  ],
  "require": {
    "php": ">=8.1",
    "magento/framework": "*"
  },
  "autoload": {
    "files": [
      "registration.php"
    ],
    "psr-4": {
      "Vendor\\Module\\": ""
    }
  },
  "minimum-stability": "stable",
  "prefer-stable": true
}

7) DI + Observer zusammenbringen: Ereignisgetriebene Architektur

Ein sauberes Muster ist: Observer ist nur ein „Adapter“, der das Event annimmt und an einen Service weitergibt. Der Service kapselt Business-Logik in PHP und lässt sich testen. DI injiziert dem Observer den Service.

Mermaid: Request/Event → Observer → Service → Persistence

flowchart LR
  A[Magento Event Dispatch] --> B[Observer]
  B -->|DI inject| C[Application Service]
  C --> D[Repository Interface]
  D --> E[Repository Implementation]
  E --> F[(DB Table vendor_module_entity)]

8) Architektur-Checkliste (Table)

FeatureDetails
Modul-SchnittstellenInterfaces in Api/ definieren, Implementierungen trennen (Service/Model)
Dependency InjectionConstructor Injection, keine direkten ObjectManager-Calls, Preferences sparsam einsetzen
Observer PatternObserver schlank halten, Logik in Services, Events gezielt wählen
Setup/PatchesSchemaPatch/DataPatch statt Install/Upgrade; idempotent und versioniert
Composer Packagingtype=magento2-module, PSR-4 Autoload, versioning, require php/magento deps
Marketplace ReadinessSaubere Dependencies, keine Core-Hacks, klare Konfiguration, Tests/Static Analysis empfohlen

9) Typische Stolperfallen (und wie du sie architektonisch vermeidest)

  • Zu viel Logik im Observer: macht Events schwer testbar → Observer delegiert an Service.
  • ObjectManager direkt nutzen: erschwert Tests → DI verwenden.
  • Fette Preferences: riskant bei Updates/Kompatibilität → lieber Plugins oder eigene Services.
  • Setup-Skripte nicht idempotent: wiederholte Deployments brechen → isTableExists/columnExists prüfen.
  • Fehlende Composer-Metadaten: Updates schwierig → composer.json sauber pflegen.

10) Umsetzung im Projekt: Wann lohnt sich Unterstützung durch eine Magento Agentur?

Wenn du ein Modul für mehrere Shops ausrollen willst, Marketplace-Compliance anstrebst oder eine komplexe Architektur (Multi-Source Inventory, B2B, Headless, Integrationen) planst, lohnt sich die Unterstützung durch eine Magento Agentur—insbesondere für Code-Reviews, Quality Gates, Performance und Release-Prozesse.

Häufig gestellte Fragen

Autor

Erol Demirkoparan

Erol Demirkoparan

Senior Software Architect

Full-Stack & Cloud-Native Systems Expert. Spezialisiert auf AWS, Next.js und skalierbare SaaS-Architekturen. Building the future of automated SEO.

AWSNext.jsScalable SaaSSystem Architecture

Veröffentlicht am

8. Januar 2026

Das könnte Sie auch interessieren