Blog: Latest Entries (15):


Ein Docker-Image CI-Script für GitLab

Die Scripts basieren auf dieser Anleitung von DigitalOcean.

Mit diesem Script werden der Master und alle Tags als Docker-Image gebaut, wobei der Master dann auch als latest-Tag abgelegt wird. D.h. der Master muss immer stable sein.. bei jedem Commit.


image: docker:latest
services:
- docker:dind

stages:
- build
- release

variables:
BUILD_IMAGE: gitlab.example.com:5555/sammy/hello_hapi:$CI_COMMIT_REF_NAME
RELEASE_IMAGE: gitlab.example.com:5555/sammy/hello_hapi:latest

before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN gitlab.example.com:5555

build_master_and_tags:
stage: build
script:
- docker build --pull -t $BUILD_IMAGE .
- docker push $BUILD_IMAGE
only:
- master
- tags

release_latest:
stage: release
script:
- docker pull $BUILD_IMAGE
- docker tag $BUILD_IMAGE $RELEASE_IMAGE
- docker push $RELEASE_IMAGE
only:
- master


Alternative kann man auch sich auf verschiedene Tags-Notationen einigen, wobei nur die Tags als latest getaggt werden, wenn diese ein release-Tag sind. Hier muss man aufpassen, falls man eine alte Version
nachträglich einpflegen will.


image: docker:latest
services:
- docker:dind

stages:
- build
- release

variables:
BUILD_IMAGE: gitlab.example.com:5555/sammy/hello_hapi:$CI_COMMIT_REF_NAME
RELEASE_IMAGE: gitlab.example.com:5555/sammy/hello_hapi:latest

before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN gitlab.example.com:5555

build_tags:
stage: build
script:
- docker build --pull -t $BUILD_IMAGE .
- docker push $BUILD_IMAGE
only:
- tags

release_latest:
stage: release
script:
- docker pull $BUILD_IMAGE
- docker tag $BUILD_IMAGE $RELEASE_IMAGE
- docker push $RELEASE_IMAGE
only:
- /^release-.*$/
except:
- branches

Create + Update vs Merge

Ich komme ja aus der Hibernate/JPA Ecke was ORMs angeht und habe auch als eines der ersten Plugins, das die Shopware-API betraf, mir ein Plugin geschrieben das POST und PUT gleichsetzt. Am Ende sehe ich nicht nur bei ORMs sondern auch bei REST-APIs keinen Vorteil darin zwischen CREATE und UPDATE beim Aufruf zu unterscheiden. Intern kann immer noch geprüft werden, ob eine Id gesetzt ist oder NULL/0 ist. Ich wurde CRUD eher durch ein RSD (READ SAVE DELETE) ersetzen. Welche Art von SAVE dann API intern ausgeführt wird, muss den Benutzer der API nicht interessieren.

Wenn die verwendete ID ein Business-Key ist hat es auch den Vorteil, dass man nicht bestimmen muss, ob man beim Speichern CREATE oder UPDATE aufrufen muss. Bei einem einheitlichen SAVE funktioniert es einfach und erspart viel Kommunikation mit dem Server.

Das ganze nur mal so zwischen durch... wollte ich nur mal sagen und nicht immer nur denken.

Spring Boot: Profiles wählen

Wenn man verschiedene application.properties Dateien für verschiedene Spring Boot Anwendungen vorhält kann man diese übe einen VM Parameter auswählen. Wenn wir z.B. ein Profil für "local" haben, wo eine DB auf Localhost verwendet werden soll, geht es so:


-Dspring.profiles.active=local


Damit wird dann die application-local.properties verwendet.

Scrum: Userstories und Impactmaps

"Das steht da nirgends drin", "Das ist viel zu ungenau", "ich kann da nicht raus lesen, dass da so etwas gefordert wird"... Userstories. In einer Userstory beschreib ein User was er mit dem Produkt machen will oder wie er das Produkt bedienen will. Jeden Falls kommt die Definition von Userstory oft so rüber. Aber jeder der mal mit einem Anwender zusammen Prozesse aufgenommen hat und sich dabei beschreiben lies, was der Benutzer eigentlich immer macht um zum Ziel zu kommen, weiß dass Benutzer nicht immer die zuverlässigste Quelle für korrekte oder vollständige Informationen ist. Was da manchmal raus kam.. damit will niemand einen Sprint planen. Ich halte Userstories für eine wichtige Sache, aber würde nie eine Userstory 1:1 in Tasks transferieren.

