Back-Link: https://www.hasenzaehn.ch/StaticHtml/wiki.html
Um aktuelle und unverfälschte Materialdaten für den Materialdesigner zu haben, können sie direkt vom https://next-gen.materialsproject.org/api gezogen werden.
Materials Project stellt die Informationen über eine strukturierte API bereit.
Die Plattform bietet unter anderem Zugriff auf:
Kristallstrukturen (fractional / Cartesian coordinates)
Gitterparameter und Symmetrien
Volumen, Dichte und chemische Zusammensetzung
Elektronische, thermodynamische und weitere physikalische Eigenschaften
Diese Daten bilden die Grundlage für Visualisierung, Transformation und Stapelung von Unit Cells im Browser.
Der Zugriff auf die API des Materials Project erfolgt authentifiziert über einen API-Key.
Da dieser Key nicht im Browser offengelegt werden darf, wird die Kommunikation nicht direkt vom Frontend durchgeführt.
Stattdessen wird ein serverseitiger Proxy-Ansatz verwendet.
Das Frontend (Qt / WebAssembly) sendet die GET Anfrage
Diese Anfrage erreicht einen dedizierten Backend-Service, der:
Der Service ruft nur die Materials-Project-API auf, verarbeitet die Antwort und gibt das JSON-Ergebnis an das Frontend zurück.

