Zum Inhalt springen

Wissensbasis

Architekturprinzipien

Architekturprinzipien machen aus Engineering-Werten Entscheidungen, die ein Team prüfen kann: Systeme an Fachdomänen ausrichten, ohne Blockieren kommunizieren, Services klein halten, kontinuierlich ausliefern, über bewährte Low-Tech-Mittel koppeln, öffentlich entscheiden und mit Angriffen rechnen. Jedes Prinzip hat eine Aussage, eine Begründung, Konsequenzen und Fragen fürs Review.

Verfasst von

Aktualisiert:

Software Engineering 5

Unsere Position. Für uns ist die Codebase der Vertrag, in dem der Agent arbeitet. Wer von KI profitiert, hängt stärker an einer sauberen Codebase als am Modell oder am Token-Spend (die Codebase ist der Vertrag (öffnet in einem neuen Tab)). Sauber heißt hier: Tests, nachahmbare Benennung, laute Architekturgrenzen und Fitness Functions. Codebases verkommen, wenn niemand die Architekturentscheidungen festgeschrieben hat. Bringt dem Agenten deshalb eure Architekturmuster bei.

Die elf Prinzipien unten leiten sich aus den fünf Werten des Engineering Manifesto ab. Beide stammen aus OTTOs öffentlichem Tech Manifest (öffnet in einem neuen Tab) (Apache-2.0). Schriftlich festgehalten, sind sie zugleich Grenzen, die ein Agent lesen und ein Review prüfen kann.

Wendet sie mit gesundem Menschenverstand an. Abweichungen kommen vor. Sie sollten die Ausnahme bleiben, und ihr besprecht sie offen mit den Teams, die sie betreffen.

Wo sollen Systemgrenzen verlaufen, und wie reden Systeme miteinander?

Bounded Contexts und vertikale Systeme

Aussage: IT-Systeme sollen fachlichen Bedarf erfüllen. Deshalb richten wir IT-Produkte an Fachdomänen aus, mit Bounded Contexts und Domänenmodellen in der Sprache des Business.

Begründung: Teams handeln nur dann unabhängig voneinander, wenn sie volle Ownership und klare Verantwortung haben. Das gehört zum Wichtigsten überhaupt. Bounded Contexts finden wir nach dem Ansatz des Domain Driven Design. Im Idealfall teilen sich die gefundenen Contexts nichts, vor allem kein Domänenmodell. So entstehen IT-Produkte, die genau einem Bounded Context zugeordnet sind und genau einem Team gehören. Jedes IT-Produkt enthält alles, was der fachliche Bedarf verlangt: Daten, Geschäftslogik und UI-Fragmente.

Konsequenzen:

  • Jeder gefundene Context hat sein eigenes Domänenmodell und besitzt seine eigenen Daten, seine Geschäftslogik und seine UI
  • Contexts kommunizieren über Domain Events
  • Software-Artefakte sprechen die Sprache der Domäne (Ubiquitous Language). Vermeidet anämische Domänenmodelle
  • Um die Grenzen zu finden, geht ihr die Events der Domain Story der Reihe nach durch. Wo das Aggregat oder die handelnde Rolle wechselt, liegt wahrscheinlich eine Context-Grenze. Ein visuelles Glossar aus dem Domain-Storytelling-Workshop ist die früheste Form der Ubiquitous Language. Es gilt pro Context, nicht für die ganze Firma. Auf der entstehenden Context Map zeichnet ihr synchronen Austausch als durchgezogenen Pfeil, beschriftet mit dem Aggregat. Asynchronen Austausch zeichnet ihr als gestrichelten Pfeil, beschriftet mit dem Event und dem Aggregat, das es trägt. Dieses Bild plus ein OpenAPI- oder AsyncAPI-Gerüst reicht einem Modell, um die erste API zu entwerfen. Beschreibungen, Beispiele und Security ergänzt ihr von Hand (codecentric - Der Softwerker Vol. 24: Von der Domain Story zum Interface (öffnet in einem neuen Tab), deutsch)
  • Für UIs ziehen wir Komposition über Micro-Frontend-Muster einem Frontend- oder UI-Monolithen vor

Diskussionspunkte:

  • Ist die Grenze eures Contexts klar?
  • Könnt ihr eure Grenz-Events benennen?
  • Wie steht ihr zu euren Upstream- und Downstream-Contexts?
  • Gehört euch euer Context ganz?
  • Versteht ihr euer Domänenmodell klar, und teilt ihr es mit dem Business, damit eine Ubiquitous Language entsteht?

