Vom Datenimport zum selbstvalidierenden ETL-System – Architektur eines modularen Java-Importers

Ein Datenimport beginnt oft unspektakulär: Dateien einlesen, Tabellen anlegen, Daten schreiben. Solange Quelle und Ziel stabil bleiben, funktioniert das erstaunlich lange.

Interessanter wird es, wenn sich Anforderungen verändern. Neue Exportversionen bringen zusätzliche Spalten mit. Datentypen ändern sich. Eine weitere Datenbank soll unterstützt werden. Metadaten müssen anders interpretiert werden. Ein Import darf nicht nur technisch durchlaufen, sondern muss anschließend überprüfen können, ob die erwarteten Daten tatsächlich angekommen sind.

Aus einem Importskript wird damit ein ETL-System.

Das Ziel meines Java-Projekts ist deshalb nicht nur, Daten zu importieren. Der Importer soll metadatengetrieben, selbstvalidierend, transaktional abgesichert, streamingfähig und erweiterbar sein – und Änderungen möglichst dort isolieren, wo sie entstehen.

Die Pipeline

Der Import ist als kontrollierter Prozess aufgebaut:

Quelldaten
    ↓
Struktur erkennen
    ↓
Validierung der Rohdaten
    ↓
Metadaten einlesen
    ↓
Schema anreichern
    ↓
Reference-Schema prüfen
    ↓
SchemaDiff
    ↓
Zielschema erzeugen
    ↓
DatabaseScript ausführen
    │
    ├── Schema / DDL
    │
    ├── Streaming Load
    │       ↓
    │   Reconciliation pro Tabelle
    │
    └── weitere DB-Operationen
    ↓
Deployment-Validierung
    ↓
Fehlerprüfung
    ↓
Commit oder Rollback

Damit wird aus „Datei nach Datenbank kopieren“ ein Prozess mit definierten Vorbedingungen, Transformationen und Nachbedingungen.

Architektur ETL-Importer
Architektur ETL-Importer

Ein modularer Monolith statt eines großen ETL-Core

Der Importer ist als Maven-Multi-Modul-Projekt aufgebaut. Die Module sind nach Stabilität und Verantwortung gruppiert:

etl-importer
│
├── 00-foundation
│   ├── etl-model
│   └── etl-exception
│
├── 10-api
│   ├── etl-connection-api
│   ├── etl-database-api
│   ├── etl-deployment-api
│   ├── etl-generator-api
│   ├── etl-io-api
│   ├── etl-mapper-api
│   └── etl-validator-api
│
├── 20-implementation
│   ├── etl-connection
│   ├── etl-database
│   ├── etl-deployment
│   ├── etl-generator
│   ├── etl-io
│   ├── etl-mapper
│   └── etl-validator
│
├── 25-infrastructure
│   ├── etl-config
│   └── etl-log
│
└── 30-application
    ├── etl-runner
    ├── etl-application
    ├── etl-bootstrap
    └── etl-importer-cli

Die Grundidee dahinter ist einfach:

              volatile Außenwelt
                     │
                     ▼
              30 Application
          Zusammensetzen & Steuern
                     │
                     ▼
           20 Implementation
       konkrete technische Lösungen
                     │
                     ▼
                 10 API
          stabile Modulverträge
                     │
                     ▼
             00 Foundation
         Modell & Grundbegriffe
                     │
                     ▼
                stabiler Kern

etl-model kennt weder SQL Server noch CSV noch secuTrial. Die äußeren Module dürfen sich verändern, ohne den stabilen Kern ständig mitzuziehen.

Das Projekt bleibt trotzdem ein modularer Monolith: ein Repository, eine Anwendung und ein gemeinsamer Prozess – aber mit echten Maven-Grenzen innerhalb der Anwendung.

Austauschbare MetadataReader

Eine wichtige Eigenschaft der aktuellen Architektur ist die Entkopplung des Metadata-Readers.

Die konkrete Anwendung entscheidet beispielsweise:

DataTypeMapper metadataTypeMapper =
        new SecuTrialMetadataTypeMapper();

Reader<List<TableDefinition>> metadataReader =
        new SecuTrialMetadataReader(
                applicationContext,
                metadataTypeMapper
        );

ReaderFacade readerFacade =
        new StandardReaderFacade(
                applicationContext,
                csvReader,
                metadataReader
        );

Die darunterliegenden Komponenten arbeiten gegen den Reader-Vertrag und müssen secuTrial selbst nicht kennen.

                 Application
                      │
              wählt Quellsystem
                      │
                      ▼
      Reader<List<TableDefinition>>
                      ▲
                      │
          SecuTrialMetadataReader

Dadurch kann später eine andere MetadataReader-Implementierung eingebunden werden, ohne die Generator- oder Deploymentlogik neu zu entwerfen.

Eine mögliche zukünftige Erweiterung wäre beispielsweise ein REDCap-Reader. REDCap ist derzeit jedoch noch keine implementierte Datenquelle.

Austauschbare TypeMapper

Reader und Typinterpretation sind bewusst getrennt.

Für technische Spalten verwendet der Generator den Vertrag:

DataTypeMapper

Die konkrete Application entscheidet derzeit beispielsweise über:

DataTypeMapper technicalTypeMapper =
        new SecutrialTechnicalTypeMapper();

Auch der MetadataReader erhält seinen TypeMapper von außen:

DataTypeMapper metadataTypeMapper =
        new SecuTrialMetadataTypeMapper();

Damit ergibt sich:

                 DataTypeMapper
                  ▲          ▲
                  │          │
       Metadata Mapper   Technical Mapper
                  │          │
                  └────┬─────┘
                       │
                       ▼
             Reader / Generator

Die konkrete Typinterpretation ist damit austauschbar, ohne sie tief in die Generatorlogik einzubauen.

Die eigentlichen Tabellendaten sind derzeit CSV-basiert

Während der MetadataReader bereits über ein Interface austauschbar ist, werden die eigentlichen Tabellendaten aktuell über CsvReader verarbeitet.

Für die derzeitigen secuTrial-Exports ist das bewusst ausreichend.

Eine mögliche zukünftige Generalisierung wäre ein eigener neutraler Datenreader:

public interface TableDataReader {

    List<TableDefinition> readTables();

    Iterable<DataRow> readRows(
            TableDefinition table
    );
}

Damit könnten später beispielsweise weitere Datenquellen implementiert werden:

TableDataReader
      ▲
      │
      ├── CsvTableDataReader
      ├── JsonTableDataReader
      ├── DatabaseTableDataReader
      └── RestTableDataReader

Diese Abstraktion ist aktuell noch nicht Teil der Implementierung. Sie ist ein möglicher zukünftiger Erweiterungspunkt.

Streaming: große Datenmengen ohne großen Speicherbedarf

Eine der wichtigsten technischen Eigenschaften des Importers ist die Streaming-Verarbeitung der eigentlichen Tabellendaten. Große Exporttabellen müssen nicht vollständig in den Arbeitsspeicher geladen werden, bevor der Import beginnen kann.

Exportdatei
    ↓
Lazy / Streaming Reader
    ↓
Zeile für Zeile
    ↓
DEBUG / BULK LoadStrategy
    ↓
JDBC Batch / Bulk Loader
    ↓
Datenbank

Dadurch bleibt das Verarbeitungsmodell auch bei großen Tabellen grundsätzlich dasselbe. Der Importer muss nicht erst Millionen Datensätze als vollständige Java-Objektstruktur im Speicher materialisieren.

Besonders wichtig ist dabei die bewusste Trennung zwischen diskreten SQL-Operationen und Massendaten.

diskrete DB-Operationen             Massendaten

SqlCommand                           Streaming
    ↓                                    ↓
SqlExecutor                        LoadStrategy
    ↓                                    ↓
JDBC                             Batch / BULK

DDL, ALTER-Anweisungen und andere einzelne SQL-Kommandos können damit kontrolliert über den zentralen SqlExecutor laufen, während große Datenströme nicht künstlich in tausende oder Millionen SqlCommand-Objekte umgewandelt werden.

Das ist eine bewusste Architekturentscheidung: Streaming bleibt Streaming.

Streaming trotz transaktionaler Kontrolle

Streaming bedeutet dabei nicht, auf die Absicherung des Importlaufs zu verzichten. Der Datenbankpfad bleibt in eine JDBC-Transaktion eingebettet.

Streaming Import
       ↓
DEBUG / BULK
       ↓
Datenbank
       ↓
Reconciliation
       ↓
Deployment Validation
       ↓
   Ergebnis?
   ↙      ↘
Fehler     OK
  ↓        ↓
ROLLBACK  COMMIT

Damit verbindet der Importer zwei Anforderungen, die bei ETL-Systemen gleichermaßen wichtig sind: skalierbare Verarbeitung großer Datenmengen und eine kontrollierte Entscheidung über Commit oder Rollback.

Die Reconciliation vergleicht dabei unter anderem erwartete und tatsächlich importierte Zeilen- und Spaltenzahlen. Ein technisch durchgelaufener Streaming-Import muss deshalb nicht automatisch als erfolgreicher Import gelten.

Mehrere Datenbanken durch explizite SQL-Dialekte

Die zweite wichtige Variationsachse befindet sich auf der Zielseite. SQL Server, DuckDB und PostgreSQL sprechen zwar SQL, unterscheiden sich aber in zahlreichen Details.

Anstatt diese Unterschiede mit datenbankspezifischen if-Abfragen über den gesamten Importer zu verteilen, werden sie über einen DatabaseContext und spezialisierte Dialekte gekapselt.

DatabaseContext
      │
      ├── SqlDialect
      ├── DdlDialect
      ├── DmlDialect
      ├── DqlDialect
      ├── ConversionDialect
      └── BulkLoaderDialect

Die Verantwortlichkeiten bleiben dadurch getrennt.

DdlDialect
    ↓
Schema und DDL

DmlDialect
    ↓
Datenmanipulation

DqlDialect
    ↓
Abfragen

ConversionDialect
    ↓
datenbankspezifische Konvertierungen

BulkLoaderDialect
    ↓
datenbankspezifischer Massendatenimport

Aktuell existieren konkrete Datenbankimplementierungen für SQL Server, DuckDB und PostgreSQL. PostgreSQL besitzt im aktuellen Entwicklungsstand noch keine eigene BULK-Implementierung.

Der Generator muss die konkrete Datenbank nicht kennen

Für die Generatorlogik ist entscheidend, welche Fähigkeit sie benötigt, nicht welche konkrete Datenbank dahintersteht.

                    DdlDialect
                  ▲      ▲      ▲
                  │      │      │
            SQL Server DuckDB PostgreSQL
                  │      │      │
                  └──────┼──────┘
                         │
                         ▼
                    Generator

Dadurch muss die Generatorlogik nicht mit Konstruktionen wie dieser wachsen:

if (databaseType == SQL_SERVER) {
    ...
}
else if (databaseType == DUCKDB) {
    ...
}
else if (databaseType == POSTGRESQL) {
    ...
}

Datenbankspezifisches Verhalten bleibt stattdessen in der jeweiligen Datenbankimplementierung.

Source und Target sind weitgehend entkoppelt

Besonders interessant wird die Architektur durch die Kombination aus austauschbaren MetadataReadern und TypeMappern auf der Quellseite und austauschbaren Datenbankdialekten auf der Zielseite.

                 QUELLSYSTEM
                      │
              MetadataReader
                      │
                 TypeMapper
                      │
                      ▼
              neutrales ETL-Modell
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
      SQL Server    DuckDB    PostgreSQL
          │           │           │
          ▼           ▼           ▼
        Dialekte    Dialekte    Dialekte

Der MetadataReader muss nicht wissen, welche Datenbank später verwendet wird.

Ein PostgreSQL-Dialekt muss wiederum nicht wissen, aus welchem Quellsystem die Metadaten stammen.

Das neutrale Modell aus TableDefinition, ColumnDefinition und TypeDefinition bildet die Grenze zwischen diesen beiden Welten.

Streaming und Dialekte greifen ineinander

