Blog: Latest Entries (15):


References vs Callback-Functions in Vue und React

Bei Vue.js werden bei den Props, die an eine Unterkomponente übergeben werden, diese als Referenz übergeben. Wenn ich in der Unterkomponente etwas Ändere, werden diese Änderungen im selben Object durchgeführt, dass sich auch in der Oberkomponente befindet und ich kann diese Änderungen direkt dort verwenden. Ein Watcher sollte auch Änderungen erkennen können.
Bei React müßte ich eine callback-funktion mit in die Unterkomponente reinreichen und bei der Änderung diese antriggern, damit die Oberkomponente die geänderten Daten in denen eigenen State wieder übernehmen kann. Also wird in dem Sinne jede Reference, wie sie in Vue.js existiert hätte, durch eine selbst zu implementierende callback-Funktion ersetzt?

Macht das Sinn? Erzeugt es nicht nur mehr Arbeit und Code? Wo genau ist da der Vorteil? Oder habe ich da was falsch verstanden bei React?

Irgendwie werde ich mit React nicht wirklich warm. Es sind so Kleinigkeiten, die mich da stören oder mir das Gefühl geben, einiges wäre unnötig kompliziert in React.

PHP: Smarty 3 und nested-Blocks

Ein Problem bei Smarty 3 ist, dass man zwar sehr schön Blöcke überschreiben oder erweitern kann, es aber keine Möglichkeit gibt, die im Block vorhanden anderen Blöcke dan unangetastet zu lassen. Man muss in so einem Fall die nested Blöcke immer mit kopieren und mit dem Parent-Template immer wieder bei Änderungen abgleichen. Wenn z.B. ein Template einen Block mit einer <form> hat und darin dann die ganzen Eingabe-Möglichkeiten auch als Blöcke ausgelegt sind, muss man wenn man die Form-Action ändern will zwingend auch das ganze Form-Content Templating mit übernehmen.

Eine Möglichkeit das zu umgehen ist den Block rendern zu lassen und dann per String-Operationen das Ergebnis anzupassen. Primitiv aber auch sehr effektiv!

https://stackoverflow.com/questions/38913099/extending-nested-blocks-keep-children

Beispiel:

{extends file='parent:frontend/forms/form-elements.tpl'}

{block name='frontend_forms_form_elements'}
{capture name="frontend_forms_form_elements"}
{$smarty.block.parent}
{/capture}
{$smarty.capture.frontend_forms_form_elements|replace:'register/index':'forms/index'}
{/block}


Falls man Formular von Shopware in anderen Contexts als dem forms-Controller einsetzt, kann es nötig sein, dass Template so anzupassen, damit es noch auf den forms-Controller zeigt und nicht auf den Controller in desen Context man gerade arbeitet.


Shopware: Eine Static-Content/Seiten Gruppe anlegen

Eine neue Seiten-Gruppe im Backend anlegen und dabei einen Key wie "myGroup" vergeben.

Der Key, den man dort verwendet, findet man in $sMenu wieder.


<ul class="group--mygroup">
{for $sMenu.myGroup as $item}
<li>
<a href="{if $item.link}{$item.link}{else}{url controller='custom' sCustom=$item.id title=$item.description}{/if}">
{$item.description}
</a>
</li>
{/for}
</ul>


Damit ist es auch schon erledigt und man kann anfangen Seiten der Gruppe zu zuordnen und sie werden entsprechend gerendert. Unterseiten und ähnliches sind auch möglich, wie frontend/index/footer-navigation.tpl zeigt. Die Gruppen findet man in der Datenbank in der Table s_cms_static_groups.

Optionale Parameter in Spring Controllern

An sich ist das hier vollkommen logisch und man wundert sich warum man diesen Fehler überhaupt gemacht hat.. weil man den vorher nicht gemacht hat. Deswegen sollte man im Kopf behalten, dass wenn man optionale Parameter im Methodenaufruf im REST-Controller in Spring hat, diese null als Wert haben können müssen.

Einfach gesagt Integer verwenden und nicht int, weil das natürlich Probleme geben würde, wenn Spring null in einen int füllen möchte.


getList(@RequestParam(value = "typeId", required = false) Integer typeId){}

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.

Older posts:

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