# ADR001-WP2-005 – Relational Repositories and Data Mapper

**Status:** Proposed  
**Autor:** ED  
**Reviewer:** LA  
**Datum:** 2026-07-20

---

# 1. Kontext

Mit den bisherigen Workpackages wurde die technische und relationale Grundlage für die Persistenz von Reconcilix geschaffen:

- WP1 – Domain Integration
- WP2-001 – Runtime Infrastructure
- WP2-002 – Composition Root
- WP2-003 – Persistence Foundation
- WP2-004 – Schema and Migrations

WP2-003 stellt insbesondere bereit:

- zentrale Datenbankkonfiguration,
- `DatabaseConnectionFactory`,
- PDO-basierte Datenbankverbindung,
- `TransactionManager`,
- Integration der Persistenzinfrastruktur in den Composition Root.

WP2-004 stellt bereit:

- das versionierte DM002-Basisschema,
- den Migration Runner,
- die technische Migration Registry,
- ein reproduzierbar initialisierbares Datenbankschema für MySQL 8 und MariaDB 10.4.

Die Datenbank enthält damit die Tabellenstruktur für fachliche Reconciliation-Daten. Es existiert jedoch noch keine Implementierung, die Domain Objects kontrolliert auf diese relationale Struktur abbildet.

WP2-005 führt deshalb die schreibende relationale Persistenz ein.

---

# 2. Problemstellung

Die Application Layer arbeitet mit fachlichen Domain Objects und Repository Contracts. Das relationale Persistenzmodell DM002 arbeitet dagegen mit:

- Tabellen,
- technischen Primärschlüsseln,
- Fremdschlüsseln,
- Insert-Reihenfolgen,
- SQL-Datentypen,
- Datenbank-Constraints.

Diese beiden Modelle dürfen nicht direkt miteinander vermischt werden.

Insbesondere soll die Application Layer weder

- Tabellen kennen,
- SQL-Anweisungen ausführen,
- technische Datenbank-IDs verwalten,
- Fremdschlüsselreihenfolgen koordinieren,
- PDO-Abhängigkeiten erhalten.

Gleichzeitig darf die Infrastructure Layer die fachliche Struktur nicht in eine Sammlung unabhängiger Tabellenoperationen auflösen, wenn dadurch fachliche Aggregate und Invarianten verloren gehen.

WP2-005 benötigt deshalb eine klare Trennung zwischen:

1. dem fachlichen Repository-Vertrag,
2. der Koordination der Aggregate-Persistenz,
3. den technischen Data Mappern,
4. der relationalen Identitätsübersetzung.

---

# 3. Entscheidung

WP2-005 führt eine relationale Repository-Implementierung für das fachliche Reconciliation-Aggregat sowie mehrere kleine spezialisierte Data Mapper ein.

Die zentrale Entscheidung lautet:

> Nach außen wird ein fachliches Aggregate Repository angeboten.  
> Nach innen delegiert die relationale Repository-Implementierung an mehrere spezialisierte Data Mapper.

Die Application Layer speichert ein vollständiges fachliches Ergebnis über einen bestehenden oder präzisierten Repository Contract.

Beispielhaft:

```php
$repository->save($reconciliationResult);
```

Die Application Layer kennt dabei weder Tabellen noch technische Primärschlüssel.

Die konkrete relationale Implementierung koordiniert intern die erforderlichen Mapper und Insert-Operationen.

---

# 4. Architekturprinzip

Ein Repository repräsentiert fachlichen Zugriff auf ein Aggregate, nicht technischen Zugriff auf einzelne Tabellen.

Daraus folgt:

- kein `CandidateItemRepository` allein wegen der Tabelle `candidate_item`,
- kein `MatchDecisionRepository` allein wegen der Tabelle `match_decision`,
- kein `InterpretationNodeRepository` allein wegen der Tabelle `interpretation_node`,
- keine tabellenorientierte Repository-API in der Application Layer.

Die Tabellenstruktur von DM002 bleibt eine technische Projektion des Domain Models.

Das Repository schützt die Repository Boundary und verhindert, dass relationale Details in Domain oder Application eindringen.