Besonders deutlich wird der Nutzen der Architektur beim eigentlichen Datenimport. Nicht nur die SQL-Syntax ist datenbankspezifisch – auch die optimale Methode, große Datenmengen zu laden, kann sich zwischen Datenbanken unterscheiden.

                    ETL-Modell
                        │
                        ▼
                 Streaming Data
                        │
                        ▼
                   LoadStrategy
                        │
              ┌─────────┼─────────┐
              ▼         ▼         ▼
         SQL Server   DuckDB   PostgreSQL
              │         │         │
              ▼         ▼         ▼
        DB-spezifische Fähigkeiten

Deshalb wird nicht versucht, allen Datenbanken künstlich dieselben Fähigkeiten aufzuzwingen. Eine Datenbank kann beispielsweise einen eigenen BulkLoaderDialect besitzen, während eine andere diese Capability derzeit nicht implementiert.

Austauschbarkeit bedeutet nicht, dass alle Datenbanken identisch sind. Sie bedeutet, dass ihre Unterschiede an einer definierten Stelle gekapselt sind.

Schema Drift ist ein expliziter Bestandteil des Prozesses

Bei langfristig betriebenen Datenexporten verändert sich das Schema.

Neue Spalten erscheinen, bekannte Definitionen können sich verändern und neue Exportversionen können zusätzliche Strukturen enthalten.

Der Importer arbeitet deshalb mit einem erkannten Candidate Schema und einem persistierten Reference Schema.

aktueller Export
      ↓
Candidate Schema
      ↓
Reference Schema
      ↓
SchemaDiff
      ↓
bekannt / unbekannt

Beim ersten Lauf kann das erkannte Schema als Reference Schema gespeichert werden.

Bei späteren Läufen werden Abweichungen zwischen Candidate und Reference Schema erkannt.

Unbekannte Spalten werden nicht einfach stillschweigend übernommen.

Reference-Schema-Governance statt blindem Schemawechsel

Das aktuelle System führt keinen allgemeinen automatischen Schema-Migrationsworkflow über ALTER TABLE durch.

Stattdessen wird geprüft, ob das erkannte Schema mit dem bekannten Reference Schema vereinbar ist.

aktueller Export
       ↓
Candidate Schema
       ↓
SchemaDiff
       ↓
unbekannte Spalten?
   ┌──────┴──────┐
   │             │
  JA            NEIN
   │             │
Abbruch      Schema erzeugen

Unbekannte Spalten werden dokumentiert und führen zu einem kontrollierten Fehler, statt unbemerkt in das Datenbankschema einzufließen.

Damit ist Schema Drift kontrollierbar, ohne so zu tun, als wäre bereits ein vollständiger automatischer Approval- oder Migration-Workflow implementiert.

Metadatengetriebene Typisierung

Die physische Datei allein enthält häufig nicht genügend Information, um die fachliche Bedeutung einer Spalte zu bestimmen.

Deshalb kombiniert der Importer technische Struktur und Source-Metadaten.

CSV-Struktur
      +
Source-Metadaten
      +
TypeMapper
      ↓
angereicherte TableDefinitions

Eine ColumnDefinition enthält dadurch neben dem Namen auch Typinformationen, Metadata-Kennzeichnung und Informationen für mögliche Konvertierungen.

Für bestimmte Metadaten können zusätzliche technische Spalten erzeugt werden, beispielsweise:

original_column
original_column_conv
original_column_prec

Diese Logik liegt nicht im CSV-Reader, sondern in der Schema- und Generatorlogik.

Mehrstufige Validierung

Validation ist als eigener Bestandteil der ETL-Pipeline modelliert.

Dafür existieren getrennte Facades:

ConfigValidationFacade
RawTablesValidationFacade
EnrichedTablesValidationFacade
TransformationValidationFacade
DatabaseScriptValidationFacade
DeploymentValidationFacade

Damit können unterschiedliche Zustände des Imports separat geprüft werden.

Die konkrete Tiefe der einzelnen Validatoren ist derzeit unterschiedlich. Einige Validatoren sind bereits funktional, andere werden noch weiter ausgebaut.