Motivierende Werte: Volle Ownership und klare Verantwortung, Tech follows Business

Nicht blockierende Kommunikation

Aussage: Zwischen Systemen ziehen wir nicht blockierende technische Kommunikation vor, damit jedes System technisch autonom von den anderen bleibt.

Begründung: Blockierende Aufrufe an entfernte Systeme halten Threads lange fest, die auf externe Abhängigkeiten warten. Geraten immer mehr Threads in diesen Zustand, legt das ein System komplett lahm. Am Ende reagiert es auf keine neue Anfrage mehr. Hängen mehrere Services in einer Kette, steigt die Latenz. Dazu kommen kaskadierende Fehler, die alle beteiligten Systeme treffen. Auch die Erholung kann leiden: Ein neu gestarteter Service bekommt sofort viel Traffic und fällt wieder um. Technische Kommunikation sollte deshalb einem nicht blockierenden Standardmuster folgen, etwa Pubsub, Feeds oder Webhooks.

Konsequenzen:

  • Wo es geht, kommunizieren wir nicht blockierend über Events und dämpfen so die Folgen entfernter Ausfälle. Services veröffentlichen Event-Streams, die interessierte Services asynchron konsumieren
  • Die Kommunikation mit anderen Systemen blockiert nicht mehr: Fällt eine Funktion vorübergehend aus, arbeitet das System trotzdem weiter
  • Wo nötig, tragen Events zusätzliche Informationen zur Reihenfolge
  • Baut Services, wo es geht, als Idempotent Receiver (öffnet in einem neuen Tab)
  • Braucht ihr einen synchronen Aufruf, setzt Circuit Breaker, Timeouts und Bulkheads ein (siehe Service Degradation)
  • Eine Kette synchroner Integrationsaufrufe über mehrere Schichten summiert Latenz und Unzuverlässigkeit jedes Hops. In einem Batch-Job ist das tragbar, hinter einer Benutzeroberfläche meist ein Fehler (codecentric - Der Softwerker Vol. 20: Integrationsprobleme in der Schokoladenzentrale (öffnet in einem neuen Tab), deutsch)

Diskussionspunkte:

  • Wie modelliert ihr Events richtig? Als Event-Carried State Transfer mit dem ganzen Zustand, als Event Notification, als Command Message oder nur als Delta?
  • Veröffentlicht ihr euer Transaktionslog als Events, oder sind eure Events auf echte Geschäftsereignisse und ihre Konsumenten zugeschnitten? Vermeidet puncturing encapsulation with change data capture (öffnet in einem neuen Tab) zwischen fachlichen Contexts
  • Sind die Events idempotent? Muss die Reihenfolge eine explizite Eigenschaft der Events sein?
  • Meldet das Event eine Zustandsänderung, oder ist es eigentlich ein Command (Kunden-E-Mail geändert vs. Erstattungsbenachrichtigung veröffentlichen)?
  • Bedient das Event generisch mehrere Konsumenten, was Änderungen erschwert? Oder ist es auf einen Konsumenten zugeschnitten, sodass eine Änderung nur ein System trifft?
  • Enthalten die Events DSGVO-relevante Daten oder PII? Dürfen alle Konsumenten darauf zugreifen?

Motivierende Werte: Abdeckung nichtfunktionaler Aspekte, Volle Ownership und klare Verantwortung

Wie groß soll ein Service sein, und wie entwickelt er sich weiter?

Klein und einfach (Microservices)

Aussage: Baut Services klein und einfach. Schneidet sie entlang fachlicher Fähigkeiten, auf Basis eines Domänenmodells mit Zustand und Verhalten.

Begründung: Ein Service soll groß genug sein, um eine gültige fachliche Fähigkeit anzubieten. Zugleich soll er klein genug für ein Two-Pizza-Team bleiben. In der Praxis betreibt ein Team vielleicht viele kleine Services, vielleicht auch weniger größere. Kleine, einfache Systeme versteht man leichter. Mit mehr Systemen können ihre Wechselwirkungen aber komplexer werden. Das Ziel sind deshalb nicht möglichst viele winzige Microservices. Gesucht ist eine Systemlandschaft in der Domäne eines Teams, in der die Subdomänen möglichst unabhängig umgesetzt sind. Vermeidet Microservices, die über Subdomänen hinweg greifen.

Konsequenzen:

  • Zieht Microservices Monolithen vor
  • Nutzt ihre Vorteile und macht sie unabhängig deploybar, damit kein verteilter Monolith entsteht
  • Modelliert Microservices nach eurer Domäne. Eine Domäne kann mehrere Microservices haben
  • Erwägt Anti-Corruption Layers, um Services von umliegenden Domänen zu entkoppeln
  • Microservices sollen sich unabhängig testen lassen
  • Berücksichtigt beim Entwurf von Microservices die 12 factor app (öffnet in einem neuen Tab)

Diskussionspunkte:

  • Lassen sich eure Services unabhängig deployen und testen?
  • Baut ihr einen verteilten Monolithen?
  • Schneidet ihr eure Microservices nach eurer Domäne, oder eher nach technischen Kriterien?
  • Baut ihr Micro- oder Nano-Services?
  • Bedienen eure Microservices mehrere Domänen?
  • Sind eure Microservices wirklich unabhängig, oder integrieren sie über die Datenbankschicht?
  • Seid ihr groß genug für Microservices? (öffnet in einem neuen Tab)

Motivierende Werte: Volle Ownership und klare Verantwortung, Move fast fail fast, Niedrige technische Hürden

Evolutionäre Architektur

Aussage: Entwerft und baut Systeme und Architekturen so, dass sie sich unabhängig von anderen Systemen weiterentwickeln.

Begründung: Heute wissen wir immer weniger als in sechs Monaten. Unsere Software soll mitwachsen, während wir dazulernen. Das gilt auf Code-Ebene und auf Service-Ebene innerhalb der fachlichen Verantwortung.

Konsequenzen:

  • Schiebt zufällige Komplexität so weit wie möglich auf. Konzentriert euch auf den aktuellen Bedarf und akzeptiert, dass wir künftigen Bedarf schlecht vorhersagen
  • Systeme und Prozesse brauchen klar definierte APIs. Dann kann sich die Implementierung darunter ändern, ohne das Gesamtsystem zu treffen. Die internen APIs einer Komponente brauchen nicht zwingend dieselbe Behandlung
  • Erwägt Muster wie TolerantReader (öffnet in einem neuen Tab), Abwärtskompatibilität und das Principle of Robustness (öffnet in einem neuen Tab), wo sie passen oder nützen
  • Standards aus laufender Software ziehen wir Standards aus Arbeitsgruppen vor
  • Manchmal reicht schrittweiser Wandel nicht, weil sich Anforderungen verschieben. Erwägt dann Sacrificial Architecture (öffnet in einem neuen Tab) als Muster. Habt keine Angst, Code wegzuwerfen und neu zu schreiben. Das geht oft schneller und bringt bessere Qualität. Habt ihr die Dinge “klein und einfach” gehalten, wird daraus kein schmerzhafter Big Rewrite
  • Erwägt Standards für Software und Protokolle. Passen sie nicht vernünftig zu eurem Bedarf, prüft weit verbreitete Alternativen. Für externe Clients gehen Industriestandards vor Firmenstandards

Diskussionspunkte:

  • Setzt ihr tatsächliche oder vermutete Anforderungen um?
  • Habt ihr mit Big Design Up Front entworfen?
  • Stützen sich eure Service-Schnittstellen und Protokolle auf bekannte Standards, oder hängen sie eng an einem proprietären Tool oder Protokoll eines Drittanbieters?
  • Stützt sich eure Software auf bekannte Standards, oder hängt sie eng an einem proprietären Tool oder Protokoll eines Drittanbieters?

Motivierende Werte: Move fast fail fast, Niedrige technische Hürden

Wie sollen Systeme in der Cloud laufen?

Cloud Native

Aussage: Wir ziehen Services vor, die Cloud-Plattformen wie AWS oder GCP direkt anbieten. Jedes Team wählt den passenden Anbieter, sollte die Wahl aber jederzeit begründen können.

Begründung: Wir wollen die Cloud voll ausschöpfen und, wo immer es geht, Services der Plattform nutzen. Das begrenzt die technische Landschaft. Betrieb und Skalierung der Backing Services fallen weg. Vendor Lock-in pro Team nehmen wir in Kauf. Teams nutzen High-Level-Services wie Datenbanken oder Serverless-Infrastruktur, die es bei anderen Anbietern nicht gleichwertig gibt. Gute Entwicklungsprinzipien gelten trotzdem, etwa Geschäftslogik von Infrastrukturkomponenten zu entkoppeln.

Konsequenzen: Für Infrastruktur und unterstützende Services gilt diese Reihenfolge:

  1. Zieht Cloud-Services eures primären Cloud-Anbieters vor
  2. Passt dort nichts, nutzt Software-as-a-Service-Angebote von Drittanbietern
  3. Erst wenn nichts die Anforderungen erfüllt, baut oder betreibt ihr einen eigenen Service in eurem Cloud-Account:
    • Eine aktive Open-Source-Lösung selbst hosten
    • Eine kostenpflichtige Lösung eines Drittanbieters selbst hosten
    • Eine Lösung selbst bauen und hosten

Diskussionspunkte:

  • Welcher Cloud-Anbieter passt technisch am besten zu unserer Software?
  • Wie haben andere Teams genau dieses Problem gelöst?
  • Brauchen wir die gewählte Technologie wirklich?
  • Ist ein Support-Level für eine gewählte Komponente angemessen oder nötig?

Motivierende Werte: Move fast fail fast, Niedrige technische Hürden

Horizontal skalieren

Aussage: Systemkomponenten sollen horizontal skalieren, wo es geht.

Begründung: Billige Ressourcen wie Standard-Server oder Datenbankknoten hinzuzufügen kostet weniger als immer größere Hardware. Die stößt irgendwann an eine Grenze und skaliert nicht weiter. Wer Systeme von Anfang an horizontal skalierbar entwirft, spart sich die Kosten eines späteren Umbaus.

Konsequenzen:

  • Zustandsdaten dürfen nicht im Speicher der Anwendung liegen. Erwägt geteilte Datenbanken oder In-Memory Data Grids wie memcached oder Redis
  • Services sollten nach Idempotenz streben

Diskussionspunkte:

  • Braucht ihr ACID, oder reicht Eventual Consistency (BASE)?

Motivierende Werte: Move fast fail fast

Wie liefert ihr Änderungen sicher und oft aus?

Continuous Delivery und Deployment

Aussage: Strebt kurze Release-Zyklen mit wenigen Änderungen pro Release an. Eine automatisierte Delivery-Pipeline macht das möglich.

Begründung: Kleine Releases haben meist weniger Bugs. Continuous Integration meint gewöhnlich, Code in der Entwicklungsumgebung zu integrieren, zu bauen und zu testen. Continuous Delivery und Deployment bauen darauf auf und decken die letzten Schritte bis zum Production-Deploy ab. Das senkt das Deploy-Risiko und zeigt glaubwürdigen Fortschritt. Feedback von echten Nutzer:innen kommt schneller.

Konsequenzen:

  • Nutzt Feature Toggles. Sie trennen Deploy und Release, und das Business entscheidet, wann ein Feature angeht. Außerdem schaltet ihr damit kaputte Features schnell ab und begrenzt den Schaden. Pflegt pro Stage oder Umgebung eigene Toggle-Sets

  • Ein Release-Toggle-Mechanismus lohnt sich, wenn er vier Anforderungen erfüllt:

    • Ein neuer Toggle berührt wenige Stellen
    • Einen Toggle entfernt ihr per Volltextsuche und Löschen, ohne neuen Code
    • Jede Umgebung aktiviert ihn unabhängig
    • Umschalten braucht keinen Commit und keinen Rebuild

    Haltet die Toggles in der Startkonfiguration der Anwendung selbst, nicht in einem externen Toggle-Service. Ist so ein Service nicht erreichbar, braucht es einen Default, den niemand richtig festlegen kann (welcher von zwei Providern ist der richtige Fallback?). Im Test-Setup schaltet ihr standardmäßig jeden Toggle ein. Dann scheitern die Tests, die ein neues Feature bricht, am Tag, an dem der Toggle kommt. Fällt der Toggle später weg, löscht ihr die Toggle-aus-Fälle, statt die Suite neu zu lesen (codecentric - Der Softwerker Vol. 20: Jederzeit deployen können (öffnet in einem neuen Tab), 2022, deutsch)

  • Testet neue Deploys als Canary, um Probleme früh zu finden

  • Haltet eine automatisierte Test-Suite mit hoher Abdeckung, die schnelle Deploys trägt

  • Nutzt Consumer Driven Contract Tests, damit Änderungen abhängige Systeme nicht brechen

  • Erwägt Push-Button-Deploys jeder Softwareversion in jede Umgebung auf Abruf, vor allem für schnelle Rollbacks

  • Beginnt die Pipeline mit einem einzigen trunk-basierten CI-Build

Diskussionspunkte:

  • Welche manuellen Gates gibt es? Nützt eines davon?
  • Können wir direkt in Production releasen?
  • Vertrauen alle im Team einem grünen Build? Falls nicht: Welche Schritte würden das ändern?
  • Welche vertrauensbildenden Maßnahmen (Tests, CDCs und so weiter) braucht es noch, damit das Vertrauen reicht?

Motivierende Werte: Niedrige technische Hürden, Move fast fail fast

Welche Technologien sollen Teams wählen?

Sensible Defaults

Aussage: Wir halten die Vielfalt der Technologien klein und setzen teamübergreifend auf Sensible Defaults.

Begründung: Wir begrenzen die Technologievielfalt in einem System bewusst, um effizient zu arbeiten. Zugleich pflegen wir ein gesteuertes Innovationsprogramm. Dafür beobachten wir, welche Technologien tatsächlich im Einsatz sind. Wir bewegen uns zu denen, die die jeweiligen Probleme am besten lösen. Gezielt probieren wir neue Technologien aus, die Produkte besser machen könnten. Ein Technologieradar gibt einen guten Überblick über aktuelle Auswahl und Nutzung. Skeleton-Templates helfen neu gegründeten Teams beim schnellen Start. Das Radar ist eine Empfehlung als Leitlinie. Nicht gelistete Technologien sind erlaubt, aber Teams müssen ihre Wahl begründen.

Konsequenzen:

  • Wählt Technologien anhand des Technologieradars
  • Besprecht den Bedarf an neuen Technologien mit anderen
  • Tragt regelmäßig zum Radar bei und hinterfragt es

Diskussionspunkte:

  • Wie oft sollte das Technologieradar aktualisiert werden?
  • Nach welchen Kriterien landet etwas in den Ringen des Tech Radars (trial, assess, adopt, hold)?

Motivierende Werte: Niedrige technische Hürden, Tech follows Business

Low-Tech-Kopplung nutzen

Aussage: Für Kommunikation ziehen wir einfache, bewährte Technologien vor. Die Intelligenz sitzt in den Knoten, nicht im Netz.

Begründung: Low-Tech-Kopplung verringert Probleme, wenn sich kommunizierende Systeme ändern. Sie kann Komplexität und Abhängigkeiten senken. Mit weniger Komplexität findet und erkennt ihr Probleme leichter. Neue Teams steigen leichter ein und zapfen zum Beispiel Event-Streams an. Bewährte, einfache Lösungen senken die kognitive Last, wenn es um Service-zu-Service-Kommunikation geht.

Konsequenzen:

  • Nutzt DNS für Service Discovery
  • Nutzt für synchrone Kommunikation interoperable Protokolle wie HTTP statt zum Beispiel RMI
  • Zieht für Payloads menschenlesbare Formate wie JSON vor (statt protobuf oder msgpack)
  • Streamt Events direkt vom Datenmaster, statt Integrationsmuster wie einen Enterprise Service Bus zu nutzen
  • Sich auf JSON, XML oder eine Tabelle zu einigen, ist noch keine Spezifikation. Die Bedeutung jedes Felds muss trotzdem geklärt werden, und Integrationsprojekte scheitern selten an der Technik. Zwischen m Quellen und n Konsumenten akzeptiert ihr entweder m×n Punkt-zu-Punkt-Routen oder stellt für m+n einen Hub in die Mitte. Die Kunst: keinen zweiten Hub schaffen und das Datenmodell des Hubs bewusst entwerfen. Führt kein System mit einer Fremdschlüsselspalte für die Kennungen der anderen, braucht die Integration einen eigenen Speicher für das Mapping (codecentric - Der Softwerker Vol. 20: Integrationsprobleme in der Schokoladenzentrale (öffnet in einem neuen Tab), 2022, deutsch)

Diskussionspunkte:

  • Welche Probleme würde eine komplexere Lösung beheben? Haben wir dieses Problem überhaupt?
  • Sehen wir in der Organisation Schwierigkeiten, die eine eingesetzte Low-Tech-Lösung verursacht?
  • Braucht der konkrete Einsatzkontext die Performance, die eine komplexere Lösung bringt?

Motivierende Werte: Niedrige technische Hürden, Abdeckung nichtfunktionaler Aspekte

Wie sollen teamübergreifende Entscheidungen fallen?

Öffentlich entscheiden

Aussage: Teamübergreifend entscheiden wir lieber öffentlich, holen Feedback ein und dokumentieren das Ergebnis.

Begründung: Viele technische und Design-Entscheidungen betreffen mehr als ein Team, manchmal die ganze Engineering-Organisation. Wir wollen das gemeinsame Wissen nutzen und öffentlich entscheiden. Alle Interessierten können sich beteiligen, damit wir zum bestmöglichen Ergebnis kommen. Das heißt nicht, dass die Mehrheit abstimmt. Es heißt, dass alle die Chance auf Feedback und Argumente bekommen. Einige Zeit nach einer Entscheidung, besonders wenn sie unerwartete Probleme bringt, hilft es enorm, den damaligen Stand zu kennen: Kontext, Wissen und erwartete Folgen zum Zeitpunkt der Entscheidung.

Konsequenzen:

  • Teamübergreifend setzen wir, wo es geht, auf RFCs (Request for Comment). Ein RFC beschreibt die Herausforderung und mögliche Lösungen. Irgendwann kommen die Entscheidung und ihre Begründung dazu
  • Wir halten RFCs an einem zentralen Ort, damit man sie leicht findet
  • Innerhalb eines Teams empfehlen wir ADRs (Architecture Decision Records) (öffnet in einem neuen Tab) für größere Architektur- oder Technikentscheidungen

Diskussionspunkte:

  • Gab es genug Zeit, ein RFC tatsächlich zu kommentieren?
  • Ist die Entscheidung sauber und an einem leicht auffindbaren Ort dokumentiert?
  • Hat euer Team wichtige Entscheidungen als ADR dokumentiert?

Motivierende Werte: Niedrige technische Hürden, Tech follows Business

Wie viel Security braucht ein System?

Security First

Aussage: Wir entwerfen und pflegen Systeme unter der Annahme, dass jede:r sie angreifen wird und sie womöglich kompromittiert.

Begründung: Einen Security-Vorfall zu bewältigen kostet deutlich mehr, als das System von vornherein zu härten. Kommt es doch zum Einbruch, begrenzen wir Ausmaß und Kosten, indem wir ihn möglichst früh entdecken. Mit einer gut geplanten Reaktion schließen wir die Lücke schnell und bewerten die Folgen. Achten wir von Anfang an auf Security, dämpfen wir die finanziellen Folgen eines Vorfalls. Den Reputationsschaden dämpfen wir ebenso.

Konsequenzen:

  • Folgt einer Security-Checkliste für Anwendungen (Ansatz Security Dojo) und macht in Security-Gilden mit
  • Habt einen Security Champion oder Catalyst und bedenkt die Security-Aspekte jeder Story
  • Nutzt immer TLS und stellt sicher, dass Aufrufer eures Service authentifiziert und autorisiert sind. HTTPS überall, nicht HTTP
  • Findet in Threat-Modelling-Workshops die Bedrohungen, die in der Domäne stecken (z. B. Amplification-Angriffe, bei denen wenige bösartige Datenbankabfragen einen Service-Ausfall auslösen)
  • Entwerft Services und Implementierungen secure by default (z. B. schaltet ein Service TLS und Autorisierung oder Authentifizierung standardmäßig ein, statt sie explizit aktivieren zu müssen)
  • Richtet Monitoring ein, das Brute-Force-Angriffe, ungewöhnliche Anfragen oder ungeschützte Ressourcen automatisch erkennt

Diskussionspunkte:

  • Ist euer gesamter Traffic verschlüsselt?
  • Schützt eine Zugriffskontrolle alle Endpoints?
  • Sind eure Backups verschlüsselt (und signiert)?
  • Würdet ihr merken, wenn eure Cloud-Kosten ungewöhnlich hoch sind?

Motivierende Werte: Abdeckung nichtfunktionaler Aspekte, Tech follows Business, Volle Ownership und klare Verantwortung

Verwandte Seiten

Quellen

Engineering-Praxis