Blog: Latest Entries (15):



Ender 3: Spulenhalterung mit Kugellagern

Wer mal gesehen hat, wie viel Kraft der Einzugsmotor braucht, um etwas Filament von der Spule abzurollen, gerade wenn etwas Dreck sich abgelagert hat, wird sich gedacht haben: Warum nichts wo es einfach leicht geht.. also eine stink normale und passende Halterung, wie man sie sonst so kennt.

Hier gibt es eine, die ich mir dann auch ausgedruckt habe.

bbcode-image


Nun rollt die Spule geschmeidig, ruhig und leise sich ab und der Motor muss nicht mehr groß dran ziehen. Außerdem verdreckt kaum noch etwas dabei.

bbcode-image


API Platform: Workflows und Resourcen erweitern

In den wenigsten Fällen, nutzt meine REST-API für einfache CRUD-Operationen. Oft muss man auf die Operationen auch reagieren. Z.B. will man, wenn eine neue Bestellung angelegt wird, auch eine Lager-Reservierung oder einen Export an das ERP anstoßen. Oder man hat Felder die übersetzt werden müssen. Auch will man bei bestimmten Resourcen wie Artikeln noch Metriken mitgeben, wie oft er verkauft wurde und ob er damit zu einem Top-Seller wird.

Für diese Anwendungsfälle stellt API Platform zwei verschiedene Konzepte zur Verfügung.

Data-Provider/Data-Persister
Wenn man wirklich Daten an der Resource ändern möchte, ist das hier der richtige Weg. Man greift hier direkt in die Persistierung ein. Der Data-Provider ist für das Laden und der Data-Persister, wie der Name schon sagt, für das Speichern zuständig. Damit man nicht die gesamte Grundlogik jedes Mal neu schreiben muss, kann man hier die vorhandenen Services dekorieren und den Ursprünglichen Service als Parent per Injektion mit rein reichen. Also klassisches Symfony. Wenn nun also Custom-Fields oder eine Sprache in der übergebenen Resource (in transient Fields) definiert sind, kann man die im Persister sich heraus picken und selber sich um die Speicherung
kümmern. Im Provider eben das selbe nur anders herum.

Nun kann man sich denken: "Ich will den Artikel mit seinen Änderungen gerne in einem ElasticSearch-Index haben, damit die Suche auch gleich aktuell ist." Aber dafür ist das der falsche Weg.

Events
Wie in Shopware sind hier Events der richtige Weg. Man greift dabei nicht in den Peristierungsvorgang ein und es kann weniger schief laufen. Jeder Subscriber arbeitet unabhängig und wenn einer fehlschlägt, sind die anderen Subscriber nicht eingeschränkt. Gerade bei den POST_WRITE-Events erhält man eine Kopie der Resource und sollte diese nicht ändern. Falls irgendwie der Entity-Manager in Zusammenhang mit der Resource verwendet wird.. sollte man zu den Persistern wechseln. Events nutzen nur die Daten und Ändern sie nicht. XML-Exports, Emails, Metriken... genau hier sind Events die ideale Lösung.

Wie in Shopware!

IR-Kamera DIY

Ich hatte bei einer USB-Kamera mit M12-Objektiv ja den IR-Sperrfilter vom Objektiv entfernt, um ein scharfes Bild zu bekommen. Dadurch hatte ich zwar Falschfarben im Bild, aber erst so wurde die Kamera brauchbar.

Nun habe ich mal getestet wie sich die Kamera zusammen mit einem IR72 Filter verhält. Also so zusagen als reine IR-Kamera. Das Bild ist erstaunlich gut und sollte in voller 720p Auflösung nochmal sehr viel besser sein.

bbcode-image


Man kann sich also relativ günstig eine IR-Kamera für verschiedene Zwecke bauen.

Ender 3: Under-Extrusion, Klick, Klack.. Knubbel

Wenn der Druck so aussieht, dann kommt zu wenig Filament aus der Nozzle. Zu wenig ist noch schlechter als zu viel, was man Over-Extrusion nennt.

bbcode-image


Dazu kommt ein Klicken oder Klacken, wenn der Motor versucht mehr Filament nachzuschieben und es nicht geht. Der Motor springt wieder zurück und oft rutscht wieder ein bisschen Filament zurück aus dem Schlauch. Wenn jetzt nicht das Filament schlecht aufgewickelt ist auf der Spule oder sogar sich verhakt hat, dann liegt es vielleicht an einer verstopften Nozzle.

Bei mir war es jeden Falls so und die Ersatz-Nozzle hat geholfen, da solche Nozzles auch so gut wie nichts kosten, ist das säubern in härteren Fällen auch überflüssig. Jetzt läuft der Druck wieder normal und Oberflächen sind auch wieder geschlossen und nicht löchrig.

bbcode-image


Nun geht es darum das Abrollen des Filaments zu verbessern. Dieses Plastik auf Plastik halte ich für zu schwer gängig... ist mein Gefühl. Wenn dann alles gut läufig, wäre eine Kamera (kleine IP-Kamera) zum Überwachen des Drucks noch sehr schön.


cJSv2: Services und Service-Injection

Ich hab cJSv2 jetzt soweit erweitert, dass man nun sich einfach in verschiedene Services in seine Controller injecten lassen kann (und ist dann unter seinem Namen oder einem Alias dort direkt aufrufbar). Jeder Service ist Singleton, so dass jeder Controller auf die selbe Instance des Services zugreift.
An sich wird damit die cjsGetController Methode, mit der sich Controller untereinander mit ihren Namen konnten, überflüssig und man kann einfach asynchrone/reactive Lösungen basteln.

Hier erst einmal ein kleines synchrones Event-System:


function EventBus() {
this.events = [];

this.register=function(event, callback){
if(!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}

this.fire=function(event, payload){
if(this.events[event]) {
this.events[event].forEach(subscriber => {
subscriber(payload);
})
}
}
}

function Receiver(){
this.value="";

this.init=function(){
this.EventBus.register("update", (payload) => {
this.value = payload.value;
});
this.cjsPushBindings();
};
}

function Sender(){
this.values=[
'Blubb',
'Test',
'Brumm',
'Event',
'Something'
];

this.send=function(event){
this.EventBus.fire("update", {value: this.values[Math.floor(Math.random() * this.values.length)]})
};
}



<div cjs-controller="sender:Sender" cjs-services="EventBus">
<input type="button" value="fire event" cjs-binding-event="click:send"/>
</div>
<div cjs-controller="receiver:Receiver:init" cjs-services="EventBus">
<input type="text" value="" cjs-binding-value="value"/>
</div>



Werte und Qualität - Eine generische Betrachung

Irgendwann kommt es in jeder Firma zu dem Zeitpunkt an dem über Werte, Qualität gesprochen wird und wie man diese selbst leben kann. Bei allen die direkten Kundenkontakt haben ist es immer einfach: Nett sein, schnell helfen und auf die Bedürfnisse der Kunden eingehen. Bei einem Entwickler der vielleicht sogar nur Backend-Services für Firmen internen Software schreibt, ist es weniger einfach zu definieren, wie man die Werte leben soll und wie Qualität erzeugt werden kann.

Als erstes: Was ist ein Software Entwickler eigentlich? Dabei ist es glaube ich falsch sich auf die Tätigkeit zu beschränken. Viel mehr geht es um den Zweck. Software hat keinen Selbstzweck. Software ist nicht da, weil sie Software ist. Jede Software hat einen Zweck und dieser Zweck ist gerade im Firmenumfeld ein Problem zu lösen. Wenn alles schon perfekt wäre bräuchte man keine neue Software und keine Entwickler mehr. Der beste Entwickler ist der, der sich durch seine Software selbst überflüssig macht.
Software ist dabei auch nicht die Lösung, sondern das Werkzeug um zur Lösung zu kommen. Ein Software-Entwickler entwickelt Lösungen, die mit Hilfe von Software herbei geführt werden.

Wenn wir das Einhalten der Werte und der Qualität (was Qualität ist nicht zu diskutieren, sondern in den ISO-Normen festgelegt!) als die großen Probleme der Firma sehen, ist die Software natürlich nicht die Lösung, weil keine Firma 100% automatisiert ist. Wir müssen hier in Schichten denken. Für die Mitarbeiter, die Kundenkontakt haben oder die Produkte für die Kunden verwalten ist unsere Lösung deren Werkzeug. Wir lösen das Problem, das besteht, dass die ein größeres und abstrakteres Problem lösen können. Das macht die Software nicht weniger bedeutend, denn die Basis muss stimmen, damit die großen Probleme überhaupt bewältigt werden können. Der Software-Entwickler ermöglicht es dem anderen Mitarbeiter erst effektiv arbeiten zu können.

bbcode-image


Jetzt mal konkreter, damit man sich nicht anfängt dabei m Kreis zu drehen. Welche Werte sollte ein Entwickler bei seiner Software anwenden, damit z.B. ein Mitarbeiter im Kundensupport seinen Job gut erledigen kann und der Kunde am Ende zufrieden und sicher verstanden fühlt?

Der Mitarbeiter sollte schnell alle Kundendaten sehen und bei Abfragen, sollte das System nicht einfach warten, sondern eine Meldung ausgeben, die der Mitarbeiter dem Kunden auch mitteilen kann, damit Wartezeiten erklärbar werden. Aber an sich sollte dieses nie nötig sein, weil Antwortzeiten immer verlässlich sind. Nennen wir diesen Wert der Software "responsive".

Fehler sind menschlich und unter Stress unvermeidbar und häufig. Wenn ein Kunde berechtigt oder unberechtigt Druck macht und etwas schnell gehen soll, sollte bei Fehlern immer eine brauchbare Fehlermeldung kommen oder auch versucht werden möglichst viel automatisch zu korrigieren. Niemals sollte wegen einer formal ungültigen ein Formular abstürzen, nicht funktionieren oder es erzwingen Daten erneut eingeben und damit erneut vom Kunden erfragen zu müssen. Wenn man zum dritten mal nach seiner Telefonnummer gefragt wird, wird das Benutzererlebnis echt schlecht. Nenne wir diesen Wert der Software "widerstandsfähig" oder "resilient".

Wenn Fehler in Systemen auftreten, kommen meistens viele Kundenanfragen auf einmal. Keiner will hören, dass es heute etwas länger alles dauert, weil das System durch die vielen Anfragen langsam ist. Gerade bei Sonderangeboten und überlasteten Systemen kann es sehr negativ auffallen, wenn ein Kunde etwas nicht bekommt, weil das System plötzlich nicht mehr reagiert. Systeme sollten sich schnell anpassen.
Wenn nun plötzlich 100 Kunden anstelle von 10 Kunden bedient werden müssen.. dann sollte alles noch genau so schnell und stabil sein wie sonst. Nennen wir diesen Wert der Software "elastisch", weil es flexibel Resourcen nutzen kann, wenn nötig.

Diese drei Werte kann man sehr gut auf die Meta-Begriff: Schnell, Kompetent und Zuverlässig übertragen.

Bestimmt kommen diese drei "Werte" den meisten schon aus dem Reactive Manifesto bekannt vor. Ich finde dieses orientiert sich so sehr an der alltäglichen Realität, dass es sich die Ansätze mehr als einfach nur ein Idee für gute Software-Systeme sind. An sich lässt sich damit alles beschreiben, wo Kommunikation stattfindet.

Dann gibt es noch so Werte, die an sich keine Werte sondern Selbstverständiglichkeiten oder auch gesetzliche Vorgaben sind. Z.B. Freundlichkeit. Egal ob eine andere Abteilung, ein Kollege oder ein Kunde eine Frage oder ein Problem hat, ist man freundlich. Niemand will nerven oder versteht Erklärungen aus Vorsatz nicht. Man muss natürlich auch bestimmend sein, wenn man z.B. an wichtigen Dingen sitzt und die Anfrage von der Priorität niedrig ist. Erklären und eine feste Zeit oder Einordnung geben. Man kümmert sich und der andere fühlt sich verstanden. Das ist auch Freundlichkeit.. und kompetentes Auftreten.

Und dann gibt es Datenschutz. Ist das ein Wert? Es ist gesetzlich vorgeschrieben. Ich glaube es sollte doch als Wert geführt werden, weil man sollte es leben und nicht nur ausführen, weil man es muss. Es macht alles einfacher, wenn Datenschutz gleich mit einfließt und nicht von einer anderen Abteilung später erzwungen wird. Don't be evil ist immer ein guter Ansatz!

Ich hoffe damit hab ich ein paar Ansätze und Denkanstöße geben können, womit ein Entwickler beim nächsten mal, wenn er nach Werten und Qualität gefragt, ein kleines Set an Werkzeugen an der Hand hat, um die Frage schneller beantworten zu können.


PHP API Platform: Speed run

Die Zeiten wo man eine API mit:

echo json_encode($result);
die();

in einem einfachen Controller implementierte und eher ein Beiwerk waren sind schon länger vorbei. Heute baut große Clients mit Vue.js oder React und PHP liefert keinen HTML-Code mehr aus sondern implementiert nur noch eine reine REST-API.

Bei REST-APIs mit klassischen Resources und GET, PUT, POST, PATCH, DELETE kann man alles sehr gut verallgemeinern, so dass ein Framework kaum noch Anpassungen benötigen, um eine lauffähige API bereit stellen zu können. Eines dieser Frameworks ist das in PHP implementierte API Platform. Zusammen mit Symfony 4 kommt noch schneller zu einer REST-API als mit
Spring-Boot oder Meecrowave.

Ich skizziere hier einen kleinen Speed run, der aufzeigt wie man sehr schnell eine recht komplexe REST-API implementieren kann.
Nicht alles wird für alle nötig sein, aber es gibt einen guten Überblick für eigene Projekte. Ich geh von einem vorhandenen Symfony 4
Projekt aus, in das API Platform mit integriert wird.

1. Installation:

composer require api


2. Die Resource:
Im Grunde nimmt man eine Doctrine-Entity und erweitert die um 2 Annotationen (ApiResource und ApiProperty für die Id).


namespace hp\examples\api\entity;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;

/**
* Example-Resource
*
* @ORM\Entity
* @ORM\Table(name="blubb")
*
* @ApiResource(
* )
*/
class Blubb {
/**
* @var int
*
* @ORM\Column(name="id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*
* @ApiProperty(
* identifier=true
* )
*/
private $id;

/**
* @var string
*/
private $value = ''

....
....
....
}


Den Rest der Entity wie einen argumentlosen Constructor und die Getter/Setter wird sicher jeder selbst schnellst erzeugen können.

Eine Migration oder die Table per Doctrine zu erzeugen kann sicher auch jeder selber und braucht hier nicht weiter ausgeführt werden.

3. Pfad zu den Resourcen:

Hier passen wir das Paths Field in config/packages/api_platform.yaml an:

api_platform:
mapping:
paths: ['%kernel.project_dir%/src/examples/api/entity']
patch_formats:
json: ['application/merge-patch+json']
swagger:
versions: [3]


Nun weiß API Platform, wo es nach den Resources suchen soll.

4. URL-Prefix anpassen:
In der config/routes/api_platform.yaml kann noch schnell ein Prefix für die API bzw den API-Controller gesetzt werden.


api_platform:
resource: .
type: api_platform
prefix: /api/examples/


5. Starten und Testen:

Nun kann die Anwendung auch schon gestartet werden, wenn sie nicht schon ist. Also in meinem Fall den Docker-Container starten, der einen Apache auf dem Port 8080 öffnet.

Ob die Routen vorhanden sind prüfen wir mit:

php bin/console debug:router


Wenn die Routen angezeigt werden kann http://localhost:8080/api/examples/docs.json aufgerufen werden und hier sollten alle Pfade zu der Resource aufgelistet sein. Sollte die Route nicht gefunden werden hilft ein php bin/console cache:clear. Um die Resource wirklich auch testen zu können, findet man eine Swagger-Umgebung unter http://localhost:8080/api/examples/blubbs.html. Wie man sieht hängt ein 's' am dem Namen der Klasse und der Name ist auch nicht in Camelcase geschrieben. Wenn man die JSON-Ausgabe sehen möchte muss man nur ../blubbs.json anstelle der HTML-Variante aufrufen.

6. DTOs und Delegations
Nicht immer will man die Entity so raus reichen, wie sie ist. Auch will man vielleicht Objekte durch einfache Strings ersetzen. Auch beim Anlegen will man vielleicht Business-Keys und keine echten Ids nutzen, die aber in der Ausgabe dann doch zusätzlich mit angezeigt werden sollen.
Dafür kann man bei API-Platform DataTransformer und Stellvertreter-Objekte definieren. Die DataTransformer muss man nur anlegen und werden durch das autowire von Symfony direkt auch schon verwendet.

Ein einfaches Beispiel:

BlubbIn

namespace hp\examples\api\entity;

class BlubbIn {
public $value;
public $logComment;
}


BlubbOut

namespace hp\examples\api\entity;

class BlubbOut {
public $id;
public $value;
public $md5sum;
}


Nun brauchen wir auch zwei DataTransformer:

Incoming

namespace hp\examples\api\entity\transformer;

use ApiPlatform\Core\DataTransformer\DataTransformerInterface;

class BlubbInTransformer implements DataTransformerInterface {
public function transform($object, string $to, array $context = []) {
$entity = new Blubb();
$entity->setValue($object->value);

\hp\Logger::log($object->logComment);
return $entity;
}

public function supportsTransformation($data, string $to, array $context = []): bool
{
return $to === Blubb::class && null !== ($context['input']['class'] ?? null);
}
}


outgoing

namespace hp\examples\api\entity\transformer;

use ApiPlatform\Core\DataTransformer\DataTransformerInterface;

class BlubbOutTransformer implements DataTransformerInterface {
public function transform($object, string $to, array $context = []) {
$dto = new BlubbOut();
$dto->id = $object->getId();
$dto->value = $object->getValue();
$dto->md5sum = md5($object->getValue() . $object->getId());
return $dto;
}

public function supportsTransformation($data, string $to, array $context = []): bool
{
return $to === BlubbOut::class && $data instanceof Blubb;
}
}


Die beiden DTOs müssen noch an der Resource referenziert werden:

...

/**
* Example-Resource
*
* @ORM\Entity
* @ORM\Table(name="blubb")
*
* @ApiResource(
* input=hp\examples\api\entity\BlubbIn::class,
* output=hp\examples\api\entity\BlubbOut::class
* )
*/

...


Nun nutzt man das BlubbIn-Format um etwas an die API zu senden, es wird als die Entity in de DB gespeichert und als Rückgabe sieht die Daten im Format von BlubbOut. Dieses ist etwas, was ich bei vielen Frameworks immer vermisst habe... Object-Replacements wie beim Serialisieren unter Java mit writeReplace. Gerade bei REST-APIs ist so etwas extrem praktisch und man kann so auch aus einer Entity verschiedene Domain-abhängige (Bounded-Context) DTOs für verschiedene API-Endpoints erzeugen.

7. Authorization und Security
Klar sind einfache API-Keys die man im Request mitgibt nicht die beste oder finale Lösung, aber sie sind so schön einfach. In API Platform lässt sich so etwas relativ einfach implementieren. Dafür braucht man Symfony Voters, die mich etwas an die Realms aus Tomcat erinnern. Man reicht eine Role rein und die Logik liefert einfach true oder false zurück. Das Beispiel ist nicht sehr fein-granular, aber sollte reichen, um die Grundidee zu vermitteln.

Die Resource schützen:

...

/**
* Example-Resource
*
* @ORM\Entity
* @ORM\Table(name="blubb")
*
* @ApiResource(
* attributes={"security"="is_granted('EXAMPLE_API_ACTION')"},
* input=hp\examples\api\entity\BlubbIn::class,
* output=hp\examples\api\entity\BlubbOut::class
* )
*/

...


Eine ausführlichere Doku mit der Verwendung des Object findet man hier bei API Platform.

Den Voter zu implementieren ist an sich genau so einfach, wie einen DataTransformer zu implementieren und funktioniert genau nach der selben Logik, die ich auf die Art auch immer gerne implementiere.


namespace namespace hp\examples\api\security;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

class ExampleAPIVoter extends Voter {

...
...
...

protected function supports($attribute, $subject) {
return $attribute == 'EXAMPLE_API_ACTION';
}

protected function voteOnAttribute($attribute, $subject, TokenInterface $token) {
return $this->apiKey == $this->getUserRequestApiKey();
}
}


Mit autowire muss man auch nicht groß etwas in die services.yaml eintragen, wenn man nichts aus der env-config oder
so braucht.

Feature-Flags: Neue Features, neue Versionen und alles beim Alten

Gerade bei den Shopware Plugins war es wichtig, dass sich die Funktionsweise eines Plugins nicht einfach ändert. Ich hab erlebt was passieren kann, wenn sich bei einen Newsletter-Plugin in einer Minor-Version plötzlich das Verhalten änderte. Es wurden plötzlich alle Kunden mit Gruppe übertragen und nicht mehr nur die einer bestimmten Gruppe und weil niemand sich die Änderungen groß angesehen hat, bekamen plötzlich alle den Newsletter. Das war doof.

Kaum jemand liest den Changelog oder nur selektiv nach bestimmten Fixes. Wenn ich als Shopware-Betreiber einen Fix für ein Plugin brauche, habe ich meistens keine Zeit jedes Verhalten und jede Funktion des Plugins nochmal durch zu testen. Ich will nur mein Problem beheben und keine neuen bekommen. Jedes neue Feature muss also in den default Einstellungen erst einmal deaktiviert sein. Nur Fixes dürfen ungefragt greifen.

Funktionen im Beta-Stadium dürfen auch nicht aktivierbar sein, wenn sie nicht stable sind. Unfertiger-Code darf mit ausgeliefert werden, wenn es nicht anders geht! ABER er muss auch hart im Code deaktiviert sein. Oder auch im Build-Prozess aus der Config-XML auskommentiert werden. Da muss dann aber sichergestellt sein, dass geprüft wird, ob das Config-Value überhaupt gesetzt ist und wenn nicht, dass es als deaktiviert gilt.
Aber wenn es nicht zwingend nötig ist, sollte man unfertige Features nie mit ausliefern und per Feature-Flag deaktivieren. Am Ende wird es sicher noch mal überarbeitet und es funktioniert dann anders. Weglassen minimiert das Risiko auf Fehler und dass irgendwer es doch aktiviert und nutzt.

Fazit: Feature-Flags sind wichtig, aber nicht dafür da um unfertige Features mit ausliefern zu können (ein Feature pro Feature-Branch!!!!)


Older posts:

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