Die Architektur für eine mehrstufige Selbstvalidierung ist damit vorhanden, ohne zu behaupten, dass bereits jede denkbare Prüfung vollständig implementiert ist.

Reconciliation: Ein erfolgreicher INSERT reicht nicht

Ein SQL-Befehl ohne Exception bedeutet noch lange nicht, dass der Import fachlich korrekt ist.

Deshalb wird während des Deployments eine Reconciliation der geladenen Tabellen durchgeführt.

Aktuell werden unter anderem erwartete und tatsächliche Zeilen- und Spaltenzahlen verglichen.

expected rows:      125481
database rows:      125481

expected columns:        84
database columns:        84

                 OK

Oder:

expected rows:      125481
database rows:      125479

               MISMATCH

Die Ergebnisse werden als ReconciliationResult modelliert.

Reconciliation ist dabei nicht nur Reporting. Fehler werden in den Lauf zurückgegeben und können verhindern, dass die Transaktion committed wird.

Import
   ↓
Reconciliation
   ↓
Fehler?
 ┌──────┴──────┐
 JA            NEIN
 ↓              ↓
ERROR          OK
 ↓              ↓
ROLLBACK       COMMIT

Transaktionale Absicherung des Datenbank-Deployments

Der DatabaseRunner kontrolliert die JDBC-Transaktion zentral.

Connection öffnen
      ↓
setAutoCommit(false)
      ↓
DatabaseScript erzeugen
      ↓
Script validieren
      ↓
Deployment ausführen
      │
      └── Reconciliation während des Loads
      ↓
Deployment validieren
      ↓
Fehler vorhanden?
   ┌──────┴──────┐
   │             │
  JA            NEIN
   │             │
ROLLBACK       COMMIT

Laufzeitfehler innerhalb dieses Datenbankpfades führen zu einem Rollback.

Ein SQLException-Fehler wird nach dem Rollback in eine EtlException überführt.

Diese Transaktion umfasst die Datenbankoperationen. Dateisystem-Nebenwirkungen wie erzeugte Dateien oder gespeicherte Reference-Schema-Dateien werden durch einen JDBC-Rollback naturgemäß nicht zurückgenommen.

DEBUG- und BULK-Strategie

Für den Datenimport existieren unterschiedliche Ausführungswege.

DEBUG
  ↓
INSERT-basierter Import
  ↓
JDBC Batch


BULK
  ↓
DB-spezifischer Bulk-Import

DEBUG ist dabei kein Dry-Run. Auch im DEBUG-Modus können Datenbankänderungen committed werden.

BULK verwendet einen eigenen performanteren Ladeweg.

SqlQuery<T> macht Datenbankabfragen explizit

Auch Datenbankabfragen werden als eigene Query-Objekte modelliert.

SqlQuery<Long>

kann beispielsweise eine Abfrage wie diese repräsentieren:

SELECT COUNT(*)
FROM dbo.patient

Das Ergebnis wird über einen ResultSetMapper<T> in einen Java-Wert überführt.

SqlQuery<T>
      ↓
SqlExecutor
      ↓
ResultSet
      ↓
ResultSetMapper<T>
      ↓
T

Damit existiert neben dem Command-Pfad auch ein klar definierter Query-Pfad.

Audit-ID für einen Importlauf

Ein Importlauf besitzt eine eigene Audit-ID:

UUID auditId = UUID.randomUUID();

Diese Audit-ID ist Bestandteil des ApplicationContext und wird unter anderem für Logging und Validation verwendet.

                 auditId
                    │
            ApplicationContext
                    │
            ┌───────┴────────┐
            ▼                ▼
        LogReport      ValidationReport

Die Reconciliation-Ergebnisse können über den Lauf und das Logging dem jeweiligen Importlauf zugeordnet werden, besitzen derzeit aber nicht selbst eine eigene Audit-ID-Eigenschaft.

Persistente Importstatistik ist ein nächster Ausbauschritt

Eine persistente Importstatistik in der Zieldatenbank ist als weiterer Ausbauschritt vorgesehen, aber im aktuellen Stand noch nicht vollständig implementiert.

