Ich habe bei meinem alten Thinkpad T500s die 160GB HDDs durch günstige Kingston 120GB SSDs ausgetauscht. Zusätzlich noch Windows 10 neu installiert und es ist wirklich ein merklicher Unterschied, so dass die Thinkpads für einfache Aufgaben noch immer ganz gut geeignet sind.
Es waren 3 lange und anstrengende Tage. Aber es gab viel neu oder erneut zu entdeckten. Vieles was man schon kannte und einges was einfach interessant war.
Gerade die eher nicht technischen Themen wie Mob-Programming bei sipgate, "A Brief History Of the Internet" und UX waren sehr interessant und auch motivierend, selbst mal wieder mehr nach vorne zu gehen. Auch wurde sehr oft erwähnt, dass die Menschen, die Standards, Frameworks und eben die Dinge machen, mit denen man täglich arbeitet, auch nur ganz normale Entwickler sind und auch über jede Idee/Bugifx oder Mithilfe sehr dankbar sind.
Und man solle nicht alte Fehler wiederholen sondern neue machen. Mehr Zeit für Experimente und keine Idee ist schlecht zu der Zeit der sie entwickelt wird, weil sie immer ein Problem löst. Ob sich die Lösung später als eher schlecht heraus stellt (wie Flash oder Java-Applets) muss sich erst noch zeigen.
Das mit wichtigste Thema war PWA (an sich kaum unterschiedlich zu den OWAs auf Firefox OS nur jetzt mit breiter Unterstützung), um Websites direkt zu Apps machen zu können und eine Interaktion mit dem Device zu erlauben.
Daher wird mein neues Experiment:
- PWA
- Vue.js für das Frontend
- Bootstrap 4 als CSS-Framework
- Fetch-API anstelle von Axios
- Daten werden nur von den momentan Teilnehmenden Devices bereit gestellt
- Die Plattform selbst hat keine klassische Persistenzschicht wie eine DB sondern bildet nur einen State für alle Devices ab
- also gibt es nur Daten-Caching aber keine Langzeitspeicherung
- JWT für den Login auf der Singlepage-App
- Backend mit Meecrowave oder Spring Boot.. da bin ich mir noch unsicher.. aber auf jeden Fall Java
Nach dem Ich mich etwas mit Redux beschäftigt habe, habe ich mir mal einen eher aktuellen Spiele-Prototypen von mir vorgenommen und geguckt, ob man diesen auf ein ähnliches Konzept umbauen kann. Also dass alles per Actions gesteuert wird. Alles was man macht wird per Action rein gereicht, in einer Loop(Interval) von einem Handler verarbeitet und dann gerendert, wenn mindestens eine Action ausgeführt wurde.
Nach einer schnellen Betrachtung wurde aber klar, dass ich da nicht viel umbauen konnte, weil die Engine schon rein der Logik wegen so implementiert wurde. Ich hab es einfach so schon gemacht ohne groß drüber nachzudenken. Ich hatte zusätzlich noch Action-Groups eingeführt, die dafür sorgen, dass immer nur die erste Action einer Gruppe ausgeführt wird (um Actors sich über die Map bewegen zu lassen, wobei alle Schritte vorberechnet sind und nicht vor dem Schritt dieser erst berechnet werden muss). Es gibt zwei Lanes: fast und slow. "slow" für das Bewegen von Actors auf der Map und "fast" für Eingaben und Dinge die an sich in Echtzeit da sein müssen. Ein kleine Verzögerung ist aber ok.
addIntervalEvent: function(eventType, eventArgs, group, lane){
this.interval.queue.push({lane: lane ? lane : 'slow',
type: eventType, args: eventArgs, parent: this.interval,
group: group});
},
performIntervallTick: function(lane){
//filter to perform events of a lane and remove this performed events
let filterCheck = [];
let render = false;
let newQueue = this.interval.queue.filter(item => {
let res = true;
if(item.lane == lane){
if(this.eventHandling[item.type] && (item.group === null || !filterCheck[item.group])){
this.eventHandling[item.type](item.args, item.parent, this);
filterCheck[item.group] = item.group;
res = false; //was performed and is removed from the queue
render = true;
}
}
return res;
});
this.interval.queue = newQueue;
//using the same method to trigger field-events
if(render){
this.globalKeyListener({keyCode: 0}, true);
this.renderViewport();
}
},
Hier bei ist der Controller der State und wird durch die Actions verändert. Eine Action wird mit einer Veränderung des State gleichgesetzt, da einmal oder zwei Rendern ohne das es nötig gewesen wäre in der Gesamtheit nichts ausmacht.
Was ich dabei gelernt habe ist, dass es schwer ist in JavaScript anders zu arbeiten, wenn man die Verarbeitung und die Ausgabe von einander entkoppelt, was gerade bei Anwendungen mit Grafik schwer ist nicht zu tun.
Heute saß ich vor der Dokumentation zu den neuen Hooks bei React und las mit durch welche Probleme die alle lösen sollen. Indirekt wurde gesagt, dass sie Redux unnötig machen sollen.. also war für mich die Zeit gekommen mir wirklich mal Redux anzusehen. Erstmal zu verstehen, was es tut, ist nicht ganz so einfach, wenn man sich deren Doku durchliest. Was gefühlt aber nur an deren Doku liegt, die viel zu zerstückelt ist, um einen schnellen Überblick zu bekommen.
Ich habe mit Hilfe von anderen Seiten dann heraus bekommen. Dass Redux einen State pro Reducer hält und der alle Actions bekommt und dann mit eigener Logik, die für ihn interessanten Actions ab arbeitet. Alter State geht rein und neuer geht raus. Eine Action hat einen Typ und ein Payload. Wenn sich der State ändert, wird etwas getriggert. Aber ich fand, dass doch für einfache Beispiele zu viel Code da war um es zu verstehen.
Um also das Prinzip von Redux zu verstehen habe ich dann mal 20min investiert und mir ein ähnliches Kontrukt geschaffen. Etwas Topic orientierter mit den States, so dass man die Reducer pro Action hat, aber vom Prinzip ist es alles relativ einfach und das Bootstrapping wurde daruch auch übersichtlicher.
Die meisten Beispiele zu Redux bestehen sowie so zu 50% aus React. Aber ich wollte ja Redux verstehen und nicht Redux+React (ja.. die gibt es wirklich auf getrennt von einander!)
Lief erstaunlicher Weise direkt wie es sollte (nach dem ein kleiner Fehler bei einer falschen Variablen behoben war).
Damit sollte man durch aus das gesamte Statemanagement einer Componente abbilden können. Wenn man nun noch Events verwenden würde, geht es schon stark in Richtung Reactive-Programming.
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.
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!
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.
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.
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.
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.
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.
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.
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.
"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!
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).
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.
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.
@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.
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.