Active Record Pattern
in Magento 2
Das Active Record Pattern kombiniert Datenbankzugriff und Geschäftslogik in einem Objekt. Magento 2 baut historisch darauf auf — aber das moderne Magento bevorzugt das Repository Pattern. Wie beide zusammenspielen und was man heute verwenden soll.
Inhaltsverzeichnis
1. Das Active Record Pattern
Das Active Record Pattern (Martin Fowler) kombiniert die Datenbankzeile und die Geschäftslogik in einem Objekt. Das Objekt weiß, wie es sich selbst speichert, lädt und löscht:
2. Active Record in Magento 2: Model und ResourceModel
Magento 2 trennt das Active Record Pattern in zwei Klassen: Model (Daten + Geschäftslogik) und ResourceModel (Datenbankzugriff). Das Model delegiert DB-Operationen an das ResourceModel:
3. Probleme des Active Record Patterns
Das Active Record Pattern in Magento 2 hat bekannte Schwächen:
- Verletzt SRP — das Model enthält Daten, Geschäftslogik UND Datenbankwissen
- Schwer testbar —
$model->load()und$model->save()brauchen Datenbankzugriff - Magic methods —
getData()/setData()sind nicht typsicher, keine IDE-Unterstützung - AbstractModel::load() deprecated — ab Magento 2.4 als deprecated markiert
- Keine Interface-Contracts — nicht über Service Contracts erreichbar
4. Repository Pattern als Alternative
Das Repository Pattern trennt Datenbankzugriff vollständig von der Geschäftslogik. Das Model ist eine reine Datenstruktur (Value Object), das Repository kümmert sich um Persistenz:
5. Vollständige Repository-Implementierung
Mironsoft
Magento 2 Repository Pattern & Service Contracts
Legacy Active Record durch Repository ablösen?
Wir migrieren Magento 2 Module von Model::load()-basiertem Active Record zu sauberem Repository Pattern mit Service Contracts, SearchCriteria und vollständiger Testabdeckung.
Service Contracts
Repository Interfaces und Data Interfaces als stabile API-Schicht
Migration
load()-Aufrufe durch Repository::getById() ersetzen, sicher und schrittweise
Unit Tests
Repository-Klassen mocken und isoliert testen ohne Datenbankzugriff
6. Zusammenfassung
Magento 2 basiert historisch auf Active Record (Model + ResourceModel), bewegt sich aber zunehmend zum Repository Pattern. AbstractModel::load() ist deprecated. Neuer Code verwendet immer Repository-Interfaces als Service Contracts — testbar, typsicher und ohne direkte DB-Abhängigkeit im Geschäftscode.
Active Record & Repository — Das Wichtigste auf einen Blick
load() deprecated
AbstractModel::load() ist seit Magento 2.4 deprecated. Stattdessen: Repository::getById() oder Repository::get().
Model + ResourceModel
Model = Daten + Geschäftslogik. ResourceModel = Datenbankzugriff. Diese Trennung ist Magento 2's Antwort auf reines Active Record.
Repository Interface
PostRepositoryInterface mit getById(), save(), delete(), getList(). In di.xml verknüpft mit konkreter Implementierung.
Data Interface
PostInterface mit typisierten Gettern/Settern. Implementiert vom Model. API-Clients kennen nur das Interface, nicht das konkrete Model.
7. FAQ: Active Record Pattern in Magento 2
1 Was ist das Active Record Pattern?
2 Warum ist AbstractModel::load() deprecated?
Repository::getById().3 Model vs. ResourceModel — der Unterschied?
4 Wie erstellt man ein Repository?
getById(), save(), delete(), getList(). Implementierung mit PostFactory + PostResource + CollectionFactory. In di.xml als Preference verknüpfen.5 Wie ladet man ein Produkt korrekt?
$productRepository->getById(42) oder $productRepository->get('SKU-001'). Nicht mehr $product->load(42) — deprecated.6 Wie testet man ein Repository?
createMock(PostRepositoryInterface::class). Klassen die das Repository nutzen bekommen den Mock per Constructor Injection. Kein Datenbankzugriff im Unit Test.7 Was ist ein Data Interface?
8 Kann man load() noch in 2.4.8 verwenden?
9 Was passiert intern bei Repository::save()?
postResource->save($post) → prüft INSERT/UPDATE → dispatcht _beforeSave/_afterSave Events → führt SQL aus → setzt Entity-ID nach INSERT. Bei Fehler: CouldNotSaveException.10 Wie implementiert man delete() im Repository?
postResource->delete($post) aufrufen. CouldNotDeleteException bei Fehler werfen. Für deleteById(int $id): erst getById(), dann delete().