Dort könnten später beispielsweise Informationen gespeichert werden wie:

audit_id
started_at
finished_at
database_type
execution_mode
status
rows_expected
rows_imported
validation_errors
committed

Damit könnte ein Importlauf langfristig nicht nur über Logdateien, sondern zusätzlich strukturiert in der Datenbank nachvollzogen werden.

Fail Fast statt später NullPointerException

Ein wichtiger Teil der Härtung besteht darin, ungültige Zustände möglichst früh zu erkennen.

Beispielsweise wird bei Metadata Columns geprüft, ob ein erwarteter Typ tatsächlich vorhanden ist, bevor die Spalte weiterverarbeitet wird.

MetadataReader
      ↓
getUniqueMetadataColumns()
      ↓
Type vorhanden?
   ┌──────┴──────┐
   │             │
  NEIN           JA
   │             │
EtlException   weiter

Dadurch entsteht ein Fehler mit fachlichem Kontext wie Tabelle und Spalte, statt erst später in einer Collection oder einem Comparator als schwer verständliche NullPointerException aufzutreten.

Testbarkeit durch injizierte Abhängigkeiten

MetadataReader und TypeMapper werden an zentralen Stellen von außen injiziert.

Dadurch können Generator- und Enrichment-Komponenten grundsätzlich auch mit alternativen Testimplementierungen betrieben werden.

Reader<List<TableDefinition>> reader =
        new FakeMetadataReader();

DataTypeMapper mapper =
        new FakeTypeMapper();

So lassen sich Schema- und Mappinglogik isolierter testen.

Ein umfassender automatisierter Testbestand ist derzeit allerdings noch ein wichtiger weiterer Ausbauschritt.

Kompatibilität über synthetische Exportversionen

Für einen langlebigen Importer bietet sich zusätzlich eine Kompatibilitätsmatrix mit synthetischen Exporten unterschiedlicher Quellversionen an.

                    SQL Server   DuckDB   PostgreSQL

Export Version A         ✓          ✓          ✓
Export Version B         ✓          ✓          ✓
Export Version C         ✓          ✓          ✓

Eine solche Matrix ist ein sinnvolles zukünftiges Testziel und kein bereits vollständig implementierter Bestandteil des aktuellen Projekts.

Erweiterbarkeit bedeutet lokale Änderungen

Der eigentliche Architekturtest lautet:

Wie viele bestehende Komponenten muss ich ändern, wenn etwas Neues hinzukommt?

Eine weitere Datenbank sollte hauptsächlich neue Implementierungen in diesen Bereichen benötigen:

DatabaseContext
Dialekte
ggf. BulkLoaderDialect

Ein weiteres Quellsystem betrifft vor allem:

MetadataReader
TypeMapper
ggf. später TableDataReader

Eine weitere Qualitätsprüfung betrifft:

Validator

Eine weitere Deployment-Variante betrifft:

DeploymentStrategy

Wenn dafür der stabile Kern nicht angepasst werden muss, erfüllt die Modulgrenze ihren Zweck.

Das Zusammenspiel der Features

Die eigentliche Stärke liegt weniger in einem einzelnen Feature als in deren Kombination:

              MetadataReader
                    ↓
                TypeMapper
                    ↓
             neutrales Modell
                    ↓
          Schema-Drift-Prüfung
                    ↓
               Validierung
                    ↓
          Datenbank-Dialekte
                    ↓
           Streaming-Import
                    ↓
       DEBUG / BULK LoadStrategy
                    ↓
            Reconciliation
                    ↓
          Deployment-Validation
                    ↓
           COMMIT / ROLLBACK

Der Importer kann damit Änderungen an der Quelle, unterschiedliche Zielsysteme und große Datenmengen behandeln, ohne diese Anforderungen in einer einzigen zentralen Importklasse miteinander zu vermischen.

MetadataReader und TypeMapper kapseln die Interpretation der Quelle. Dialekte kapseln die Datenbank. Streaming skaliert den Datentransport. Reconciliation und Transaktionen sichern das Ergebnis ab.

Architektur verhindert Veränderung nicht – sie begrenzt ihre Auswirkungen

