Entwurf einer
dienstorientierten
Plug-in-Architektur für
Webanwendungen
am Beispiel von
Bachelorarbeit
Jonas Thelemann
20.01.2022
Gliederung
- Projekt
- Entwurf
- Implementierung
- Vertiefung
Code
Dritter
Design
+
Kontext
2014
2016
2018
2020
2022
Gründung NAHhaft
Idee
Antragsphase
Gründung, Beta
Logistik-Marktplatz
nearbuy
Dashboard
Betriebsprofil
Marktplatz
nearbuy
Frontend
React
Anforderungen
- 🔒 sichere Datenzugriffe
- 🕵 Nachvollziehbarkeit von Interaktionen
- ⛓ Sicherstellung der Kompatibilität
- 🎨 visuelle Konsistenz
- 🏪 Erstellung eines Plug-in-Markplatzes
- 🔂 Installation pro Konto oder Betrieb
- 🪝 mehrere Erweiterungspunkte
Entwurf
Entwurf
Entwurf
Entwurf
Netzwerkstruktur
Netzwerkstruktur
Flexible Verwendung konsistenten Designs
Webkomponenten
Flexible Verwendung konsistenten Designs
Webkomponenten
Stencil
Flexible Verwendung konsistenten Designs
Webkomponenten
Stencil
Bit
Flexible Verwendung konsistenten Designs
Webkomponenten
Stencil
Bit
Datenmodell eines Plug-ins
const nearbuyPluginMuesliIndex: NearbuyPluginOptions = {
id: "muesli-index",
name: {
de: "Muesli-Index", en: "Muesli Index"
},
description: {
en: "Calculates the 'Muesli index', representing the availability of ingredients
required for a yummy muesli. Requests for missing ingredients can be added
with one click."
},
version: packageJson.version,
versionNearbuyCompatibility: packageJson.peerDependencies.nearbuy,
url: new URL("http://localhost:3001"),
iconPngBase64: "iVBORw0KGgoAAAA...",
entryPoints: {
dashboard: [{
dashlet: [{
contentUrl: "https://nearbuy_muesli-index.jonas-thelemann.de/",
}],
}],
plugin: {
contentUrl: "http://localhost:3001/",
},
},
}
Implementierung
- 🔑 Autorisierung von API-Zugriffen durch Dritte
- 💻 Webkomponenten
- 📦 Plug-in-Installation
1/2 Konfiguration von Keycloak
2/2 Erweiterung der Anfragevalidierung im Backend
val userinfo = parseUserinfo(String(Base64.getDecoder().decode(xUserinfoEncoded)))
this.scopes.addAll(userinfo.scope.split(" "))
val annotationScope =
"${annotation.clazz.name.lowercase()}-${annotation.action.name.lowercase()}"
if (!(requestContext.securityContext as NearbuySecurityContext).hasScope(annotationScope)) {
abort(requestContext)
}
@Allowed(READ, COMPANY, "{companyId}")
Autorisierung von API-Zugriffen durch Dritte
<Button
onClick={(): void => this.props.
onClose()}
/>
<Button
onClick={(): void => this.saveOffer()}
color="primary"
variant="contained"
disabled={...}
/>
<NbButton
onNbButtonEvent={(): void =>
this.props.onClose()
}
secondary
/>
<NbButton
onNbButtonEvent={(): void =>
this.saveOffer()
}
disabled={...}
/>
React
Einbindung in Webanwendungen
Buttons
Webkomponenten
import { applyPolyfills, defineCustomElements }
from "@dargmuesli/nearbuy-web-components/loader"
Vue.config.ignoredElements = [[/nb-\w+/]
applyPolyfills().then(() => {
defineCustomElements()
})
{
hydratePath: ’@dargmuesli/nearbuy-web-components/hydrate’,
lib: ’@dargmuesli/nearbuy-web-components’,
loaderPath: ’@dargmuesli/nearbuy-web-components/loader’,
prefix: ’nb-’,
},
Vue
Nuxt
Einbindung in Webanwendungen
Webkomponenten
Webkomponenten
@Prop() secondary: boolean = false;
@Prop() disabled: boolean = false;
@Prop({ attribute: ’first-last’ }) name: Name = {
first: ’Max’,
last: ’Mustermann’
};
@Event() nbButtonEvent: EventEmitter<string>;
render() {
return <button
class={[
"font-medium leading-7 px-4 py-1 rounded-full text-sm uppercase",
...(this.secondary ? ["nb-secondary"] : []),
...(this.disabled ? ["nb-disabled"] : [])
].join(’ ’)}
onClick={() => this.nbButtonEvent.emit(this.getText())}
part="nb-button"
type="button"
>
<slot>{this.getText()}</slot>
</button>;
}
Erzeugung mit Stencil
Plug-in-Marktplatz
Plug-in-Installation
Visualisierung im Frontend
Plug-in-Oberfläche
Plug-in-Installation
Visualisierung im Frontend
Vertiefungen
1/3 Globales Theming trotz Shadow DOM
button {
background-color: var(--primary-color, #92BE9B);
font-family: ’Quicksand’, sans-serif;
@apply font-bold shadow-md text-white;
}
body.theme-muesli-index nb-button[secondary]::part(nb-button):hover {
background-color: rgba(72 111 80 / 4%);
}
CSS Custom Property
CSS Shadow Parts
2/3 API-Versionierung
Semantic
2/3 API-Versionierung
Semantic
Periodic
2/3 API-Versionierung
Semantic
Periodic
Deprecation
https://api.nearbuy-food.de/v1/public/companies
3/3 Weiterentwicklung des Kubernetes-Clusters
Istio
3/3 Weiterentwicklung des Kubernetes-Clusters
Istio
Flagger
3/3 Weiterentwicklung des Kubernetes-Clusters
Istio
Flagger
OPA
Zusammenfassung, Fazit und Ausblick
- Entwurf
- Plug-in-System
- Plug-in + Dienst
- Plug-in-Marktplatz
- Gegenvorschlag
- Keycloak
- Backend-Auth
- Design über Webkomponenten
- React, Vue, Nuxt
- React, Vue, Nuxt
- Empfehlungen für Kubernetes
- Codeanalyse, -stil & CI
✅
✅
✅
✅
✅
nearbuy
Quarkus
Frontend
React
Backend
nearbuy
Quarkus
Frontend
React
Backend
Kubernetes
Orchestrierung
Plug-in-Installation
Persistenz im Backend
CREATE TABLE plugin_installation (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
plugin_id TEXT NOT NULL,
owner_person UUID REFERENCES person ON DELETE CASCADE,
owner_company UUID REFERENCES company ON DELETE CASCADE,
date_installed TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
UNIQUE (plugin_id, owner_person),
UNIQUE (plugin_id, owner_company),
CONSTRAINT either_person_or_company CHECK (num_nonnulls(owner_person, owner_company) = 1)
)
PostgreSQL
Entwurf einer dienstorientierten Plug-in-Architektur für Webanwendungen
By Jonas Thelemann
Entwurf einer dienstorientierten Plug-in-Architektur für Webanwendungen
- 89