Mir gefällt diese Interpretation der Userstory als Hypothese sehr gut, weil mehr kann eine Userstory nicht sein. Eine Userstory ist der Lösungsweg einer Interpretation eines Problems oder Ziels. Niemand kann bei einer Userstory sicher sein, dass das Ziel richtig verstanden und seine Position im übergreifenden Gesamtbild richtig verstanden worden ist. Ist der Weg im gesamten Prozessablauf sinnvoll und schlank umgesetzt?
Wenn ich eine Userstory direkt von einem Benutzer bekomme, muss diese einfach nochmal gegen geprüft werden. Es gibt immer ein Problem, das zu lösen ist oder ein Ziel, das zu erreichen ist. Gerade wenn es um die Verbesserung eines vorhandenen Produkts geht, wo mehr zu tun ist, als ein paar GUI-Elemente neu zu ordnen, ist bis zur wirklichen Verwendung durch die Benutzer nie klar, ob die erhoffte Verbesserung wirklich eintritt. Oft ist nicht einmal klar, ob es nicht auch ungewollte Nebeneffekte gibt, die nicht bedacht wurden (die aber eingeplant werden müssen).

Man kennt es ja sicher, dass eine Abteilung mit neuen Anforderungen kommt, die alles besser machen sollen und man Tätigkeiten, die man gar nicht bräuchte, entfernt hat. Oft gucken, die dann sehr doof, wenn man denen erklärt, dass diese Tätigkeiten, aber für die nach gelagerten Prozesse in einer anderen Abteilung echt wichtige Daten liefern, die auch nur da her kommen können. Die Userstory war also eine Hypothese der Abteilung, die sich als falsch heraus stellte. Zum Glück noch bevor etwas umgesetzt wurde und es sich im Test oder gar im Produktivbetrieb herausstellen konnte.

Die im Artikel beschriebene Impact-Map bringt genau diese Übersicht mit und zwängt die Userstories in einen direkten Kontext in dem sie viel besser zu beurteilen und zu interpretieren sind sind, als wenn diese allein stehen würden.

- Welches Ziel soll insgesamt erreicht werden?
- Wer ist von diesen Änderungen betroffen? (Abteilungen, Kunden, etc)
- Welche einzelnen Änderungen sollen zum Gesamtziel führen? (Wo gibt es also momentan Probleme und Verbesserungspotential)

Erst dann kommen Epics und Userstories.

So steht über jeder Userstory ein Problem, dass zu lösen ist. Wenn eine Userstory eindeutig, dass Problem nicht löst, setzt man diese nicht um. Ähnliches gilt für Dinge die eindeutig fehlen um ein Problem zu lösen. "Da steht aber nirgends, dass man eine Benutzerverwaltung mit Rollen und Hierarchie will!".. aber es steht da, dass die Bereichsleiter Reports über die Tätigkeiten ihrer Abteilungen sehen wollen. Da würde einfach der wieder der Blick auf die Übergreifenden Prozesse vergessen und nur die Prozesse in den Abteilungen betrachtet. Also fängt man damit an eine Umgebung zu schaffen, bei der man sich Anmelden kann und ein Benutzer-Objekt mit Berechtigungen erhält, bevor man anfängt sensible Ansichten zu bauen, die diese Benutzer-Daten brauchen, um die richtigen Daten anzeigen zu können (und nicht zu viele Daten, die der Benutzer nicht sehen dürfte).

Und am Ende darf man nie vergessen, zu prüfen, ob eine Userstory auch wenn sie logisch klang, den gewünschten Effekt hatte. Dabei muss man schnell prüfen und messen, damit man schnell reagieren kann, um dann wieder rum schnell nachbessern zu können.

Userstories helfen also dabei den Kontext zu verstehen, aber nehmen einen nicht die Hauptaufgabe ab, nämlich als Entwickler eine Lösung für ein Problem zu entwickeln. Der Begriff Softwareentwickler beschreibt nicht was man entwickelt, sondern mit welchen Werkzeug man entwickelt!

Lifelong Learning: ABAP