---

# 5. Aggregate Repository

## 5.1 Fachlicher Schnitt

WP2-005 verwendet ein fachliches Repository für das persistierbare Reconciliation-Aggregat.

Vorgesehene konkrete Implementierung:

```text
RelationalReconciliationRepository
```

Der genaue Name des bestehenden Application-Contracts wird vor der Implementierung gegen den aktuellen Codebestand geprüft. Ein neuer paralleler Contract wird nur eingeführt, wenn der vorhandene Vertrag fachlich nicht ausreicht.

## 5.2 Verantwortung

Das Repository ist verantwortlich für:

- Annahme eines vollständigen persistierbaren Domain-Aggregats,
- Koordination der schreibenden Mapper,
- Einhaltung der erforderlichen Persistierungsreihenfolge,
- Übersetzung fachlicher Referenzen in technische Fremdschlüssel,
- Sicherstellung, dass abhängige Datensätze konsistent miteinander verknüpft werden,
- Weitergabe technischer Fehler als geeignete Persistence Exception.

Das Repository ist nicht verantwortlich für:

- fachliche Candidate Discovery,
- Ranking,
- OpenRefine-Request-Mapping,
- HTTP-Ausgabe,
- Migrationen,
- Rehydration,
- allgemeine Transaktionssteuerung des produktiven Application Flows,
- Reporting-Abfragen.

## 5.3 Aggregate Boundary

Für WP2-005 wird das Reconciliation-Ergebnis als zusammenhängende Persistenzeinheit behandelt.

Die persistierte Struktur umfasst, soweit im aktuellen Domain-Aggregat vorhanden:

```text
ReconciliationResult
├── SourceValue
├── InterpretationGraph
│   ├── InterpretationNode
│   └── InterpretationNodeProperty
├── CandidateItem
└── MatchDecision
```

Tabellenbestandteile aus DM002, die im aktuellen Domain Flow noch nicht fachlich befüllt werden, bleiben unberührt und werden nicht künstlich mit Platzhalterdaten versehen.

---

# 6. Spezialisierte Data Mapper

WP2-005 verwendet mehrere kleine Data Mapper statt eines monolithischen Aggregate Mappers.

Vorgesehene Mapper sind beispielsweise:

```text
SourceValueMapper
InterpretationGraphMapper
InterpretationNodeMapper
InterpretationNodePropertyMapper
ReconciliationResultMapper
CandidateItemMapper
MatchDecisionMapper
```

Die endgültige Liste wird anhand des tatsächlich persistierbaren Domain-Aggregats und des aktuellen DM002-Schemas festgelegt.

## 6.1 Verantwortung eines Mappers

Ein Data Mapper kennt:

- den zugehörigen Domain-Teil,
- die relevante Tabelle,
- die Spaltenzuordnung,
- SQL-Parameter,
- technische Primär- und Fremdschlüssel,
- erforderliche Typkonvertierungen.

Ein Data Mapper kennt nicht:

- den vollständigen Application Flow,
- HTTP oder OpenRefine,
- Candidate Discovery,
- andere fachliche Aggregate,
- globale Transaktionsentscheidungen,
- den Composition Root.

## 6.2 Granularität

Mapper werden entlang klarer relationaler Mapping-Verantwortlichkeiten geschnitten.

Dabei gilt:

> Klein und spezialisiert bedeutet nicht zwingend exakt ein Mapper pro Tabelle.

Ein eigener Mapper ist sinnvoll, wenn eine Mapping-Verantwortung:

- eigenständig testbar ist,
- eigene Insert- oder Update-Regeln besitzt,
- eigene Typ- oder Identitätsübersetzungen benötigt,
- nicht nur aus einem trivialen SQL-Fragment besteht.

Sehr kleine abhängige Tabellen dürfen gemeinsam mit ihrem fachlich unmittelbar zugehörigen Mapper behandelt werden, sofern dadurch Verantwortung und Testbarkeit klarer werden.

Diese Entscheidung verhindert sowohl:

- einen monolithischen Mapper mit zu vielen Verantwortlichkeiten,
- als auch eine künstliche Überfragmentierung in technisch bedeutungslose Kleinstklassen.

---

# 7. Koordination zwischen Repository und Mappern

Das Repository koordiniert. Die Mapper führen die konkreten relationalen Operationen aus.

Vereinfachter Ablauf:

```text
Application Service
        │
        ▼
ReconciliationRepository Contract
        │
        ▼
RelationalReconciliationRepository
        │
        ├── SourceValueMapper
        ├── InterpretationGraphMapper
        ├── InterpretationNodeMapper
        ├── ReconciliationResultMapper
        ├── CandidateItemMapper
        └── MatchDecisionMapper
        │
        ▼
PDO
```

Die konkrete Insert-Reihenfolge wird aus den Fremdschlüsselbeziehungen des DM002-Schemas abgeleitet.

Voraussichtlich:

1. `source_value`
2. `interpretation_graph`
3. `interpretation_node`
4. `interpretation_node_property`
5. `reconciliation_result`
6. `candidate_item`
7. `match_decision`

Die endgültige Reihenfolge wird vor der Implementierung gegen die Baseline-Migration und die tatsächlichen Foreign Keys geprüft.

---

# 8. Identitäten und technische Primärschlüssel

Technische Datenbank-IDs bleiben vollständig innerhalb der Infrastructure Layer.

Domain Objects erhalten keine relationalen Primärschlüssel nur zur Unterstützung der Persistenz.

Insbesondere werden keine Felder wie

```text
databaseId
rowId
foreignKeyId
```

in Domain Objects eingeführt.

Die Mapper liefern bei Inserts technische IDs an das koordinierende Repository zurück, damit nachfolgende abhängige Datensätze korrekt verknüpft werden können.

Beispiel:

```text
SourceValueMapper::insert(...)
        │
        └── sourceValueId

ReconciliationResultMapper::insert(..., sourceValueId, graphId)
        │
        └── reconciliationResultId

CandidateItemMapper::insert(..., reconciliationResultId)
        │
        └── candidateItemId
```

Diese IDs verlassen die Infrastructure Layer nicht.

---

# 9. Fachliche URIs und relationale Referenzen

Fachliche Identitäten werden weiterhin durch Domain-Werte und kanonische URIs repräsentiert.

Die relationale Datenbank verwendet ergänzend technische Primär- und Fremdschlüssel.

Besonders relevant ist die Zuordnung von:

```text
MatchDecision.selectedCandidateUri
```

zu:

```text
match_decision.selected_candidate_item_id
```

Der `MatchDecisionMapper` beziehungsweise das koordinierende Repository löst diese Zuordnung über die während desselben Persistierungsvorgangs erzeugte Candidate-ID auf.

Die Auflösung erfolgt nicht über eine erneute freie Datenbanksuche, wenn die Identitätszuordnung bereits innerhalb des aktuellen Schreibvorgangs bekannt ist.

Kann eine fachlich ausgewählte Candidate URI keinem Candidate Item des zugehörigen Reconciliation Results eindeutig zugeordnet werden, muss die Persistierung fehlschlagen.

Eine stillschweigende Speicherung mit `NULL` oder einer falschen Referenz ist unzulässig, sofern die Domain-Entscheidung eine Auswahl enthält.

---

# 10. Schreibstrategie

WP2-005 implementiert zunächst eine explizite Insert-orientierte Persistenzstrategie.

Das Workpackage führt nicht automatisch ein:

- generisches Upsert,
- Merge-Semantik,
- Unit of Work,
- Dirty Tracking,
- Identity Map,
- automatisches Objektgraph-Diffing.

Ob Wiederholung, Ersetzung oder Versionierung eines bereits persistierten fachlichen Ergebnisses später unterstützt wird, benötigt eine eigene fachliche und architektonische Entscheidung.

Bis dahin muss das Verhalten bei doppelten fachlichen Identitäten explizit sein:

- entweder kontrollierter Fehler durch Datenbank-Constraint,
- oder klar dokumentierte Idempotenzregel, sofern bereits durch vorhandene fachliche Schlüssel möglich.

Eine implizite Überschreibung bestehender Daten findet nicht statt.

---

# 11. Transaktionsgrenze

Die Mapper führen keine eigenen unabhängigen Commits aus.

Alle Mapper verwenden dieselbe von außen bereitgestellte PDO-Verbindung.

Damit kann das Repository als zusammenhängende Persistenzeinheit innerhalb einer Transaktion ausgeführt werden.

Für WP2-005 gilt:

- keine Commit- oder Rollback-Logik in einzelnen Mappern,
- keine verschachtelten Mapper-Transaktionen,
- keine globalen Verbindungen,
- keine Auto-Migration vor dem Schreiben.

Die vollständige transaktionale Einbindung in den produktiven Reconciliation Application Flow bleibt WP2-007 vorbehalten.

WP2-005 muss seine Repository-Implementierung jedoch so gestalten, dass sie durch den bereits vorhandenen `TransactionManager` atomar ausgeführt werden kann.

Für fokussierte Integrationstests darf das Repository innerhalb einer explizit gestarteten Testtransaktion ausgeführt werden.

---

# 12. Fehlerbehandlung

Die Infrastructure Layer übersetzt technische Datenbankfehler in eine kleine, aussagekräftige Persistence-Fehlerstruktur.

Mindestens unterschieden werden sollen:

- Verbindungs- beziehungsweise Ausführungsfehler,
- Constraint-Verletzung,
- inkonsistente interne Identitätszuordnung,
- fehlende ausgewählte Candidate-Referenz,
- nicht unterstützter Domain-Zustand.

SQL, DSN, Benutzername oder Passwort dürfen nicht unkontrolliert in öffentliche Fehlermeldungen gelangen.

PDO-Exceptions dürfen als interne Ursache erhalten bleiben, aber nicht direkt die Application- oder HTTP-Grenze bestimmen.

WP2-005 führt keine allgemeine Logging-Abstraktion ein.

---

# 13. Abhängigkeiten und Schichten

Die Dependency Direction bleibt unverändert:

```text
Domain
   ▲
   │
Application
   ▲
   │
Infrastructure
```

Die Application Layer definiert oder besitzt den Repository Contract.

Die Infrastructure Layer implementiert ihn.

Zulässige Abhängigkeiten:

```text
RelationalReconciliationRepository
    → Application Repository Contract
    → Domain Objects
    → Data Mapper
    → PDO
```

Unzulässige Abhängigkeiten:

```text
Domain → PDO
Domain → Data Mapper
Application → konkrete relationale Mapper
Application → Tabellen- oder SQL-Struktur
Mapper → Controller
Mapper → OpenRefine Transport
```

---

# 14. Composition Root

Der bestehende `ReconciliationCompositionRoot` ist der einzige Ort für die konkrete Verdrahtung der relationalen Repository-Implementierung im HTTP-Objektgraphen.

Der Composition Root darf erzeugen:

- PDO-Verbindung über `DatabaseConnectionFactory`,
- spezialisierte Mapper,
- `RelationalReconciliationRepository`,
- erforderliche Factory- oder Application-Service-Abhängigkeiten.

Er enthält keine Mapping- oder Persistierungslogik.

Da die produktive transaktionale Integration erst in WP2-007 erfolgt, wird die Repository-Implementierung in WP2-005 nur soweit in den Objektgraphen eingebunden, wie dies ohne Änderung des produktiven Application Flows sinnvoll und testbar ist.

Eine ungenutzte oder vorzeitig produktiv aufgerufene Verdrahtung soll vermieden werden.

---

# 15. Teststrategie

WP2-005 ergänzt Unit- und Integrationstests.

## 15.1 Mapper-Tests

Jeder relevante Mapper wird mindestens auf folgende Aspekte geprüft:

- korrekte Spaltenzuordnung,
- korrekte Parameterbindung,
- Rückgabe technischer Primärschlüssel,
- korrekte Behandlung von `NULL`,
- Erhalt kanonischer URI-Werte,
- korrekte Typkonvertierung,
- Constraint-Fehler werden nicht verschluckt.

## 15.2 Repository-Integrationstest

Ein vollständiges Domain-Aggregat wird gegen eine eigens dafür vorgesehene Testdatenbank persistiert.

Geprüft wird mindestens:

- alle erwarteten Tabellenzeilen wurden erzeugt,
- Fremdschlüssel sind korrekt,
- Candidate-Reihenfolge beziehungsweise `rank` bleibt erhalten,
- ausgewählte Candidate URI wird korrekt auf `selected_candidate_item_id` abgebildet,
- nicht ausgewählte Candidates bleiben unverändert,
- technische IDs gelangen nicht in Domain Objects,
- der Vorgang kann innerhalb einer Testtransaktion vollständig zurückgerollt werden.

## 15.3 Negativtests

Mindestens folgende Fehlerfälle werden geprüft:

- ausgewählte Candidate URI existiert nicht im Candidate-Satz,
- doppelte oder mehrdeutige Candidate URI verhindert eindeutige Auswahl,
- Datenbank-Constraint wird verletzt,
- nicht unterstützter Domain-Zustand wird vor oder während des Mappings erkannt,
- Fehler in einem späten Mapper hinterlässt bei transaktionaler Ausführung keinen Teilbestand.

## 15.4 Regression

Folgende bestehende Tests müssen unverändert erfolgreich bleiben:

- WP1 Domain Foundation,
- WP1 Application Boundary,
- WP1 Application Flow,
- WP2 Runtime,
- WP2 Composition Root,
- WP2 Persistence Foundation,
- WP2 Schema and Migrations,
- Manifest-Endpunkt,
- produktive OpenRefine-Reconciliation.

Automatisierte Tests dürfen nicht gegen eine produktive Datenbank ausgeführt werden.

---

# 16. Nicht Bestandteil dieses Workpackages

WP2-005 implementiert ausdrücklich nicht:

- Rehydration vollständiger Domain-Aggregate,
- lesende Repository-Methoden, soweit sie nur für Rehydration benötigt werden,
- produktive transaktionale Integration in den Reconciliation Application Flow,
- allgemeine Update- oder Delete-Semantik,
- Upsert oder Merge,
- Unit of Work,
- Identity Map,
- Lazy Loading,
- ORM,
- Active Record,
- Event Sourcing,
- CQRS,
- Bulk-Import-Optimierung,
- Reporting Repositorys,
- Evaluation Queries,
- Schemaänderungen ohne vorherige Fortschreibung von DM002 und neue Migration,
- neue fachliche Domain-Invarianten.

Diese Themen bleiben insbesondere WP2-006, WP2-007 oder späteren gesonderten Workpackages vorbehalten.

---

# 17. Verworfene Alternativen

## 17.1 Repository pro Tabelle

Verworfen, weil dadurch die Application Layer die relationale Struktur koordinieren müsste und die Aggregate Boundary verloren ginge.

## 17.2 Ein monolithischer Aggregate Mapper

Verworfen, weil ein einzelner Mapper für alle Tabellen zu viele Verantwortlichkeiten bündeln und Änderungen am Schema unnötig weit verteilen würde.

## 17.3 ORM-Einführung

Verworfen, weil Reconcilix derzeit ein überschaubares, explizites Persistenzmodell besitzt und ein ORM zusätzliche Lifecycle-, Proxy-, Mapping- und Konfigurationskomplexität einführen würde.

## 17.4 Active Record

Verworfen, weil Domain Objects dadurch Datenbankverantwortung übernehmen und die Trennung zwischen Domain und Infrastructure verletzt würde.

## 17.5 Technische Datenbank-IDs in Domain Objects

Verworfen, weil relationale Implementierungsdetails dadurch in das fachliche Modell eindringen würden.

## 17.6 Eigene Transaktion pro Mapper

Verworfen, weil dadurch ein fachlich zusammengehöriger Schreibvorgang nur teilweise persistiert werden könnte.

---

# 18. Konsequenzen

## Positive Konsequenzen

- Die Application Layer bleibt frei von relationalen Details.
- Das Domain Model bleibt frei von technischen Datenbank-IDs.
- Das Repository bildet eine fachliche Aggregate Boundary.
- Mapper bleiben klein, fokussiert und separat testbar.
- Änderungen an einzelnen Tabellen bleiben lokal begrenzt.
- Die spätere Rehydration kann auf denselben Mapping-Grenzen aufbauen.
- Die transaktionale Integration in WP2-007 bleibt möglich.
- PDO bleibt vollständig in der Infrastructure Layer.

## Bewusst akzeptierte Konsequenzen

- Das Repository muss eine explizite Insert-Reihenfolge koordinieren.
- Mehrere spezialisierte Mapper erzeugen mehr Klassen als ein einzelner Mapper.
- Technische IDs müssen während eines Schreibvorgangs intern weitergereicht werden.
- Integrationstests benötigen eine separate reale MySQL- oder MariaDB-Testdatenbank.
- Das erste Persistenz-Feature unterstützt bewusst noch keine generische Update-Semantik.

---

# 19. Geplante Struktur

Vorgesehene Struktur, vorbehaltlich Abgleich mit dem aktuellen Projektstand:

```text
src/
├── Application/
│   └── Persistence/
│       └── ReconciliationRepository.php
└── Infrastructure/
    └── Persistence/
        ├── RelationalReconciliationRepository.php
        ├── Exception/
        │   └── PersistenceException.php
        └── Mapper/
            ├── SourceValueMapper.php
            ├── InterpretationGraphMapper.php
            ├── InterpretationNodeMapper.php
            ├── ReconciliationResultMapper.php
            ├── CandidateItemMapper.php
            └── MatchDecisionMapper.php
```

Falls der Repository Contract bereits an anderer Stelle besteht, wird er nicht dupliziert, sondern wiederverwendet oder minimal präzisiert.

Die tatsächliche Dateiliste wird im Workpackage-README dokumentiert.

---

# 20. Akzeptanzkriterien

WP2-005 ist abgeschlossen, wenn:

- ein fachlicher Repository Contract durch eine relationale Infrastructure-Klasse implementiert wird,
- die Application Layer keine Tabellen, SQL-Anweisungen oder PDO kennt,
- die relationale Repository-Implementierung mehrere klar abgegrenzte Mapper koordiniert,
- technische Primärschlüssel die Infrastructure Layer nicht verlassen,
- ein vollständiges unterstütztes Reconciliation-Aggregat in DM002 persistiert werden kann,
- `MatchDecision.selectedCandidateUri` eindeutig auf `selected_candidate_item_id` abgebildet wird,
- Mapper keine eigenen Commits durchführen,
- die Implementierung innerhalb des vorhandenen `TransactionManager` atomar ausführbar ist,
- Integrationstests gegen eine separate Testdatenbank erfolgreich sind,
- bestehende Tests und der OpenRefine-End-to-End-Test unverändert erfolgreich bleiben,
- keine Rehydration oder produktive Transaction Boundary vorweggenommen wird.

---

# 21. Review-Fragen an LA

1. Ist die Entscheidung für ein fachliches Repository auf Aggregate-Ebene gegenüber Repositorys pro Tabelle eindeutig und angemessen?
2. Ist `RelationalReconciliationRepository` als koordinierende Infrastructure-Komponente korrekt abgegrenzt?
3. Ist die Aufteilung in mehrere spezialisierte Data Mapper ausreichend präzise, ohne eine starre Eins-zu-eins-Regel „ein Mapper pro Tabelle“ zu erzwingen?
4. Ist es korrekt, technische Primärschlüssel vollständig innerhalb der Infrastructure Layer zu halten?
5. Ist die Zuordnung von `selectedCandidateUri` zu `selected_candidate_item_id` innerhalb des Repository-Schreibvorgangs richtig verortet?
6. Ist die Abgrenzung zwischen WP2-005, WP2-006 Rehydration und WP2-007 Transactional Integration eindeutig?
7. Soll WP2-005 ausschließlich Insert-Semantik unterstützen oder ist bereits jetzt eine eng definierte Idempotenzregel erforderlich?
8. Ist die vorgeschlagene Teststrategie mit realer Testdatenbank und transaktionalem Rollback ausreichend?
9. Kann WP2-005 auf dieser Grundlage implementiert werden?