Caddy übernimmt:
TLS-Terminierung (HTTPS)
Weiterleitung der Anfragen an den internen API-Service
Setzen relevanter Proxy-Header (z. B. Client-IP)
Zugriffsbeschränkung auf definierte Frontend-Origins
Der API-Service läuft containerisiert mit Docker:
isolierte Laufzeitumgebung
einfache Updates und Rollbacks
konsistente Deployments
saubere Trennung von Infrastruktur und Anwendung
Zusätzlich implementiert der Service:
Rate-Limiting
grundlegende Schutzmechanismen gegen Missbrauch
kontrollierte Weitergabe der API-Antworte
Das Ziel ist: Eine saubere Trennung zwischen Material-Logik und Netzwerk-Implementierung. Trennung von
MaterialDataDie Client-Server-Bibliothek ist so entworfen, dass Materialdaten aus unterschiedlichen Quellen (Netzwerk, Datei oder Datenbank) einheitlich, asynchron und plattformunabhängig geladen werden können.
Dies wird durch eine Kombination aus Strategy Pattern, Factory Pattern und Dependency Injection erreicht.
Zentrales Domänenmodell:
namespace MaterialDataFetcher {
using MaterialId_t = std::string;
using Density_t = double;
using LatticeLength_t = double;
using LatticeAngle_t = double;
struct WyckoffSite {
std::string species;
std::array<double,3> frac;
std::string wyckoff;
};
struct MaterialData {
std::optional<MaterialId_t> id;
std::optional<std::string> name;
std::optional<std::string> spaceGroup;
std::optional<LatticeLength_t> a, b, c;
std::optional<LatticeAngle_t> alpha, beta, gamma;
std::optional<Density_t> density;
std::vector<WyckoffSite> wyckoffSites;
};
//...
}
Alle Datenquellen implementieren dasselbe Interface:
class IMaterialDataLoader {
public:
using AsyncCallback =
std::function<void(std::expected<MaterialData, FetchError>)>;
virtual void getMaterialDataAsync(
const MaterialId_t& material_id,
AsyncCallback cb) const = 0;
virtual ~IMaterialDataLoader() = default;
};
Man braucht Asynchronität, das wird von WebAssembly erzwungen. Blockierende Aufrufe im Main-Thread sind dort nicht erlaubt! Was für WASM funktioniert, funktioniert auch auf Desktop (aber umgekehrt eben nicht...). Es sind keine Threads, keine Futures, keine Promises notwendig. Callbacks sind der kleinste gemeinsame Nenner für alle Zielplattformen.
Jede Datenquelle ist eine eigene Strategy, die IMaterialDataLoader implementiert.
live aus dem Netzwerk (Materials Project via Backend-Proxy),
lokal aus einer Datei (Cache / Export),
oder aus einer Datenbank (lokaler Index, Offline-Modus,Repository)
Die Erzeugung der konkreten Loader-Implementierung erfolgt über eine Factory:
enum class LoadSource {
NetworkQt,
NetworkCpr,
File
};
class MaterialLoaderFactory {
public:
std::unique_ptr<IMaterialDataLoader>
create(LoadSource src);
};
und dann
std::unique_ptr<IMaterialDataLoader>
MaterialLoaderFactory::create(LoadSource src) {
switch (src) {
case LoadSource::NetworkQt:
return std::make_unique<MaterialProjectDataLoaderStrategy>(
std::make_unique<QtHttpClientAsync>());
case LoadSource::NetworkCpr:
//...
case LoadSource::File:
//...
return nullptr;
}
return nullptr; // defensive fallback
Um das Material-Modul vollständig von Qt und anderen HTTP-Bibliotheken zu entkoppeln, wird eine HTTP-Client-Abstraktion eingeführt.
Diese Schnittstelle definiert genau das, was benötigt wird, nämlich HTTP GET, Json Antwort, asynchrones Callback, damit gibt es keine Abhängigkeit von Qt, CPR oder libcurl.
class IHttpClient {
public:
using Result = std::expected<nlohmann::json, HttpError>;
using Callback = std::function<void(Result)>;
virtual void getAsync(
const std::string& path,
const std::string& materialID,
Callback cb) const = 0;
virtual ~IHttpClient() = default;
};
Die Bibliothek verwendet nlohmann/json (https://github.com/nlohmann/json) für die Verarbeitung aller JSON-Daten, die aus externen Quellen (Netzwerk, Datei, Datenbank) stammen. nlohmann/json ist nicht Teil der öffentlichen API der Client-Server-Bibliothek, sondern wird nur in den konkreten Loader-Strategie und in den Parser-Funktionen eingesetzt. Damit bleibt das Domänenmodell MaterialData JSON frei.
Auf Basis von IHttpClient können mehrere Implementierungen existieren:
QNetworkAccessManager, genutzt für Desktop und Qt/WASM:class QtHttpClientAsync :
public QObject,
public IHttpClient
{
Q_OBJECT
public:
explicit QtHttpClientAsync(QObject* parent = nullptr);
void getAsync(const std::string& path,
const std::string& materialID,
Callback cb) const override;
private:
mutable QNetworkAccessManager m_qnam;
};
Diese Strategy orchestriert mehrere Requests und parst die Ergebnisse in MaterialData.
class MaterialProjectDataLoaderStrategy : public IMaterialDataLoader {
public:
explicit MaterialProjectDataLoaderStrategy(
std::unique_ptr<IHttpClient> client);
void getMaterialDataAsync(
const MaterialId_t& id,
AsyncCallback cb) const override;
private:
std::unique_ptr<const IHttpClient> m_client;
};
materials/summary/<id>materials/chemenv/<id>MaterialDataauto post = [cb = std::move(cb)]
(std::expected<MaterialData, FetchError> r) mutable
{
QMetaObject::invokeMethod(
QGuiApplication::instance(),
[cb = std::move(cb), r = std::move(r)]() mutable {
cb(std::move(r));
},
Qt::QueuedConnection);
};
Parsing-Funktionen sind statisch, wiederverwendbar und unabhängig vom Transport.
template <typename T>
std::optional<T>
opt_from_json(const nlohmann::json& obj, const std::string& key)
{
if (obj.contains(key) && !obj[key].is_null())
return obj[key].get<T>();
return std::nullopt;
}
Eine File-Strategy folgt exakt demselben Interface:
class FileMaterialDataLoaderStrategy : public IMaterialDataLoader {
public:
explicit FileMaterialDataLoaderStrategy(
std::unique_ptr<IFileReader> reader);
void getMaterialDataAsync(
const MaterialId_t& id,
AsyncCallback cb) const override;
};
class DbMaterialDataLoaderStrategy : public IMaterialDataLoader {
// SQLite, Indexed Cache, Remote DB, etc.
};
Damit ist File-Loading austauschbar mit Network-Loading. Eine Datenbank-Strategie fügt sich ohne API-Änderung ein...

Sequence Diagram:
