<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="de"><generator uri="https://jekyllrb.com/" version="4.1.1">Jekyll</generator><link href="https://aberger.at/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://aberger.at/blog/" rel="alternate" type="text/html" hreflang="de" /><updated>2026-07-28T06:17:37+00:00</updated><id>https://aberger.at/blog/feed.xml</id><title type="html">Gedanken eines Programmierers</title><subtitle>RESTFul Webservices, Inversion of Control und Cloud Computing.
</subtitle><entry><title type="html">Die kanonische Anwendung</title><link href="https://aberger.at/blog/design/2021/11/06/canonical-application.html" rel="alternate" type="text/html" title="Die kanonische Anwendung" /><published>2021-11-06T06:34:12+00:00</published><updated>2026-07-28T10:00:00+00:00</updated><id>https://aberger.at/blog/design/2021/11/06/canonical-application</id><content type="html" xml:base="https://aberger.at/blog/design/2021/11/06/canonical-application.html"><![CDATA[<p><a href="https://de.wikipedia.org/wiki/Kanonisch" target="_blank" rel="nofollow">Kanonisch</a> - griechisch κανονικός - bedeutet „den Regeln entsprechend“.</p>

<p>Für unsere Applikationsentwicklung habe ich <a href="https://github.com/caberger/leocloud" target="_blank" rel="nofollow">die kanonische Anwendung  als Open Source Projekt</a> auf Github zur Verfügung gestellt. 
Dabei handelt es sich um ein kleines Beispiel mit einer vorgeschalteten Nginx als Web-Server und <a href="https://en.wikipedia.org/wiki/Reverse_proxy" target="_blank" rel="nofollow">Reverse Proxy</a>,
einem nativen Quarkus Microprofile Applicationsserver und einer mysql Datenbank.</p>

<p>Dieses Beispiel wir bei jedem Push des Source Codes automatisch gebaut, getestet, auf einen Staging Server hochgeladen und gestartet, siehe  <a href="https://martinfowler.com/articles/continuousIntegration.html" target="_blank" rel="nofollow">CD/CD</a>. 
Die <a href="https://caberger.github.io/leocloud">Dokumentation</a> wird bei diesem Vorgang ebenfalls automatisch <a href="https://www.writethedocs.org/guide/docs-as-code/" target="_blank" rel="nofollow">aus dem Code erstellt</a> und in Github Pages eingespielt.
Wenn das <a href="https://student.cloud.htl-leonding.ac.at/c.aberger">Ergebnis</a> zufriedenstellen ist, wird es mit kubectl in die <a href="https://cloud.htl-leonding.ac.at/" target="_blank" rel="nofollow">Cloud</a> geladen.</p>

<figure class="image">
    <a href="https://gregorsailer.com"><img src="/blog/assets/images/architecture/architecture.jpg" alt="Die Kanonische Anwendung" /></a>
    <figcaption>Die Kanonische Anwendung</figcaption>
</figure>

<p>Wie man in obigem Diagramm sieht, wird dabei nur der https Port in der Firewall nach außen freigegeben, das REST-API ist hier hinter dem Reverse Proxy verborgen.</p>]]></content><author><name></name></author><category term="design" /><summary type="html"><![CDATA[Eine kanonische Referenzanwendung aus Nginx als Reverse Proxy, nativem Quarkus-Server und MySQL — samt automatischer CI/CD-Pipeline bis in die Cloud.]]></summary></entry><entry><title type="html">Technische Grundkenntnisse</title><link href="https://aberger.at/blog/design/2021/06/22/voraussetzungen.html" rel="alternate" type="text/html" title="Technische Grundkenntnisse" /><published>2021-06-22T07:45:12+00:00</published><updated>2021-06-22T07:45:12+00:00</updated><id>https://aberger.at/blog/design/2021/06/22/voraussetzungen</id><content type="html" xml:base="https://aberger.at/blog/design/2021/06/22/voraussetzungen.html"><![CDATA[<p>Für das Verständnis der technischen Inhalte dieses Blogs sind gute Kenntnisse in den hier angeführten Basistechnologien unverzichtbar.</p>

<ul>
  <li><a href="https://tldp.org/LDP/Bash-Beginners-Guide/html/Bash-Beginners-Guide.html" target="_blank" rel="nofollow">bash</a></li>
  <li><a href="https://docs.docker.com/get-started/" target="_blank" rel="nofollow">docker</a></li>
  <li><a href="http://quarkus.io/" target="_blank" rel="nofollow">quarkus</a></li>
  <li><a href="https://jakarta.ee/" target="_blank" rel="nofollow">JEE</a></li>
  <li><a href="https://nodejs.org/en/" target="_blank" rel="nofollow">nodejs</a></li>
  <li><a href="https://webpack.js.org/" target="_blank" rel="nofollow">webpack</a></li>
  <li><a href="http://maven.apache.org/" target="_blank" rel="nofollow">maven</a></li>
  <li><a href="https://github.com/" target="_blank" rel="nofollow">git</a></li>
  <li><a href="https://www.openapis.org/" target="_blank" rel="nofollow">openapi</a></li>
  <li><a href="https://kubernetes.io/" target="_blank" rel="nofollow">kubernetes</a></li>
</ul>]]></content><author><name></name></author><category term="design" /><summary type="html"><![CDATA[Für das Verständnis der technischen Inhalte dieses Blogs sind gute Kenntnisse in den hier angeführten Basistechnologien unverzichtbar.]]></summary></entry><entry><title type="html">Benutzerdatenvernichtung</title><link href="https://aberger.at/blog/design/2021/06/08/formsubmission.html" rel="alternate" type="text/html" title="Benutzerdatenvernichtung" /><published>2021-06-08T08:00:00+00:00</published><updated>2021-06-08T08:00:00+00:00</updated><id>https://aberger.at/blog/design/2021/06/08/formsubmission</id><content type="html" xml:base="https://aberger.at/blog/design/2021/06/08/formsubmission.html"><![CDATA[<figure class="image">
    <a href=""><img src="/blog/assets/images/trash/trash-small.jpg" alt="Informationsvernichtung" /></a>
    <figcaption>Informationsvernichtung</figcaption>
</figure>

<p>Unsere Software hat vor allem auch die Aufgabe Daten zu erfassen und sicher zu speichern. 
Ein Beispiel: Wir möchten Online etwas einkaufen, durchstöbern das Angebot und stellen uns sehr zufrieden
unseren umfangreichen Warenkorb zusammen.</p>

<p>Stellen wir uns einmal vor, wie wir uns fühlen würden, wenn dann beim Kaufabschluss plötzlich folgende Meldung erscheint: 
“Das Produkt XY, das sie bestellt haben, ist nicht mehr verfügbar. Ihr ganzer Warenkorb wurde deshalb gelöscht”.</p>

<p>Erfüllt diese Software <a href="/blog/design/2021/06/08/surprise.html">das Prinzip der kleinsten Überraschung</a> ? 
Würden wir noch einmal in diesem Shop eine umfangreiche Bestellung zusammenstellen?</p>

<p>Es ist erstaunlich, dass wir manchmal im Berufleben auch im dritten Jahrtausend A.D. noch mit solcher Software arbeiten müssen.
Eine Anwendung, die sich so verhält, ist einfach fehlerhaft.</p>

<p>Wir erwarten uns von einer korrekten Software, dass uns ein Produkt, das wir nicht kaufen können, auch gar nicht zur Auswahl angeboten wird.</p>

<p>Dann sollten wir auch noch darüber nachdenken, ob Benutzer es mögen, wenn wir Daten vernichten, die sie bereits eingegeben haben …?</p>]]></content><author><name></name></author><category term="design" /><summary type="html"><![CDATA[Informationsvernichtung]]></summary></entry><entry><title type="html">Dasselbe und das Gleiche sind nicht dasselbe</title><link href="https://aberger.at/blog/design/2021/06/08/same-equals.html" rel="alternate" type="text/html" title="Dasselbe und das Gleiche sind nicht dasselbe" /><published>2021-06-08T06:34:12+00:00</published><updated>2021-06-08T06:34:12+00:00</updated><id>https://aberger.at/blog/design/2021/06/08/same-equals</id><content type="html" xml:base="https://aberger.at/blog/design/2021/06/08/same-equals.html"><![CDATA[<figure class="image">
    <a href=""><img src="/blog/assets/images/same/same-small.jpg" alt="Dasselbe und das Gleiche" /></a>
    <figcaption>Dasselbe und das Gleiche</figcaption>
</figure>

<p>Manchmal müssen wir in unseren Anwendungen den Benutzer dabei unterstützen bestehende Daten zu kopieren. 
Allein dieser Umstand sollte uns zu denken geben, hierbei handelt es sich wahrscheinlich um einen Design-Fehler im 
Datenmodell.</p>

<p>Sollten wir aber dennoch zu der Entscheidung kommen dem Benutzer einen Dupliziervorgang anzubieten, 
müssen wir das auch richtig implementieren. Eine Kopie muss gleich dem Original sein. Das bedeutet, dass jedes einzelne Feld
mit dem entsprechenden Feld im Original übereinstimmt. Die Kopie ist dann gleich dem Original. Die Kopie ist eine eigene Instanz,
aber sie ist nicht dasselbe.</p>

<p>Wenn wir uns nicht daran halten, verletzen wir <a href="/blog/design/2021/06/08/surprise.html">das Prinzip der kleinsten Überraschung</a>.
Wenn wir uns nicht daran halten ist dies kein Spezialfall - extra für unsere Anwendung die sich der Benutzer doch wohl wird merken können. 
Nein, es ist dann schlicht und einfach fehlerhafte Software.</p>

<p>Manche Programmiersprachen haben für dasselbe und das Gleiche sogar eine eigene Notation<sup id="fnref:equals"><a href="#fn:equals" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>.</p>

<hr />
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:equals">
      <p>Im Java etwa unterscheiden wir zwischen == und Object.equals(), im Javascript gibt es die Vergleichsoperatoren == und === usw. <a href="#fnref:equals" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="design" /><summary type="html"><![CDATA[Dasselbe und das Gleiche]]></summary></entry><entry><title type="html">Das Prinzip der kleinsten Überraschung</title><link href="https://aberger.at/blog/design/2021/06/08/surprise.html" rel="alternate" type="text/html" title="Das Prinzip der kleinsten Überraschung" /><published>2021-06-08T05:00:00+00:00</published><updated>2026-07-28T10:00:00+00:00</updated><id>https://aberger.at/blog/design/2021/06/08/surprise</id><content type="html" xml:base="https://aberger.at/blog/design/2021/06/08/surprise.html"><![CDATA[<figure class="image">
    <a href=""><img src="/blog/assets/images/surprise/surprise-small.jpg" alt="kleine Überraschung" /></a>
    <figcaption>kleine Überraschung</figcaption>
</figure>

<p>Gute Software zeichnet sich dadurch aus, dass sich die Benutzer im Umgang mit ihr wohlfühlen,
dass wir damit eine angenehme Erfahrung erleben. Es ist angenehm, wenn wir unsere Anwendungen benutzen können
ohne dabei in höchsten Maße konzentriert und gestreßt zu sein. Selbst ein Hochschulprofessor wird es schätzen, wenn
er beim Ausfüllen seiner Reiserechnung nur sein halbes Gehirn einschalten muss.</p>

<p>Es ist höchst irritierend, wenn sich ein Programm anders verhält, als wir es im Alltag erwarten würden.
<strong>Software soll die Benutzer unterstützen und nicht die Benutzer die Software</strong>.</p>

<p>Wenn wir Entscheidungen treffen, wie sich unsere Software in gewissen Situationen verhalten soll,
ist das meist ganz einfach. Wir handeln nach dem <strong>Prinzip der kleinsten Überraschung</strong>.
Unsere Anwendung muss so reagieren, wie es der Benutzer intuitiv erwartet und kein vollkommen überraschendes
Verhalten zeigen. Dies verlangt von den Benutzern weniger Konzentration und belastet sie weniger. Unsere Anwendung
steigt so in der Sympathie der Benutzer. Software soll sich idealer Weise so verhalten, 
wie wir es aus unserer täglichen Erfahrung mit der realen Welt gewohnt sind.</p>]]></content><author><name></name></author><category term="design" /><summary type="html"><![CDATA[Das Prinzip der kleinsten Überraschung: Software soll sich so verhalten, wie Benutzer es intuitiv erwarten — das entlastet sie und schafft Sympathie.]]></summary></entry><entry><title type="html">Potemkinsche Dörfer</title><link href="https://aberger.at/blog/design/2021/06/07/pontemkinschesdorf.html" rel="alternate" type="text/html" title="Potemkinsche Dörfer" /><published>2021-06-07T10:34:12+00:00</published><updated>2026-07-28T10:00:00+00:00</updated><id>https://aberger.at/blog/design/2021/06/07/pontemkinschesdorf</id><content type="html" xml:base="https://aberger.at/blog/design/2021/06/07/pontemkinschesdorf.html"><![CDATA[<figure class="image">
    <a href="https://gregorsailer.com"><img src="/blog/assets/images/potemkin/gal-sailer-potemkin-01.jpg" alt="&copy; Gregor Sailer&lt;br&gt;from the series The Potemkin Village&lt;br/&gt;
Carson City, Sweden, 2016" /></a>
    <figcaption>&copy; Gregor Sailer<br />from the series The Potemkin Village<br />
Carson City, Sweden, 2016</figcaption>
</figure>

<p>Manchmal macht es Sinn komplexe Objekte durch Attrappen zu ersetzen. 
Nach aussen hin sehen diese Attrappen aus wie die echten, schweren Objekte, tatsächlich sind sie
aber leicht und einfach.</p>

<p>Sehen wir uns das am Beispiel des <a href="https://de.wikipedia.org/wiki/Modultest" target="_blank" rel="nofollow">automatischen Testens</a>
einer Managed Bean in einer Jakarta™EE-Anwendung an. Wenn wir hier gegen eine echte Datenbank testen würden,
wäre der Aufwand hoch. Jeder einzelne Testfall müsste sich in der Datenbank einloggen, Testdaten einfügen, 
seine Tests durchführen, dabei vielleicht umfangreiche Lese- und Schreibtransaktionen absetzen
und am Ende diese Daten wieder entfernen. Abgesehen vom Verwaltungsaufwand würden diese Tests hohe Durchlaufzeiten haben,
was wir nicht wollen. Der zusätzliche Wert, den die Verwendung der Datenbank an dieser Stelle bringt ist
relativ gering. 
Trotzdem: Wir wollen unsere Anwendung testen ohne sie zu dazu verändern zu müssen. Wir wollen das Original testen.</p>

<p>Dazu verwenden wir für die Tests einen Platzhalter für das Objekt, das für uns die Datenbankzugriffe erledigt.
Dieser Platzhalter implementiert dasselbe Interface wie unser echtes Datenbank-Zugriffsobjekt,
aber seine Umsetzung gleicht der eines <a href="https://de.wikipedia.org/wiki/Potemkinsches_Dorf" target="_blank" rel="nofollow">Potemkinschen Dorfes</a>.</p>

<p>Um das zu veranschaulichen demonstrieren wir das jetzt an dem <em>DataAccessObject</em> für unseren
<a href="/blog/rest/wildfly/2016/10/26/rest-easy.html">RESTeasy und Wildfly</a> Beitrag.
Dazu erstellen wir statt einer realen Datenbank eine Klasse, die die Objekte im Speicher anlegt<sup id="fnref:testcontainers"><a href="#fn:testcontainers" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>.</p>

<p>Das kann so aussehen:</p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Named</span>
<span class="nd">@ApplicationScoped</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">InMemoryUserDatabase</span> <span class="o">{</span>
    <span class="kd">private</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">,</span> <span class="nc">User</span><span class="o">&gt;</span> <span class="n">users</span><span class="o">;</span>
    <span class="kd">private</span> <span class="kt">int</span> <span class="n">sequence</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">InMemoryUserDatabase</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">users</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;();</span>
        <span class="n">addUser</span><span class="o">(</span><span class="s">"Joe Sixpack"</span><span class="o">,</span> <span class="s">"joe.sixpack@example.com"</span><span class="o">);</span>
        <span class="n">addUser</span><span class="o">(</span><span class="s">"John Doe"</span><span class="o">,</span> <span class="s">"john.dow@example.com"</span><span class="o">);</span>
        <span class="n">addUser</span><span class="o">(</span><span class="s">"Jane Roe"</span><span class="o">,</span> <span class="s">"jane.roe@example.com"</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="kd">public</span> <span class="nc">User</span> <span class="nf">addUser</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nc">String</span> <span class="n">email</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">User</span> <span class="n">user</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">User</span><span class="o">();</span>
        <span class="n">user</span><span class="o">.</span><span class="na">id</span> <span class="o">=</span> <span class="o">++</span><span class="n">sequence</span><span class="o">;</span>
        <span class="n">user</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
        <span class="n">user</span><span class="o">.</span><span class="na">email</span> <span class="o">=</span> <span class="n">email</span><span class="o">;</span>
        <span class="n">users</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">id</span><span class="o">,</span> <span class="n">user</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">user</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="kd">public</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Integer</span><span class="o">,</span> <span class="nc">User</span><span class="o">&gt;</span> <span class="nf">getUsers</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">users</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Hierbei haben wir der Datenbank den <em>@ApplicationScope</em> gegeben, so dass die Daten während der gesamten Laufzeit der Anwendung
erhalten bleiben.</p>

<p>Damit implementieren wir unser UserDao:<sup id="fnref:returnonce"><a href="#fn:returnonce" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Named</span>
<span class="nd">@RequestScoped</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">InMemoryUserDao</span> <span class="kd">implements</span> <span class="nc">DataAccessObject</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="o">{</span>
    <span class="nd">@Inject</span>
    <span class="nc">InMemoryUserDatabase</span> <span class="n">userDatabase</span><span class="o">;</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">User</span> <span class="nf">findById</span><span class="o">(</span><span class="kt">int</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
         <span class="k">return</span> <span class="n">userDatabase</span><span class="o">.</span><span class="na">getUsers</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="nf">listAll</span><span class="o">()</span> <span class="o">{</span>
        <span class="nc">List</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;</span> <span class="n">sortedUsers</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;(</span><span class="n">userDatabase</span><span class="o">.</span><span class="na">getUsers</span><span class="o">().</span><span class="na">values</span><span class="o">());</span>

        <span class="nc">Collections</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="n">sortedUsers</span><span class="o">,</span> <span class="k">new</span> <span class="nc">Comparator</span><span class="o">&lt;</span><span class="nc">User</span><span class="o">&gt;()</span> <span class="o">{</span>
            <span class="kd">public</span> <span class="kt">int</span> <span class="nf">compare</span><span class="o">(</span><span class="nc">User</span> <span class="n">lhs</span><span class="o">,</span> <span class="nc">User</span> <span class="n">rhs</span><span class="o">)</span> <span class="o">{</span>
                <span class="k">return</span> <span class="n">lhs</span><span class="o">.</span><span class="na">id</span> <span class="o">-</span> <span class="n">rhs</span><span class="o">.</span><span class="na">id</span><span class="o">;</span>
            <span class="o">}</span>
        <span class="o">});</span>
        <span class="k">return</span> <span class="n">sortedUsers</span><span class="o">;</span>
    <span class="o">}</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="nc">User</span> <span class="nf">add</span><span class="o">(</span><span class="nc">User</span> <span class="n">user</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">userDatabase</span><span class="o">.</span><span class="na">addUser</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">name</span><span class="o">,</span> <span class="n">user</span><span class="o">.</span><span class="na">email</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">delete</span><span class="o">(</span><span class="nc">User</span> <span class="n">user</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">userDatabase</span><span class="o">.</span><span class="na">getUsers</span><span class="o">().</span><span class="na">remove</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">id</span><span class="o">);</span>
    <span class="o">}</span>
    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">update</span><span class="o">(</span><span class="nc">User</span> <span class="n">user</span><span class="o">)</span> <span class="o">{</span>
        <span class="kt">boolean</span> <span class="n">ok</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
        <span class="nc">User</span> <span class="n">existingUser</span> <span class="o">=</span> <span class="n">userDatabase</span><span class="o">.</span><span class="na">getUsers</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">id</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">existingUser</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">existingUser</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="na">name</span><span class="o">;</span>
            <span class="n">existingUser</span><span class="o">.</span><span class="na">email</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="na">email</span><span class="o">;</span>
            <span class="n">ok</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="k">return</span> <span class="n">ok</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span></code></pre></figure>

<p>Unser echtes <em>UserDao</em> würde zur Laufzeit einen <em>EntityManager</em> statt der <em>InMemoryDatabase</em> injiziert bekommen.
Darauf geht das <a href="/blog/ios/2016/12/21/java-persistency-api.html">Tutorial über JPA</a> genauer ein. User REST Service lässt sich damit aber schon fertig stellen.</p>

<hr />
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:testcontainers">
      <p>speziell für Datenbank - Logik verwenden wir manchmal aber auch <a href="https://www.testcontainers.org/" target="_blank" rel="nofollow">Testcontainer</a> <a href="#fnref:testcontainers" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:returnonce">
      <p>die lokale Variable ok in der <em>update</em> Funktion entspricht unserem Style Guide, der nur ein einziges return Statement pro Funktion unmittelbar vor der schliessenden Klammer fordert. <a href="#fnref:returnonce" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="design" /><summary type="html"><![CDATA[Wann es sinnvoll ist, komplexe Objekte durch Attrappen zu ersetzen — und wann aus Testhilfen Potemkinsche Dörfer werden, die Substanz nur vortäuschen.]]></summary></entry><entry><title type="html">CI/CD und DevOps</title><link href="https://aberger.at/blog/2021/05/29/cloud-startproject.html" rel="alternate" type="text/html" title="CI/CD und DevOps" /><published>2021-05-29T11:00:00+00:00</published><updated>2021-05-29T11:00:00+00:00</updated><id>https://aberger.at/blog/2021/05/29/cloud-startproject</id><content type="html" xml:base="https://aberger.at/blog/2021/05/29/cloud-startproject.html"><![CDATA[<p>Ein kleines Beispiel für <strong>C</strong>ontinous <strong>I</strong>ntegration und <strong>C</strong>ontinous <strong>D</strong>elivery soll zeigen, wie wir eine
SPA<sup id="fnref:spa"><a href="#fn:spa" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> - Webanwendung mit einer Datenbank, 
einem Applikationsserver und einem Web - REST - Client erstellen.</p>

<p>Das alles klingt sehr verlockend. Die unverzichtbaren Voraussetzungen für das Verständnis dieser Beispielanwendung findet man in dem Beitrag <a href="/blog/design/2021/06/22/voraussetzungen.html">Voraussetzungen</a>. Ich empfehle dringend vorab diese Kenntnisse zu überprüfen,
ansonsten macht die Beschäftigung mit dem Source Code wenig Sinn.</p>

<p>Den Source Code zum Beispiel finden Sie <a href="https://github.com/caberger/javafx-cdi-jpa" target="_blank" rel="nofollow">hier</a>. Dabei handelt es sich um einen
Typescript Client mit WebPack und einen Java/JPA/REST - Server mit Quarkus.</p>

<p>Bei jedem Push in den Master - Branch baut eine Github - Action das Projekt, erstellt die Docker-Images und lädt anschliessend diese Images in die <a href="https://cloud.htl-leonding.ac.at" target="_blank" rel="nofollow">Cloud</a>.
In meinem vorangegangenen Blog über Minikube finden sie, wie wir das Projekt vorher auf dem eigenen Rechner testen.</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:spa">
      <p>Singe Page Application <a href="#fnref:spa" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[Ein kleines Beispiel für Continous Integration und Continous Delivery soll zeigen, wie wir eine SPA1 - Webanwendung mit einer Datenbank, einem Applikationsserver und einem Web - REST - Client erstellen. Singe Page Application &#8617;]]></summary></entry><entry><title type="html">Mentos und Coke</title><link href="https://aberger.at/blog/typescript/mvc/2021/05/23/immutable-state.html" rel="alternate" type="text/html" title="Mentos und Coke" /><published>2021-05-23T11:00:00+00:00</published><updated>2026-07-28T10:00:00+00:00</updated><id>https://aberger.at/blog/typescript/mvc/2021/05/23/immutable-state</id><content type="html" xml:base="https://aberger.at/blog/typescript/mvc/2021/05/23/immutable-state.html"><![CDATA[<p>Wir haben gelernt in unseren mobilen und Desktop-Anwendungen konsequent die Model-View-Controller Architektur einzuhalten. 
Nun aber scheint es uns im Web-Breich sehr verlockend zu sein davon abzugehen und Frameworks zu benutzen, die es nicht
so genau mit dem Applikationszustand nehmen und sich zum Zwecke des Databindings lokale Kopien von Daten des Models in die Komponenten ziehen.
Zu Beginn scheint das sehr viel einfacher zu sein und die durchwegs sehr guten Demo-Beispiele geben uns das Gefühl 
in diesen Frameworks gut aufgehoben zu sein. Oft gibt es ein böses Erwachen, wenn die Anwendung weg vom trivialen
Demo-Beispiel zu einer echten Applikation wachsen soll.</p>

<p><img src="/blog/assets/images/immutable/mentosandcoke.jpg" alt="Mentos and coke" align="right" style="width:380px; margin-left: 16px; margin-top: 8px" /></p>

<p>Man denke zum Beispiel an eine alltägliche Aufgabenstellung: Ein Benutzer fügt ein Produkt zum Warenkorb hinzu.
Wie mühsam ist es da für manche Programmierer das Badge rechts oben,
das anzeigt wie viele Produkte sich im Warenkorb befinden, zu aktualisieren.
Eine Vielzahl spaghettiartig verflochtenter Komponenten kommuniziert miteinander oder reagiert auf Inputs(), erzeugt Outputs und so weiter. Das Angular - 
Anfänger - Tutorial hierzu ist so lange, dass man beim Lesen schon beinah einen Muskelkater vom vielen Herumscrollen bekommt.</p>

<p>Das ist alles viel zu kompliziert, es geht wesentlich einfacher wenn wir uns konsequent an unsere Entwurfsmuster halten.</p>

<p>Unsere Anwendungen müssen große Mengen von “Zustand” verarbeiten. Dieser Zustand setzt sich zusammen aus den Antworten,
die wir von unserem Applikations-Server erhalten haben, aus gecachten und lokal erstellten Daten, aus aktiven Routen, 
aktuellem Zustand von Fortschrittsbalken, Seiten- und Tabauswahl-Informationen und vielem mehr.</p>

<p>Diesen Applikationszustand zu managen ist eine nicht zu unterschätzende Aufgabe. Ein typisches Beispiel eines solchen Vorganges ist: ein Model aktualisiert eine View, dann aktualisiert diese View das Model,
diese aktualisiert wiederum eine andere View, das ein anderes Model aktualisiert, da wiederum zum Update einer anderen View führt. Dabei treten zusätzlich viele Ereignisse asynchron ein, wie etwa eine Benutzereingabe, eine asynchron eintreffende Antwort vom Server, ein Event über einen Websocket etc.</p>

<p>Weil wir die zwei an sich einfachen Dinge <strong>Veränderung</strong> und <strong>Asynchronizität</strong> vermischen,  ist die Komplexität schwer zu handhaben. 
Unserem Verstand fällt es schwer mit der Kombination dieser beiden an sich harmlosen Tatsachen umzugehen. Es erinnert etwas an das Beispiel <a href="https://www.youtube.com/watch?v=ZwyMcV9emmc" target="_blank" rel="nofollow">Mentos und Coke</a>.</p>

<p>In meinem <a href="https://github.com/caberger/javafx-cdi-jpa" target="_blank" rel="nofollow">Demo - Beispiel</a> wird im www-Ordner gezeigt,
wie wir mit den zwei Herausforderungen Veränderung und Asynchronizität problemlos umgehen können.</p>]]></content><author><name></name></author><category term="typescript" /><category term="MVC" /><summary type="html"><![CDATA[Warum lokale Kopien des Anwendungszustands im Web-Frontend so explosiv sind wie Mentos in Cola — und was unveränderlicher State dagegen hilft.]]></summary></entry><entry><title type="html">Minikube: Cloud Computing Grundbegriffe</title><link href="https://aberger.at/blog/cloud/2021/05/04/minikube-intro.html" rel="alternate" type="text/html" title="Minikube: Cloud Computing Grundbegriffe" /><published>2021-05-04T17:00:00+00:00</published><updated>2026-07-28T10:00:00+00:00</updated><id>https://aberger.at/blog/cloud/2021/05/04/minikube-intro</id><content type="html" xml:base="https://aberger.at/blog/cloud/2021/05/04/minikube-intro.html"><![CDATA[<p>Die grundlegenden Begriffe in einer Kubernetes Cloud sind folgende: 
<img src="/blog/assets/images/cloud/steerman.jpg" alt="κυβερνήτης ... Steuermann" align="right" style="width:200px; margin-right: 0px; margin-top: 0px" /></p>

<ul>
  <li><strong>Controller</strong>: Etwas, das den Ist- Zustand mit dem Soll-Zustand vergleicht und den Ist-Zustand ändert, bis er dem Soll-Zustand entspricht.</li>
  <li><strong>ReplicaSet</strong>: versucht immer eine stabile Menge von Pods am Laufen zu halten.</li>
  <li><strong>Node</strong>: ein Computer</li>
  <li><strong>Pod</strong>: Ein Gruppe von einem oder mehreren Containern mit gemeinsamem Speicher und Netzwerk-Resourcen und einer Spezifikation, wie diese Container laufen sollen.</li>
  <li><strong>Deployment</strong>: stellt deklarative Updates für Pods und Replicasets zur Verfügung. Der <em>Deployment Controller</em> ändert den Status der Deployments, so dass dieser dem vorgegebenen Zustand des Deployments entspricht.</li>
  <li><strong>Service</strong>: Ein abstrakter weg um eine Applikation, die auf einer Menge von Pods läuft, nach außen verfügbar zu machen.</li>
  <li><strong>Ingress</strong>: ein Controller, der ständig nachsieht, ob es aktuell neue Services gibt, die - falls ja - auf einem öffentlichen Port in das Internet freigegeben werden.
<img src="/blog/assets/images/cloud/kubernetescluster.png" alt="Kubernetes Cluster" /></li>
  <li><strong>Container</strong>: Das Ergebnis, wenn ein Dockerfile mit <code class="language-plaintext highlighter-rouge">docker build</code> oder <code class="language-plaintext highlighter-rouge">docker-compose up --build</code> gebaut wird.</li>
</ul>

<p>Zunächst installieren, starten und konifgurieren wir <a href="https://minikube.sigs.k8s.io/docs/start/" target="_blank" rel="nofollow">minikube</a>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>minikube start
minikube addons list
minikube addons <span class="nb">enable </span>ingress
minikube addons <span class="nb">enable </span>dashboard
</code></pre></div></div>

<p>Jetzt speichern wir die folgenden Dateien in unserem k8s<sup id="fnref:kubernetes"><a href="#fn:kubernetes" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> Ordner.</p>

<p>meow-deployment.yaml:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">apps/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Deployment</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">meow</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">replicas</span><span class="pi">:</span> <span class="m">2</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">matchLabels</span><span class="pi">:</span>
      <span class="na">app</span><span class="pi">:</span> <span class="s">meow</span>
  <span class="na">template</span><span class="pi">:</span>
    <span class="na">metadata</span><span class="pi">:</span>
      <span class="na">labels</span><span class="pi">:</span>
        <span class="na">app</span><span class="pi">:</span> <span class="s">meow</span>
    <span class="na">spec</span><span class="pi">:</span>
      <span class="na">containers</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">meow</span>
        <span class="na">image</span><span class="pi">:</span> <span class="s">gcr.io/kubernetes-e2e-test-images/echoserver:2.1</span>
        <span class="na">ports</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">containerPort</span><span class="pi">:</span> <span class="m">8080</span>
</code></pre></div></div>

<p>meow-service.yaml:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">meow-svc</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">ports</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">port</span><span class="pi">:</span> <span class="m">80</span>
    <span class="na">targetPort</span><span class="pi">:</span> <span class="m">8080</span>
    <span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">http</span>
  <span class="na">selector</span><span class="pi">:</span>
    <span class="na">app</span><span class="pi">:</span> <span class="s">meow</span>
</code></pre></div></div>
<p>ingress.yaml:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Ingress</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">meow-ingress</span>
  <span class="na">annotations</span><span class="pi">:</span>
    <span class="na">nginx.ingress.kubernetes.io/rewrite-target</span><span class="pi">:</span> <span class="s">/</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">rules</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">http</span><span class="pi">:</span>
      <span class="na">paths</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">/meow</span>
        <span class="na">pathType</span><span class="pi">:</span> <span class="s">Prefix</span>
        <span class="na">backend</span><span class="pi">:</span>
          <span class="na">service</span><span class="pi">:</span>
            <span class="na">name</span><span class="pi">:</span> <span class="s">meow-svc</span>
            <span class="na">port</span><span class="pi">:</span>
              <span class="na">number</span><span class="pi">:</span> <span class="m">80</span>
</code></pre></div></div>

<p>Anschließend spielen wir das auf unserem minikube ein:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl apply <span class="nt">-f</span> meow-deployment.yaml 
kubectl apply <span class="nt">-f</span> meow-service.yaml
kubectl apply <span class="nt">-f</span> ingress.yaml
minikube dashboard
</code></pre></div></div>

<p>Danach starten wir kubectl um uns das meow-service auf einen Port auf unserem Rechner zu forwarden:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl port-forward service/meow-svc 4000:80
</code></pre></div></div>

<p>Damit haben wir in unserem Minikube folgende Situation:</p>

<p><img src="/blog/assets/images/cloud/ingress.png" alt="Ingress Controller" /></p>

<p>Die URL, die wir im Browser aufrufen ist folgende: http://localhost:4000</p>

<p>Jetzt sehen wir, dass wir einen von außen erreichbaren Dienst haben.</p>

<p>Ein etwas umfangreicheres Beispiel mit einem Applikations-Server finden Sie <a href="https://github.com/caberger/javafx-cdi-jpa" target="_blank" rel="nofollow">hier</a>.</p>

<p>Wenn unsere Projekte so vorbereitet sind, dass sie in dieser Art problemlos auf minikube laufen, können wir diese in unsere eigene Cloud einspielen. 
Wie wir uns aus ein paar normalen Servern aus purem Metall eine echte eigene Cloud bauen, sehen wir in einem der nächsten Beiträge.</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:kubernetes">
      <p>ein <em>k</em>, acht andere Buchstaben und ein <em>s</em> … <em>kubernetes</em> <a href="#fnref:kubernetes" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="cloud" /><summary type="html"><![CDATA[Die Grundbegriffe einer Kubernetes-Cloud — Controller, Pod, Deployment, Service und Ingress — erklärt am laufenden Beispiel eines lokalen Minikube-Clusters.]]></summary></entry><entry><title type="html">OSX im VMWare Player installieren</title><link href="https://aberger.at/blog/install/2020/12/08/install-osx.html" rel="alternate" type="text/html" title="OSX im VMWare Player installieren" /><published>2020-12-08T15:00:00+00:00</published><updated>2020-12-08T15:00:00+00:00</updated><id>https://aberger.at/blog/install/2020/12/08/install-osx</id><content type="html" xml:base="https://aberger.at/blog/install/2020/12/08/install-osx.html"><![CDATA[<p>Die <a href="/blog/design/2016/10/23/drei-prinzipien.html">drei Design Prinzipien</a>
haben uns zu einer Architektur geführt, die offen für alle Client-Plattformen ist.
So auch für iOS, tvOS und OSX  Anwendungen, mit denen wir unsere iPhones, iPads, Apple Watches und AppleTVs in ihrer nativen Sprache,
mit <em>Swift</em> oder mit <em>Objective-C</em> programmieren können. Hierzu benötigen wir eine Installation von OSX</p>

<h1 id="installation-von-osx-unter-windows">Installation von OSX unter Windows</h1>

<p>Die OSX-Programmierung lässt sich auch in einer virtuellen Umgebung unter Linux ausführen.
Wer auf seinem Computer Windows installiert hat, verkleinert einfach die Windows Partition und installiert ein Dual-Boot System<sup id="fnref:dualboot"><a href="#fn:dualboot" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> mit Linux.</p>

<h1 id="installation-von-osx-unter-linux">Installation von OSX unter Linux</h1>

<p>Zur Installation folgen wir den Anweisungen aus dem <a href="https://github.com/foxlet/macOS-Simple-KVM/" target="_blank" rel="nofollow">macOS-Simple-KVM</a> Projekt.</p>

<p>Damit sind wir bereit zum nächsten Schritt
<a href="/blog/tutorial/2016/11/05/learn-swift.html">iOS Anwendungen entwickeln</a>.</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:dualboot">
      <p>zum Beispiel <a href="https://help.ubuntu.com/community/WindowsDualBoot" target="_blank" rel="nofollow">Ubuntu</a> <a href="#fnref:dualboot" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="install" /><summary type="html"><![CDATA[Die drei Design Prinzipien haben uns zu einer Architektur geführt, die offen für alle Client-Plattformen ist. So auch für iOS, tvOS und OSX Anwendungen, mit denen wir unsere iPhones, iPads, Apple Watches und AppleTVs in ihrer nativen Sprache, mit Swift oder mit Objective-C programmieren können. Hierzu benötigen wir eine Installation von OSX]]></summary></entry></feed>