Ich habe gestern angefangen mein Geburtstagsgeschenk "Schrödinger programmiert ABAP" zu lesen. Bis jetzt kann ich sagen, dass man schon mal ein gutes Gefühl bekommt wie man sich ein Dev-System einrichten kann und welche Teile bei den Anfängen elementar sind (z.B. das Logon-Tool mit seinen Load-Balancing Fähigkeiten).

bbcode-image


In der nächsten Zeit werde ich mal gucken, ob ich es schaffe mir selbst ein SAP zu installieren (gibt es das auch für Docker?) und auch viel weiter lesen.

Auf die Art des Buches muss man sich aber erst einmal einlassen.. zum Glück sind nur die ersten Seiten etwas wirr und man findet sich schnell in die Struktur ein.

Apache Meecrowave: REST-API + Docker schnell und einfach

Wer sich sonst immer für Wildfly/Tomcat und JAX-RS für seine REST-API Lösungen entschieden hat wird sich mit Apache Meecrowave sehr schnell zu Hause fühlen. Im Grunde ist es auch nichts anderes als ein Tomcat mit JAX-RS nur dass die Setup-Phase fast komplett entfällt. Für Microservices und schnelle Lösungen hat man in wenigen Minuten eine funktionsfähige REST-API.

Für eine einfache REST-API braucht man die pom.xml, eine Klasse mit einer Main-Methode und einen REST-Endpoint.


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>TestMeecrowave</groupId>
<artifactId>TestMeecrowave</artifactId>
<version>1.0-SNAPSHOT</version>

<plugins>
<plugin>
<groupId>org.apache.meecrowave</groupId>
<artifactId>meecrowave-maven-plugin</artifactId>
<version>1.2.1</version>
</plugin>
</plugins>

<properties>
<meecrowave.version>1.2.3</meecrowave.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.meecrowave</groupId>
<artifactId>meecrowave-core</artifactId>
<version>${meecrowave.version}</version>
</dependency>

<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.1</version>
</dependency>
</dependencies>
</project>


Startet mit Port 8080. Mehr Starteinstellungen sind möglich aber nicht nötig:

public class MainClass {
public static void main(String[] args) {
try{
Meecrowave.Builder builder = new Meecrowave.Builder();
builder.setHttpPort(8080);
builder.setJaxrsMapping("/api/*");
try (Meecrowave meecrowave = new Meecrowave(builder)) {
meecrowave.bake().await();
}
}
catch(Exception e){
e.printStackTrace();
}
}
}


Ein einfacher Endpoint als Beispiel:

import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("test")
@ApplicationScoped
public class TestController {
@GET
@Produces(MediaType.APPLICATION_JSON)
public TestModel action(){
TestModel model = new TestModel();
model.setId(23);
model.setName("Test");

return model;
}
}


Der Endpoint ist jetzt erreichbar:

http://localhost:8080/test


Der Vorteil bei dieser Lösung ist, dass man sehr einfach ein Docker-Image mit dieser Anwendung erstellen kann, das man dann direkt deployen kann.
Durch das Meecrowave-Maven-Plugin wird eine meecrowave-meecrowave-distribution.zip im Target-Verzeichnis erstellt.

Ein passendes Docker-Image könnte so aussehen:

FROM openjdk:8-alpine3.8
WORKDIR /app

ADD target/meecrowave-meecrowave-distribution.zip .

RUN apk --no-cache add bash
EXPOSE 8080
ENTRYPOINT["sh /app/meecrowave.sh start"]


Auch für Test gibt fertige Meecrowave-Packages, die man nutzen kann. Sonst geht natürlich auch einfach JUnit.

Wer sich jetzt fragt ob Spring Boot besser oder schlechter ist.. ich hatte jetzt mit beiden zu tun und am Ende ist beides an sich das Selbe mit ein jeweils anderen Libs. Beides ist für Microservices sehr gut geeignet.

Github und Composer

Irgendwann fängt man an aus seinem großen Framework einzelne kleine Module heraus lösen zu wollen. Die will man dann als Composer-Requirements wieder einbinden. Zum Glück geht das sehr einfach, indem man einfach ein Repository zu seiner composer.json hinzufügt:


"repositories": [
{"type": "vcs", "url": "https://github.com/annonyme/events"}
],


und dann kann man einfach das Module hinzufügen:


php composer.phar require annonyme/events
php composer update