Das ist die zentrale Idee hinter dem Projekt.

Ein ETL-System kann nicht verhindern, dass:

  • sich Exportformate ändern,
  • neue Spalten auftauchen,
  • Datenbanken unterschiedliche Dialekte besitzen,
  • neue Validierungsregeln notwendig werden,
  • weitere Quellsysteme hinzukommen.

Eine gute Architektur versucht deshalb nicht, Veränderung zu verhindern.

Sie sorgt dafür, dass eine Änderung dort bleibt, wo sie fachlich hingehört:

Änderung am Quellsystem
        ↓
Reader / Mapper

Änderung der Datenbank
        ↓
DatabaseContext / Dialect

neue Qualitätsregel
        ↓
Validator

neue Deployment-Technik
        ↓
DeploymentStrategy

Änderung grundlegender Datenstrukturen
        ↓
Foundation

Genau dadurch wird aus einem wachsenden Importskript ein wartbares System.

Langzeitwartbarkeit durch geringe Plattformabhängigkeit

Ein weiteres Ziel des Projekts ist ein möglichst langer und kontrollierbarer technischer Lebenszyklus.

Der Importer ist deshalb bewusst nicht an eine große ETL-Plattform gebunden. Seine technische Basis besteht im Wesentlichen aus Java, Maven, JDBC, Datenbanktreibern und wenigen zusätzlichen Bibliotheken.

ETL-Logik
    ↓
eigene Module
    ↓
Java + JDBC
    ↓
Datenbank

Dadurch bleibt die Zahl der technologischen Abhängigkeiten überschaubar. Änderungen an einer externen ETL-Plattform, proprietären Projektformaten, Plugin-Systemen oder grafischen Jobdefinitionen beeinflussen den Importer nicht.

Bei einem späteren Java-LTS-Wechsel müssen damit vor allem Compiler, verwendete Bibliotheken und JDBC-Treiber geprüft werden. Die eigentliche ETL-Architektur bleibt davon weitgehend unabhängig.

Für einen Importer, der über viele Jahre betrieben werden soll, ist diese geringe Plattformabhängigkeit eine bewusste Architekturentscheidung.

Fazit

Der aktuelle Stand des Projekts kombiniert bereits eine ganze Reihe technisch real implementierter Eigenschaften:

metadatengetriebene Schemaerkennung, austauschbare MetadataReader und TypeMapper, mehrere Datenbankdialekte, Reference-Schema-basierte Schema-Drift-Erkennung, Streaming-Verarbeitung, DEBUG- und BULK-Load-Strategien, zentrale SQL-Ausführung für diskrete Commands und Queries, transaktionale Absicherung des Datenbank-Deployments, mehrstufige Validierung, Reconciliation sowie eine Audit-ID für Logging und Validation.

Daneben sind weitere Ausbaupunkte sinnvoll anschließbar, etwa ein neutraler TableDataReader, persistente Importstatistik, weitere Quellsysteme und eine umfassende Kompatibilitäts-Testmatrix.

Entscheidend ist dabei weniger die Anzahl einzelner Features als ihr Zusammenspiel:

QUELLE
  │
  ▼
MetadataReader
  │
  ▼
TypeMapper
  │
  ▼
neutrales ETL-Modell
  │
  ├──────────────► Validation
  │
  ▼
Streaming
  │
  ▼
LoadStrategy
  │
  ▼
DatabaseContext / Dialekte
  │
  ▼
ZIELDATENBANK
  │
  ▼
Reconciliation
  │
  ▼
Deployment Validation
  │
  ▼
Transaction
  │
  ▼
COMMIT / ROLLBACK

Der stabile Kern beschreibt die Daten und grundlegenden Begriffe. Die äußeren Module entscheiden, woher Daten kommen, wie sie interpretiert, validiert und in welches Zielsystem sie geschrieben werden.

Gute ETL-Architektur verhindert Änderungen nicht. Sie sorgt dafür, dass Änderungen lokal bleiben.

Und genau an diesem Punkt wird aus einem Importer ein System.

Posted in Forschung, SQLTags: