selectline-software / selectline-api

Apache License 2.0
26 stars 5 forks source link

Internal Server Error bei documents/../print bei mehreren Requests #312

Closed AM-Sartissohn closed 1 year ago

AM-Sartissohn commented 1 year ago

Details

Steps to replicate the behavior:

Leider ist dieses Problem nicht direkt reproduzierbar, sondern scheint wieder ein Timing-Konflikt im Backend zu sein. Zum Hintergrund:

Wir lösen mit externen Skripten über die API diverse Druckaufträge aus, dabei können auch mehrere Skriptaufrufe direkt nacheinander ausgeführt werden. Die Skripte führen im Einzelnen folgende Schritte aus:

Hintergrund ist, dass über eine Liste der Aufträge gesteuert wird, welche Rechnungen aktuell archiviert und daraus versendet werden sollen. Die Kundenstammdaten müssen ausgelesen werden, um über Extrafelder die benötigte Druckvorlage zu ermitteln. Der Mandantenwechsel dient hier nur zur Sicherheit, alle Belege befinden sich im gleichen Mandanten.

Das Skript funktioniert grundsätzlich in einzelnen Tests. Wenn wir jedoch das Skript für mehrere Belege nacheinander laufen lassen, schlägt der print-Request teilweise mit einem Internal Server Error fehl. Wenn wir das Skript später für die betroffenen Belege noch einmal starten, laufen alle Requests ohne Probleme durch - vermutlich gibt es hier ein Timing-Problem.

Insgesamt ist das Skript in diesem Fall für 55 Belege durchgelaufen, von denen 6 in Fehlermeldungen gelaufen sind. Das Problem tritt also nicht vereinzelt auf, sondern schon bei einer geringen Menge an Requests.

Für den betreffenden Tag gibt es nur Logeinträge für API.Backend und WebApi, diese sind angehängt. Die Zeitstempel passen zu den aufgetretenen Fehlern. SelectLine.Erp.WebApi.WebHost.log SelectLine.API.Backend_2023_01_27_15_33_37.log

Micha-Richter commented 1 year ago

Wenn ihr das Skript für mehrere Belege nacheinander laufen lasst, dann wartet der zweite Skriptdurchlauf nicht darauf, dass das erste Durchlauf vollständig abgearbeitet ist? Habe ich das richtig verstanden?

Falls es so sein sollte: versucht mal, die Skriptausführungen zu serialisieren, so dass eins nach dem anderen abgearbeitet wird (innerhalb des Skripts müsst ihr das ja sowieso, weil ihr Zwischenergebnisse weiterverwenden müsst!?). Ich halte die API in Bezug auf gleichzeitige Requests für mehr als "wacklig".

Micha-Richter commented 1 year ago

Aber wenn ihr innerhalb des Skripts das Login ausführt, dann könnt ihr das Skript nicht mehrfach gleichzeitig laufen lassen!? Allerdings sollte dann eher ein Access-Fehler kommen, weil sich die LoginId "hintenrum" geändert hat.. 🤔

AM-Sartissohn commented 1 year ago

Dass die API mit parallelen Requests Probleme hat, kenne ich auch schon. Innerhalb des Skripts werden die Requests serialisiert abgesetzt, ja. Eigentlich sollten auch die Skriptaufrufe für die Belege sequentiell abgearbeitet werden - die Aufrufe werden durch das Archivsystem ausgeführt, da kenne ich den Code nicht genau, gehe aber davon aus, dass dies sequentiell ist.

Und ja, da wir in jedem Skriptaufruf eine neue LoginId holen, muss der Abruf eigentlich sequentiell erfolgen - andernfalls hätten wir ja Requests mit ungültigen LoginIds, und das können wir nicht beobachten.

Micha-Richter commented 1 year ago

Kann es sein, dass das Drucken ins Archivsystem manchmal zu lange dauert?

"2023-01-27T14:34:34.463Z"
"Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding."

Ansonsten sieht

"2023-01-27T14:33:02.0648309Z"
"Exception": {"Message":"Client.Send failed. See InnerException","InnerException":{"Message":"Can't deserialize message into FriendClientMessage","InnerException":{"Message":"Der Wert darf nicht NULL sein.\r\nParametername: FriendClientMessage"

für mich komisch aus, vielleicht klingelt da bei einem der Entwickler was.

Mehr fällt mir aber nicht mehr ein.. Da kann wohl nur noch SelectLine selbst helfen. Good luck! 🙏

MatthiasGuse commented 1 year ago

Hallo,

meine Empfehlung wäre eher, für jeden Mandanten einen eigenen API User anzulegen, der gleich dem Mandanten zugeordent ist. Erstmal erspart man sich damit den Schritt des Mandantenwechsels und wird dadurch auch an Performance gewinnen, da das Backend nicht erst den Mandantenwechsel durchführen muss und bereits auf ein laufendes Backend des Mandanten zugreifen kann.

Also die beiden Schritte:

Meine Vermutung ist, dass genau der Mandantenwechsel zu dem beschriebenen Timeout führt, da der Druck noch während des im Hintergrund laufenden Mandantenwechsels angestoßen wird.

Viele Grüße

Micha-Richter commented 1 year ago

Aber dann sind wir wirklich bei "COM 2.0", Matthias... Ich muss mich darauf verlassen können, dass intern alles abgeschlossen ist, wenn der API-Call (hier der Mandantenwechsel) zurückkommt! Sonst fange ich im API-Client wie anno dazumal an, Leerrunden zu drehen..

(und ganz nebenbei: woher soll ich dann von außen erkennen, dass ich die API jetzt wieder ansprechen kann?? Was passiert bei mehr als 1 Client, wie soll der zweite das mitkriegen?? usw.)

Ich weiß, dass du nichts dafür kannst, aber das ist peinlich. Kannst du gern weitergeben.

AM-Sartissohn commented 1 year ago

Hallo, da muss ich Micha-Richter recht geben. Wenn die Response auf den API-Call mit einem OK zurückkommt, muss die API bereit sein, den nächsten Request zu verarbeiten. Andernfalls kann ich mich auf die Response ja gar nicht verlassen. Ich kann jetzt natürlich versuchen, zwischen alle Requests jeweils noch eine Wartezeit einzuschieben, aber das ist ja auch nur wieder Flickwerk...

Grundsätzlich sollte der Mandantenwechsel aber gar kein Problem sein, da wir den Mandanten tatsächlich nicht wechseln - alles spielt sich im gleichen Mandanten ab, wir haben also einen API-Benutzer pro Mandant. Der PUT /users/current dient nur dazu, sicherzustellen, dass tatsächlich der korrekte Mandant ausgewählt ist, weil man den zugeordneten Mandanten ja auch über die SL Mobiles-Benutzerverwaltung ändern kann. Es sei denn, der Request kann selbst dann zu einem Timeout führen, wenn der bereits ausgewählte Mandant übergeben wird - ist das möglich?

MatthiasGuse commented 1 year ago

Sorry! Ich wollte nur helfen... 🤯

Zugegeben ist das verbesserungswürdig. Ich habe bereits auf dem Plan beim Login direkt den Mandanten mitzugeben. In dem Zusammenhang könnten wir das so angehen, dass erst ein OK kommt, wenn das Backend auch "fertig" ist. Mal gucken ob ich damit durchkomme. 🤔

Und ja, auch das erneute "Auswählen" des bereits benutzen Mandanten führt zu einer Verzögerung im Backend, weil der Mandant neu eingelesen wird. Das kann man ganz einfach nachvollziehen, indem man das in der Wawi mal ausprobiert.

Grüße

konrad79 commented 1 year ago

@Micha-Richter Peinlich ist das nun nicht. Ungünstig vielleicht. Aber dass die API in ständiger Evolution ist, wissen wir alle.

Ich vertrete den Standpunkt, dass jeder API-Call sofort eine Antwort verdient hat. Wenn dahinter einer größere Operation steckt, die Zeit kostet, dann hat die API dennoch sofort eine Antwort zu liefern ala "Operation gestartet". Natürlich muss man dann entsprechende Statusfunktionen zur Verfügung stellen, die es dem Client ermöglichen, durch regelmäßiges Polling den Status der Operation abzufragen. Ob diese Operation dann ein Mandantenwechsel, der Druck eines riesigen PDFs oder noch größere Dinge sind, ist dabei egal.

Da wir das aber derzeit so nicht anbieten bin ich bei Matthias: Nehmt euch einfach pro Mandant einen APIUser.

Micha-Richter commented 1 year ago

Sorry! Ich wollte nur helfen... 🤯

Das wissen wir alle, @MatthiasGuse . Danke dafür! 👍

Und ja, auch das erneute "Auswählen" des bereits benutzen Mandanten führt zu einer Verzögerung im Backend, weil der Mandant neu eingelesen wird. Das kann man ganz einfach nachvollziehen, indem man das in der Wawi mal ausprobiert.

Das ist keine gute Begründung: beim Mandantenwechsel in der Wawi muss alles mögliche sofort neu eingelesen und im UI angepasst werden. Das dauert leider ein par Sekunden. In einer API ist das aber alles nicht notwendig, Mandantenwechsel heißt hier im Kern: "merk dir den ab jetzt gültigen Mandantennamen" (damit beim nächsten Aufruf der API die richtige Datenbank angesprochen werden kann). Das könnte (oder müsste!?) die schnellste API-Funktion der ganzen Schnittstelle sein.

@konrad79 : Wenn du die API so siehst, dann ist deine Handschrift in der API aber nicht zu finden.. von der Dokumentation, die diese Grundentscheidungen für externe Nutzer vielleicht dokumentieren sollte, ganz zu schweigen 🤷‍♂️ (über die Polling-Idee da oben möchte ich mich gar nicht auslassen, die bestärkt aber meinen "COM 2.0"-Vorwurf)

konrad79 commented 1 year ago

@Micha-Richter Den Vergleich mit einer COM-2.0 sehe ich gar nicht als Vorwurf. Du solltest es noch am besten wissen, dass unsere API keine schicke reine WebAPI auf der grünen Wiese ist, sondern im Hintergrund immer noch auf eine große OnPremise-Warenwirtschaft zugreifen muss. Dies zwingt uns gewissermaßen zu vielen Kompromissen, die die API dann nach außen hin vielleicht wirklich wie eine Weiterentwicklung der COM-Schnittstelle aussehen lassen.

Du kannst dir gewiss sein, dass wir ohne dass das jetzt für Entwickler wie dich schon groß sichtbar ist, im Hintergrund einiges dafür tun, das zu ändern. Und mal sehen - vielleicht haben wir dann eine API, die auch deinen Ansprüchen genügt :-)

Micha-Richter commented 1 year ago

... sondern im Hintergrund immer noch auf eine große OnPremise-Warenwirtschaft zugreifen muss. Dies zwingt uns gewissermaßen zu vielen Kompromissen, die die API dann nach außen hin vielleicht wirklich wie eine Weiterentwicklung der COM-Schnittstelle aussehen lassen.

Die API gibt's jetzt seit über 10 Jahren, @konrad79 🤷‍♂️😅

Du kannst dir gewiss sein, dass wir ohne dass das jetzt für Entwickler wie dich schon groß sichtbar ist, im Hintergrund einiges dafür tun, das zu ändern. Und mal sehen - vielleicht haben wir dann eine API, die auch deinen Ansprüchen genügt :-)

Notier mich als Beta-Tester, dann kann ich dir noch vor Auslieferung sagen, ob meine von dir vermuteten Ansprüche erfüllt sind 😉

konrad79 commented 1 year ago

Notier mich als Beta-Tester, dann kann ich dir noch vor Auslieferung sagen, ob meine von dir vermuteten Ansprüche erfüllt sind 😉

Bist notiert :-)

Die API gibt's jetzt seit über 10 Jahren, @konrad79 🤷‍♂️😅

Du hast nicht ernsthaft damit gerechnet, dass wir die Warenwirtschaft in 10 Jahren komplett neu schreiben und sie damit perfekt API-fähig machen. Michael, das ist ein Gutteil auch dein Code 😅

Micha-Richter commented 1 year ago

Die API gibt's jetzt seit über 10 Jahren, @konrad79 🤷‍♂️😅

Du hast nicht ernsthaft damit gerechnet, dass wir die Warenwirtschaft in 10 Jahren komplett neu schreiben und sie damit perfekt API-fähig machen. Michael, das ist ein Gutteil auch dein Code 😅

😂 Dass ich als Ausrede für den aktuellen Stand der API herhalten muss hätte ich mir auch nicht träumen lassen.. 🤦‍♂️

Es geht nicht um Perfektion und noch weniger um "komplett neu schreiben", aber der aktuelle Stand sollte dir schon zu denken geben. Die Issues der letzten Wochen sprechen nicht für die API.

konrad79 commented 1 year ago

Es geht nicht um Perfektion und noch weniger um "komplett neu schreiben", aber der aktuelle Stand sollte dir schon zu denken geben. Die Issues der letzten Wochen sprechen nicht für die API.

Die API ist nur so gut wie die darunter liegende Warenwirtschaft. Und die ist v.a. in Belegdingen verdammt gut. Halt nur technologisch nicht immer mit den modernen Anforderungen an eine API kompatibel. Und wir bauen da sehr viel, um sie dennoch kompatibel zu bekommen und Entwicklern wie dir eine komfortable Schnittstelle zu bieten. Nur sind es halt diese Unterschiede in den beiden technologischen Welten, die uns manchmal ein paar Kopfschmerzen bereiten ;-)

AM-Sartissohn commented 1 year ago

Ob eine API jetzt auf jeden Request unmittelbar eine Antwort zurückgeben oder besser warten sollte, bis der Server mit der Bearbeitung fertig ist, kann man sicherlich verschieden diskutieren. Mein Problem hier ist eher, dass die SelectLine API u.a. unter https://demo.slmobile.de/demoapi/help explizit als REST-Service bezeichnet wird - und REST beinhaltet auch Zustandslosigkeit.

Einzelne Ausnahmen wie die Mandantenauswahl (das Backend merkt sich, in welchem Mandanten der Benutzer grade angemeldet ist - also nicht zustandslos) mag es da aus technischen Gründen geben, aber diese Punkte sollten dann aus meiner Sicht explizit dokumentiert sein. Andernfalls kann das Ganze nicht als REST-Service bezeichnet werden, weil die Bezeichnung dann irrführend für den Entwickler ist.

ThePholph commented 1 year ago

... Die Kundenstammdaten müssen ausgelesen werden, um über Extrafelder die benötigte Druckvorlage zu ermitteln. Der Mandantenwechsel dient hier nur zur Sicherheit, alle Belege befinden sich im gleichen Mandanten....

Hallo,

ich bin beim Punkt "... um ... Druckvorlage zu ermitteln ..." aufmerksam geworden. Da hier evtl. nur gewisse Druckvorlagen ein Problem darstellen.

Ist geprüft worden, ob die Druckvorlagen und deren Druckziel korrekt konfiguriert sind und alle Drucker etc. für den Benutzer, der den Backend-Manager ausführt, eingerichtet sind.

Ein Beispiel: Wenn in einer Druckvorlage eingerichtet ist, dass für alle Arbeitsplätze und Benutzer der Drucker "XY" verwendet werden soll und dieser nicht für den ausführenden Benutzer des Backendmanagers eingerichtet ist (im Windows), bekommt man denselben Fehler in der API:

"Exception": {"Message":"Client.Send failed. See InnerException","InnerException":{"Message":"Can't deserialize message into FriendClientMessage","InnerException":{"Message":"Der Wert darf nicht NULL sein.\r\nParametername: FriendClientMessage"

grafik

Evtl. hängt es ja damit zusammen.

Viele Grüße

Daniel

AM-Sartissohn commented 1 year ago

Hallo,

vielen Dank, aber das kann ich als Fehlerquelle recht sicher ausschließen. Andere Belege werden mit der gleichen Druckvorlage ohne Fehler gedruckt. Wenn ich die Requests für die Belege mit Fehlermeldung nach einiger Zeit erneut absetze, wird die gleiche Druckvorlage wieder ausgewählt und diesmal auch korrekt gedruckt. Das ist also sehr sicher ein Timing-Problem.

Das Entfernen der Mandantenauswahl hat den Fehler tatsächlich seltener auftreten lassen, aber nicht vollständig behoben. Teilweise habe ich beim Login auch eine Fehlermeldung "Conflict" erhalten".

Mein Workaround ist tatsächlich, nach jeder Response der API einen Sleep von 2 Sekunden auszuführen, bevor der nächste Request abgesetzt wird. Das ist zwar nicht meine präferierte Lösung, aber immerhin treten die Fehler jetzt nicht mehr auf.

ThePholph commented 1 year ago

Hallo,

alles klar, war nur ein Ansatz, da der Fehler der gleiche ist.

Viele Grüße

Daniel

MatthiasGuse commented 1 year ago

Das Startverhalten und die Stabilität der API, insbesondere in Bezug auf die Backendprozesse, wurden optimiert und Fehler korrigiert. Sollten Sie Probleme mit der Verfügbarkeit des Backendmanagers haben, empfehlen wir Ihnen nach einem Update auf diese Version die Funktion „Datenbank einrichten“ im Mobile Manager auf der Seite „Datenbank“ erneut auszuführen.

Entgegen der oben getroffenen Aussagen, ist es nicht nötig nach einem Login oder Mandantenwechsel auf das Backend zu warten. Wenn das Backend bei Aufrufen einer Funktion noch nicht zur Verfügung steht oder noch nicht gestartet wurde, wartet die API bis das Backend antwortet und antwortet selbst erst dann, wenn die Verarbeitung im BAckend abgeschlossen ist.

Beste Grüße