Damit kann man dann anfangen, sein Framework in einzelne Module zu zerlegen.


Dateien nachträglich in die gitignore aufnehmen

Manchmal will man später Dateien oder Verzeichnisse in die .gitignore aufnehmen, die man vorher nicht drin hatte und mit versioniert hatte. Das Hinzufügen zur gitignore reicht aber nicht, um die Dateien aus dem Repository verschwinden zu lassen.

Da muss man zuerst


git rm -r --cached .


ausführen und dann wieder alle Dateien adden. Dann werden die nun ignorierten Dateien als gelöscht in Git geführt.

Proxies in JavaScript

Was man in Java teilweise mit Javassist gemacht hat und man für die Erkennung von Änderungen an Object-Values in JavaScript dringend braucht sind Proxy-Objekte.

Das geht zum Glück an sich ganz einfach.


<html>
<head>
<script type="text/javascript">
var x = {value: ""};

var xHandler = {
set: function(target, property, value, receiver) {
target[property] = value;
console.log(target);
}
};

var xProxy = new Proxy( x, xHandler );
</script>
</head>
<body>
<input type="text" onkeyup="xProxy.value=this.value;">
</body>
</html>


Damit kann auch bidirektionales Binding zwischen Model und HTML-Element implementieren.

Was man heute unter KI versteht...

Ein sehr guter Wired-Artikel Auch 2029 wird es keine Künstliche Intelligenz geben, die diesen Namen verdient, der endlich mal klarstellt, dass das was man seit kurzen überall als KI verkauft, nichts weiteres ist als klassische Mustererkennung ist, wie man sie seit Anfang der 90er nutzt. Der klassische KI-Begriff, in dem um die Schaffung einer Intelligenz geht, hat damit garnichts zu tun.

Mich stört es wenn Firmen von KI sprechen und man dahinter nur einfache Datenanalyse mit stupiden Bedingungen und Gewichtungen erkennt. Vektoren zum Vergleichen mit bekannten Mustern sind jetzt auch nicht wirklich neu oder irgendwie intelligent.

Klickbar oder Messbar?

Am Ende eines Sprints soll etwas heraus kommen, was man dem Kunden zeigen kann bzw deliverable ist. Kunden an sich können nur sehen was klickbar ist. Dinge die nicht klickbar sind sind für die meisten Kunden einfach nicht existent, weil diese zu meist keine technischen Anforderungen stellen sondern fachliche. Man Klickt etwas an und es passiert was und das ist das was der Kunde sieht und bewertet.

Aber muss es so sein? Es gibt viele Dinge innerhalb eines Sprints, die die Entwicklung und technische Basis voran bringen ohne das ein Anwender sie bemerkt oder sehen kann. Deswegen ist es vielleicht falsch nur den Kunden die Bewertung zu überlassen. Der Kunde muss nur am Ende etwas sehen, das man messen und dann validieren kann.

Angenommen, ich möchte mehr Automatisierung in der UI haben. Formulare sollen sich automatisch erzeugen und nicht mehr per Hand zusammen gebaut werden müssen. Kann ich das in einem Sprint nach der Definition liefern? Ja! Ich baue es ein und liefere dem Kunden am Ende des Sprints einen Mess-Fall. Genau so ob die Änderung die der Kunde sieht er nach dem Spring validiert werden kann, ob der erhoffte Impact eintritt, kann meine technische Änderung genau so zur selben Zeit getestet werden. Zwar nicht durch einen Anwender sondern durch die Daten des nächsten Sprints selbst, aber das Ergebnis ist das Selbe.
Ich liefere etwas und gebe dem Kunden etwas an die Hand, um die Hypothese auf der dieses gelieferte fußt, prüfen zu können. Ich sage, ich kann mit dieser Änderung im nächsten Sprint 6 neue Formulare in der UI liefern und nicht mehr nur 2. Das ist eine gute Annahme, die sich überprüfen lässt. Ob eine andere Userstory, die vom Kunden selbst kommt, am Ende das erhoffte liefert, weis man genau so wenig, wie diese Annahme, mit der ich den Form-Generator eingebaut habe.

Nach dem folgenden Sprint, gehe ich wieder zum Kunden und zeige ihm, was wir an Formularen gebaut haben. Nun prüft er, ob meine Annahme mit 6 Formularen erfüllt wurde. Vielleicht sind es nur 5 geworden oder wir hatten sogar Zeit über, in der wir noch ein Formular mehr hätten machen können... das ist gar nicht ganz so wichtig, weil sich heraus gestellt hat, dass es eine Verbesserung gab und damit meine technische Userstory erfüllt werden konnte und nicht nochmal neu bearbeitet werden oder als Fehlschlag einzustufen ist.

Ob man also klickt oder am Ende Zahlen vorlegt, ist für den Kunden dann egal. Man hat jeweils etwas gebaut, was sich in der Zeit danach bewähren muss und nur das was dann da heraus kommt zählt und nicht was am Ende des Sprint sichtbar war. Deswegen muss ich am ende des Sprints immer nur etwas liefern, was messbar ist. Nicht etwas was direkt sichtbar ist.

Behat + Mink: Selenium und eigene Methoden

Wenn man mit JavaScript zu tun bekommt, führt leider kein Weg an Selenium vorbei. Das erst einmal zum laufen zu bekommen ist dabei auch nicht immer einfach. Ich habe dabei gelernt, dass Firefox nicht wirklich funktioniert und Chrome dagegen ohne Probleme funktioniert. Um Selenium zum Laufen zu bekommen brauchen wir einiges:

- eine aktuelle Version des Selenium Standalone Servers als JAR
- den Chrome-Driver für Selenium
- Java

dann kann man Selenium mit java -Dwebdriver.chrome.driver=/opt/selenium/chromedriver -jar selenium-server-standalone-X.X.X.jar starten. Den Driver kann man natürlich auch in anderen Verzeichnissen ablegen.
https://selenium-release.storage.googleapis.com/index.html

In der Behat-config sieht es dann so aus:


default:
suites:
default:
contexts:
- Behat\MinkExtension\Context\MinkContext
# - TestContext
extensions:
Behat\MinkExtension:
base_url: 'http://www.hannespries.de'
files_path: 'vendor'
browser_name: 'chrome'
selenium2: ~


Um nun auch eigene Methoden wie Warten hinzuzufügen brauchen wir einen eigenen Context. Den legen wir unter features/bootstrap ab.


<?php

class TestContext extends \Behat\MinkExtension\Context\MinkContext {
/**
* @When I wait :arg1 seconds
*/
public function iWaitSeconds($seconds)
{
$this->getSession()->wait($seconds * 1000);
}

/**
* @When I click the :arg1 element accepting the alert
*/
public function iClickTheElementAcceptingTheAlert($selector)
{
$page = $this->getSession()->getPage();
$element = $page->find('css', $selector);

if (empty($element)) {
throw new Exception("No html element found for the selector ('$selector')");
}

$element->click();

$driver = $this->getMink()->getSession()->getDriver();
if($driver instanceof \Behat\Mink\Driver\Selenium2Driver){
/** $driver \Behat\Mink\Driver\Selenium2Driver*/
$driver->getWebDriverSession()->accept_alert();
}
}
}


Jetzt den Context eintragen. Weil wir mit extends arbeiten dürfen nicht beide aktiv sein, weil die sonst die selben Methoden implementieren.


default:
suites:
default:
contexts:
# - Behat\MinkExtension\Context\MinkContext
- TestContext
extensions:
Behat\MinkExtension:
base_url: 'http://www.hannespries.de'
files_path: 'vendor'
browser_name: 'chrome'
selenium2: ~


Nun können wir Warten und sogar Javascript Dialoge bestätigen.

Wenn wir nun mit "bin/behat features/XXX.feature" unser Test Scenario XXX starten öffnet sich der Chrome-Browser und wir können alle Tests visuell mit verfolgen.

bbcode-image

Orange Pi One: Ein passendes Netzteil

Leider kann man über den Orange Pi One nicht über den USB-Anschluss mit Strom versorgen sondern braucht ein 5V 2A Netzteil. Ich habe mir dieses Aukru 8 in 1 USB Ladegerät gekauft und mit den Adaptern passte es dann auch und der Adapter sitzt auch so, dass ich erst einmal den Karton als vorläufiges Gehäuse verwenden kann.

bbcode-image


bbcode-image


Damit stehen den Experimenten mit Motioneye OS nichts mehr im Weg. Wenn das nichts wird, kommt einfach RetroPi rauf :-)


Older posts:

Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?