Skip to content

29. Anwendungsaufgabe: Version 11

29.1. Einleitung

In früheren Versionen der Client-Server-Anwendung zur Steuerberechnung befand sich die Schicht [métier], die die Geschäftsregeln dieser Berechnung implementiert, auf der Serverseite. Nun soll sie auf die Clientseite verlagert werden. Was ist der Vorteil? Ein Teil der Arbeit, die bisher vom Server ausgeführt wurde, wird auf die Client-Seite verlagert. Stellt man sich die Situation vor, dass ein Server von N Clients abgefragt wird, so werden N geschäftliche Steuerberechnungen von den Clients durchgeführt. In früheren Versionen führte der Server diese N geschäftlichen Berechnungen durch. Da der Server die geschäftlichen Berechnungen nun nicht mehr selbst durchführt, kann er schneller auf seine Clients reagieren und somit mehr Clients gleichzeitig bedienen.

Die Client-Server-Architektur sieht nun wie folgt aus:

Image

  • Die Schicht [métier] [10] wurde auf dem Client als [12] dupliziert;
  • ein neues Skript [main2] [11] wurde auf dem Client hinzugefügt;

Der Web-Client hat zwei Möglichkeiten, die Steuer für die in [3] gefundene Steuerzahlerliste zu berechnen:

  • Verwendung der Methode aus der vorherigen Version. Dabei wird die Serverschicht [métier] [10] genutzt. Das Skript [main] wird diese Methode verwenden;
  • einfach die Daten der Steuerbehörde [2-4] vom Server anfordern und anschließend die clientseitige Ebene [métier] [12] verwenden;

Wir werden die Leistung der beiden Methoden vergleichen.

29.2. Der Webserver

Die Verzeichnisstruktur des Webservers sieht wie folgt aus:

Image

  • Der Ordner „[http-servers/06]“ wird zunächst durch Kopieren des Ordners „[http-servers/05]“ erstellt. Wir werden nämlich die Errungenschaften der vorherigen Version 10 beibehalten. Wir werden lediglich eine neue Funktion hinzufügen. Diese zeigt sich durch das Vorhandensein eines neuen Controllers [get_admindata_controller] [1]. Der andere Controller [calculate_tax_controller] ist nichts anderes als der alte Controller [index_controller], der umbenannt wurde;

29.3. Konfiguration

Der Server wird zwei URL-Dienste bereitstellen:

  • [/calculate-tax] zur Berechnung der Steuer für eine Liste von Steuerpflichtigen, die im Hauptteil eines POST übergeben wird. Er entspricht somit dem URL [/] der vorherigen Version 10;
  • [/get-admindata] liefert die Zeichenfolge jSON mit den Daten der Steuerbehörde;

Die Konfiguration [config] ordnet jede dieser URL dem Controller zu, der sie verarbeitet:


    # Wörterbuch der Controller der Webanwendung
    import calculate_tax_controller, get_admindata_controller
    controllers = {"calculate-tax": calculate_tax_controller, "get-admindata": get_admindata_controller}
    config['controllers'] = controllers

29.4. Das Hauptskript [main]

Das Hauptskript [main] strukturiert das Skript [main] aus der vorherigen Version neu:


# Es wird ein Parameter „mysql“ oder „pgres“ erwartet
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
    sgbd = sys.argv[1].lower()
    erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()

# Die Anwendung wird konfiguriert
import config
config = config.configure({'sgbd': sgbd})

# Abhängigkeiten


# Abruf der Daten von der Steuerbehörde
erreur = False
try:
    # „admindata“ wird ein schreibgeschützter Datensatz auf Anwendungsebene sein
    config["admindata"] = config["layers"]["dao"].get_admindata()
    # Erfolgsprotokoll
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    # Der Fehler wird protokolliert
    …

# Bei einem Fehler wird der Vorgang abgebrochen
if erreur:
    sys.exit(2)

# Die Flask-Anwendung kann gestartet werden
app = Flask(__name__)

# Hauptcontroller
def main_controller() -> tuple:
    # Die angeforderte Aktion wird abgerufen
    dummy, action=request.path.split('/')
    logger = None
    try:
        # Logger
        logger = Logger(config["logsFilename"])
        # wird in einer dem Thread zugeordneten Konfiguration gespeichert
        thread_config = {"logger": logger}
        thread_name = threading.current_thread().name
        config[thread_name] = {"config": thread_config}
        # Die Anfrage wird protokolliert
        logger.write(f"[index] requête : {request}\n")
        # Der Thread wird unterbrochen, falls dies angefordert wurde
        sleep_time = config["sleep_time"]
        if sleep_time != 0:
            # Die Pause erfolgt zufällig, sodass einige Threads unterbrochen werden und andere nicht
            aléa = randint(01)
            if aléa == 1:
                # Protokollierung vor der Pause
                logger.write(f"[index] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                # Pause
                time.sleep(sleep_time)
        # Die Anfrage wird vom Anfragecontroller ausgeführt
        controller = config['controllers'][action]
        résultat, status_code = controller.execute(request, config)
        # Ist ein schwerwiegender Fehler aufgetreten?
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            # Es wird eine E-Mail an den Anwendungsadministrator gesendet
            config_mail = config["adminMail"]
            config_mail["logger"] = logger
            SendAdminMail.send(config_mail, json.dumps(résultat, ensure_ascii=False))
        # Die Antwort wird protokolliert
        logger.write(f"[index] {résultat}\n")
        # Die Antwort wird gesendet
        print(résultat)
        return json_response(résultat, status_code)
    except BaseException as erreur:
        # Der Fehler wird protokolliert, sofern dies möglich ist
        if logger:
            logger.write(f"[index] {erreur}")
        # die Antwort an den Kunden wird vorbereitet
        résultat = {"réponse": {"erreurs"[f"{erreur}"]}}
        # Die Antwort wird gesendet
        return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
    finally:
        # Die Protokolldatei wird geschlossen, falls sie geöffnet war
        if logger:
            logger.close()

# Steuerberechnung
@app.route('/calculate-tax', methods=['POST'])
@auth.login_required
def calculate_tax():
    # die Kontrolle wird an den Hauptcontroller übergeben
    return main_controller()

# Daten von der Steuerbehörde abrufen
@app.route('/get-admindata', methods=['GET'])
@auth.login_required
def get_admindata():
    # Übergabe an den Hauptprüfer
    return main_controller()

# nur Übergabe
if __name__ == '__main__':
    # Server wird gestartet
    app.config.update(ENV="development", DEBUG=True)
    app.run(threaded=True)
  • Zeilen 88–93: Die Funktion [calculate_tax] verarbeitet die Skripte URL und [/calculate-tax];
  • Zeilen 95–100: Die Funktion [get_admindata] verarbeitet die Funktionen URL und [/get-admindata];
  • Diese beiden Funktionen führen selbst keine Aktionen aus. Sie übergeben die Kontrolle sofort an den Hauptcontroller [main_controller] in den Zeilen 37–86;
  • Zeilen 37–86: Der Hauptcontroller [main_controller] ist nichts anderes als die Funktion [index] aus der vorherigen Version, bis auf ein Detail: Während die Funktion [index] nur eine einzige URL verarbeitete, verarbeitet [main_controller] hier zwei URL. Daher muss sie diese von einem der beiden Controller [calculate_tax_controller, get_admin,data_controller] verarbeiten lassen;
  • Zeilen 39–40: Die angeforderte Aktion [calculate_tax] oder [get_admindata] wird abgerufen. Diese Information befindet sich im Pfad von URL [request.path]. Je nach Fall entspricht [request.path] entweder [/get-admindata] oder [/calculate_tax]. Die Aufteilung der Zeile 40 ergibt zwei Elemente:
    • die leere Zeichenkette für den Teil vor dem /;
    • den Namen der angeforderten Aktion für den Teil, der auf das „/“ folgt;
  • Zeilen 62–63: Sobald die Aktion von URL ermittelt wurde, ist bekannt, welcher Controller zur Verarbeitung von URL verwendet werden muss. Diese Informationen befinden sich in der Konfiguration von [config];

29.5. Die Controller

Der Controller [calculate_tax_controller] ist nichts anderes als der Controller [index_controller] aus der vorherigen Version.

Der Controller [get_admindata_controller] ist hingegen der folgende:


from flask_api import status
from werkzeug.local import LocalProxy

def execute(request: LocalProxy, config: dict) -> tuple:
    # die Antwort wird zurückgegeben
    return {"réponse": {"result": config["admindata"].asdict()}}, status.HTTP_200_OK
  • Der URL [/get-admindata] muss die Zeichenfolge jSON aus den Daten der Steuerbehörde zurückgeben;
  • Zeile 6: Diese wurden vom Hauptskript [main] abgerufen und in das Wörterbuch [config] in Form eines Objekts [AdminData] eingefügt. Das Wörterbuch dieses Objekts wird zurückgegeben;

29.6. Postman-Tests

Wir starten den Webserver, den SGBD und den Mailserver [hMailServer]. Anschließend berechnen wir mit einem Postman-Client die Steuer für mehrere Steuerzahler:

Image

In der Postman-Konsole sieht der Client-Server-Dialog wie folgt aus:


POST /calculate-tax HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 5e71461a-fec8-4315-85e8-41721de939e5
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 824

[ 
{ 
"marié": "oui", 
"enfants": 2, 
"salaire": 55555 
}, 

{ 
"marié": "oui", 
"enfants": 3, 
"salaire": 200000 
} 
]

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 1461
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 29 Jul 2020 07:02:07 GMT

{"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347…]}}

Nun wollen wir das URL [/get-admindata] mit einem GET abfragen:

Image

Der Client-Server-Dialog in der Postman-Konsole sieht wie folgt aus:


GET /get-admindata HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 4af342c4-7ecb-4ab2-9e12-d653f81da424
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

HTTP/1.0 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 596
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Wed, 29 Jul 2020 07:07:24 GMT

{"réponse": {"result": {"limites": [9964.0, 27519.0, 73779.0, 156244.0, 93749.0], "coeffr": [0.0, 0.14, 0.3, 0.41, 0.45], "coeffn": [0.0, 1394.96, 5798.0, 13913.7, 20163.4], "plafond_decote_couple": 1970.0, "valeur_reduc_demi_part": 3797.0, "plafond_revenus_celibataire_pour_reduction": 21037.0, "plafond_qf_demi_part": 1551.0, "abattement_dixpourcent_max": 12502.0, "plafond_impot_celibataire_pour_decote": 1595.0, "plafond_decote_celibataire": 1196.0, "plafond_revenus_couple_pour_reduction": 42074.0, "id": 1, "abattement_dixpourcent_min": 437.0, "plafond_impot_couple_pour_decote": 2627.0}}}

29.7. Der Webclient

Image

Image

Der Ordner „[http-clients/06]“ wird zunächst durch Kopieren des Ordners „[http-clients/05]“ erstellt. Die Anpassungsarbeiten bestehen im Wesentlichen darin:

  • die Konfiguration [config_layers] so anzupassen, dass sie nun eine Ebene [métier] enthält. Zuvor hatte sie nur eine Ebene [dao];
  • Hinzufügen einer neuen Methode zur Schicht [dao];
  • ein Skript [main2] zu schreiben, das auf der Client-Schicht [métier] basiert, um die Steuern der Steuerzahler zu berechnen;

29.7.1. Konfiguration der Client-Schichten

Die Konfiguration der Schichten erfolgt an zwei Stellen:

  • in der Konfiguration [config], die in den Abhängigkeiten des Clients den Ordner enthalten muss, der die Implementierung der Schicht [métier] enthält. Dieser Ordner war bereits in den Abhängigkeiten enthalten:

absolute_dependencies = [
        # Projektordner
        # BaseEntity, MyException
        f"{root_dir}/classes/02/entities",
        # InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
        f"{root_dir}/impots/v04/interfaces",
        # AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
        f"{root_dir}/impots/v04/services",
        # ImpotsDaoWithAdminDataInDatabase
        f"{root_dir}/impots/v05/services",
        # AdminData, ImpôtsError, TaxPayer
        f"{root_dir}/impots/v04/entities",
        # Konstanten, Tranchen
        f"{root_dir}/impots/v05/entities",
        # ImpôtsDaoWithHttpClient
        f"{script_dir}/../services",
        # Konfigurationsskripte
        script_dir,
        # Logger
        f"{root_dir}/impots/http-servers/02/utilities",
    ]

Anschließend muss die Datei [config_layers] geändert werden:


def configure(config: dict) -> dict:
    # Instanziierung der Anwendungsschichten

    # Schicht [métier]
    from ImpôtsMétier import ImpôtsMétier
    métier = ImpôtsMétier()

    # DAO-Schicht
    from ImpôtsDaoWithHttpClient import ImpôtsDaoWithHttpClient
    dao = ImpôtsDaoWithHttpClient(config)

    # Die Konfiguration der Schichten wird zurückgegeben
    return {
        "dao": dao,
        "métier": métier
    }
  • Zeilen 4–6: Instanziierung der Schicht [métier];
  • Zeilen 13–16: Die Schicht [métier] wird im Schichtwörterbuch zurückgegeben;

29.7.2. Implementierung der Schicht [dao]

Image

Die Schicht [dao] wird die folgende Schnittstelle [InterfaceImpôtsDaoWithHttpClient] aufweisen:


from abc import abstractmethod

from AbstractImpôtsDao import AbstractImpôtsDao

class InterfaceImpôtsDaoWithHttpClient(AbstractImpôtsDao):

    # Steuerberechnung
    @abstractmethod
    def calculate_tax_in_bulk_mode(self, taxpayers: list):
        pass
  • Zeile 5: Die Schnittstelle [InterfaceImpôtsDaoWithHttpClient] erbt von der abstrakten Klasse [AbstractImpôtsDao], die den Zugriff auf das Dateisystem des Clients verwaltet. Zur Erinnerung: Sie verfügt über eine abstrakte Methode [get_admindata];
  • Zeilen 7–10: Die Methode [calculate_tax_in_bulk_mode], die wir in der vorherigen Version definiert haben, ermöglicht die Berechnung der Steuer für eine Liste von Steuerpflichtigen;

Diese Schnittstelle wird durch die folgende Klasse [ImpôtsDaoWithHttpClient] implementiert:


# Importe

import json

import requests
from flask_api import status

from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsDaoWithHttpClient import InterfaceImpôtsDaoWithHttpClient

class ImpôtsDaoWithHttpClient(InterfaceImpôtsDaoWithHttpClient):

    # Hersteller
    def __init__(self, config: dict):
        # Initialisierung der übergeordneten Ebene
        AbstractImpôtsDao.__init__(self, config)
        # Speicherung der Konfigurationselemente
        # Allgemeine Konfiguration
        self.__config = config
        # Server
        self.__config_server = config["server"]
        # Debug-Modus
        self.__debug = config["debug"]
        # Logger
        self.__logger = None

    def get_admindata(self) -> AdminData:
        # Ausnahmen werden weitergeleitet

        # Dienst-URL
        config_server = self.__config_server
        config_services = config_server['url_services']
        url_service = f"{config_server['urlServer']}{config_services['get-admindata']}"

        # Verbindung
        if config_server['authBasic']:
            response = requests.get(url_service,
                                    auth=(
                                        config_server["user"]["login"],
                                        config_server["user"]["password"]))
        else:
            response = requests.get(url_service)

        # Debug-Modus?
        if self.__debug:
            # Logger
            if not self.__logger:
                self.__logger = self.__config['logger']
            # Protokollierung
            self.__logger.write(f"{response.text}\n")

        # Statuscode
        status_code = response.status_code
        # Ergebnis in Form eines Dictionaries
        résultat = json.loads(response.text)
        # wenn Statuscode ungleich 200 OK
        if status_code != status.HTTP_200_OK:
            raise ImpôtsError(58, résultat['réponse']['erreurs'])
        # Das Ergebnis (ein Dictionary) wird zurückgegeben
        return AdminData().fromdict(résultat["réponse"]["result"])

    # Steuerberechnung im Bulk-Modus
    def calculate_tax_in_bulk_mode(self, taxpayers: list):
        # Ausnahmen werden weitergeleitet

        
  • Zeile 13: Die Klasse [ImpôtsDaoWithHttpClient] implementiert die Schnittstelle [InterfaceImpôtsDaoWithHttpClient]. Sie leitet sich somit von der Klasse [AbstractImpôtsDao] ab;
  • Zeilen 65–66: die Methode [calculate_tax_in_bulk_mode], die in der vorherigen Version behandelt wurde;
  • Zeilen 29–62: die Methode [get_admindata], die von der übergeordneten Klasse [AbstractImpôtsDao] als abstrakt deklariert wurde. Sie wird daher in der untergeordneten Klasse implementiert;
  • Zeilen 33–35: Hier wird die URL des Webdienstes festgelegt, den die Methode [get-admindata] abfragen soll. Diese Service-URL sind in der Client-Konfiguration [config] definiert:

       # Der Server für die Steuerberechnung
        "server": {
            "urlServer""http://127.0.0.1:5000",
            "authBasic"True,
            "user": {
                "login""admin",
                "password""admin"
            },
            "url_services": {
                "calculate-tax""/calculate-tax",
                "get-admindata""/get-admindata"
            }
        },
  • (Fortsetzung)
    • Zeilen 9–12: die beiden URL des Webservers;
  • Zeilen 37–44: Der Dienst URL wird synchron abgefragt;
  • Zeilen 46–42: Falls in der Konfiguration vorgesehen, wird die Antwort des Servers protokolliert;
  • Zeile 57: Es ist bekannt, dass der Server eine Zeichenkette jSON aus einem Wörterbuch gesendet hat;
  • Zeilen 58–60: Wenn der Status HTTP der Antwort nicht 200 ist, wird eine Ausnahme ausgelöst;
  • Zeilen 61–62: Das Objekt [AdminData], das die vom Server gesendeten Daten der Steuerbehörde enthält, wird zurückgegeben;

29.8. Die Skripte [main, main2]

Das Skript [main] stammt aus der vorherigen Version. Es nutzt die Methode [calculate_tax_in_bulk_mode] der Schicht [dao] und greift somit auf die Schicht [métier] des Servers zu;

Das Skript [main2] führt dieselben Aufgaben aus wie das Skript [main], nutzt dabei jedoch die Client-Schicht [métier]:


# Konfiguration der Anwendung

import config
config = config.configure({})

# Abhängigkeiten
from ImpôtsError import ImpôtsError
from Logger import Logger

logger = None
# Code
try:
    # Logger
    logger = Logger(config["logsFilename"])
    # wird in der Konfiguration gespeichert
    config["logger"] = logger
    # Startprotokoll
    logger.write("début du calcul de l'impôt des contribuables\n")
    # die Ebene [dao] wird abgerufen
    dao = config["layers"]["dao"]
    # die Steuerzahler werden abgerufen
    taxpayers = dao.get_taxpayers_data()["taxpayers"]
    # der Steuerzahler?
    if not taxpayers:
        raise ImpôtsError(36f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
    # Es werden die Daten der Steuerbehörde abgerufen
    admindata = dao.get_admindata()
    # Berechnung der Steuer der Steuerzahler durch die Ebene [métier]
    métier = config['layers']['métier']
    for taxpayer in taxpayers:
        métier.calculate_tax(taxpayer, admindata)
    # Die Ergebnisse werden in der Datei jSON gespeichert
    dao.write_taxpayers_results(taxpayers)
# außer BaseException als Fehler:
# # Anzeige des Fehlers
# print(f"Der folgende Fehler ist aufgetreten: {Fehler}")
finally:
    # Das Logging wird beendet
    if logger:
        # Protokollende
        logger.write("fin du calcul de l'impôt des contribuables\n")
        # Logger wird geschlossen
        logger.close()
    # Fertig
    print("Travail terminé...")
  • Zeilen 26–27: Die Daten der Steuerbehörde werden vom Server abgerufen;
  • Zeilen 28–31: Anschließend erfolgt die Berechnung der Steuer der Steuerzahler lokal;

29.9. Client-Tests

In jedem der Skripte [main, main2] werden der Beginn und das Ende des Skripts protokolliert. So lässt sich die Ausführungsdauer des Skripts berechnen. Machen wir einige Prognosen:

  • Das Skript [main] aus der vorherigen Version:
    • erzeugt N Threads, die gleichzeitig ausgeführt werden;
    • Jeder Thread verarbeitet eine Gruppe von Steuerzahlern, deren Steuer er über eine einzige Abfrage an den Server berechnen lässt;
    • da die N Threads gleichzeitig ausgeführt werden, wird die N+1-Anfrage gestartet, bevor die N-Anfrage ihre Antwort erhalten hat. Somit sind die N Anfragen teurer als eine einzige Anfrage, aber wahrscheinlich nicht wesentlich teurer. Außerdem gibt es 11 (die Anzahl der Steuerzahler) Fachberechnungen auf dem Server;
  • Das Skript [main2] dieser Version:
    • führt eine einzige Abfrage an den Server durch;
    • führt 11 Geschäftsberechnungen lokal auf dem Client durch;

Die Fachberechnungen dauern sowohl auf dem Server als auch auf dem Client gleich lang. Der Unterschied liegt also bei den Abfragen. Es ist daher zu erwarten, dass die Ausführungszeit von [main] etwas länger ist als die von [main2].

Wir starten den Server der Version 11, den SGBD und den Mail-Server [hMailServer]. Auf der Serverseite setzen wir den Parameter [sleep_time] auf Null, damit beide Tests unter denselben Bedingungen ausgeführt werden.

Ausführung 1 [main]

Die Ausführung von [main] liefert die folgenden Protokolle:


2020-07-29 14:35:50.016079, MainThread : début du calcul de l'impôt des contribuables
2020-07-29 14:35:50.016079, Thread-1 : début du calcul de l'impôt des 1 contribuables
2020-07-29 14:35:50.016079, Thread-2 : début du calcul de l'impôt des 4 contribuables
2020-07-29 14:35:50.016079, Thread-3 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.016079, Thread-4 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.024426, Thread-5 : début du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.050473, Thread-1 : {"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.050473, Thread-1 : fin du calcul de l'impôt des 1 contribuables
2020-07-29 14:35:50.050473, Thread-3 : {"réponse": {"results": [{"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-3 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, Thread-5 : {"réponse": {"results": [{"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-5 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, Thread-2 : {"réponse": {"results": [{"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347}, {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0}, {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-2 : fin du calcul de l'impôt des 4 contribuables
2020-07-29 14:35:50.051214, Thread-4 : {"réponse": {"results": [{"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0}, {"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0}]}}
2020-07-29 14:35:50.051214, Thread-4 : fin du calcul de l'impôt des 2 contribuables
2020-07-29 14:35:50.051214, MainThread : fin du calcul de l'impôt des contribuables

Die Ausführungsdauer betrug [051214-016079] Nanosekunden (Zeile 17 – Zeile 1), d. h. 35 Millisekunden und 135 Nanosekunden.

Man sieht, dass zwischen der ersten Anfrage an den Server und der letzten vom Client empfangenen Antwort dieselbe Dauer [051214-016079] (Zeile 15 – Zeile 1) liegt, nämlich 35 Millisekunden und 135 Nanosekunden.

Ausführung 2 [main2]

Die Ausführung von [main2] liefert folgende Protokolle:


2020-07-29 14:41:03.303520, MainThread : début du calcul de l'impôt des contribuables
2020-07-29 14:41:03.345084, MainThread : {"réponse": {"result": {"limites": [9964.0, 27519.0, 73779.0, 156244.0, 13500.0], "coeffr": [0.0, 0.14, 0.3, 0.41, 0.45], "coeffn": [0.0, 1394.96, 5798.0, 13913.7, 20163.4], "plafond_decote_couple": 1970.0, "valeur_reduc_demi_part": 3797.0, "plafond_revenus_celibataire_pour_reduction": 21037.0, "plafond_qf_demi_part": 1551.0, "abattement_dixpourcent_max": 12502.0, "plafond_impot_celibataire_pour_decote": 1595.0, "plafond_decote_celibataire": 1196.0, "plafond_revenus_couple_pour_reduction": 42074.0, "id": 1, "abattement_dixpourcent_min": 437.0, "plafond_impot_couple_pour_decote": 2627.0}}}
2020-07-29 14:41:03.349975, MainThread : fin du calcul de l'impôt des contribuables

Die Ausführungsdauer betrug [349975-303520] Nanosekunden (Zeile 3 – Zeile 1), d. h. 46 Millisekunden und 455 Nanosekunden. Völlig unerwartet ist [main] schneller als [main2].

Man sieht, dass die einzige Abfrage von [main2] [345084-303520] (Zeile 2 – Zeile 1) gedauert hat, d. h. 41 Millisekunden und 564 Nanosekunden. Die Steuerberechnung dauerte anschließend [349975-345084] (Zeile 3 – Zeile 2), d. h. 4 Millisekunden und 91 Nanosekunden. Die Abfrage HTTP ist für die Ausführungsdauer verantwortlich. Überraschenderweise zeigt sich hier, dass die einzelne Abfrage von [main2] länger gedauert hat als die vier gleichzeitigen Abfragen von [main] und [35 millisecondes].

Auf der Serverseite sehen die Protokolle wie folgt aus:


2020-07-29 14:35:27.047721, MainThread : [serveur] démarrage du serveur
2020-07-29 14:35:27.140927, MainThread : [serveur] connexion à la base de données réussie
2020-07-29 14:35:28.790716, MainThread : [serveur] démarrage du serveur
2020-07-29 14:35:28.847518, MainThread : [serveur] connexion à la base de données réussie
2020-07-29 14:35:50.039178, Thread-2 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.039178, Thread-3 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.043220, Thread-4 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.044307, Thread-5 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.045796, Thread-2 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 3, 'salaire': 100000, 'impôt': 9200, 'surcôte': 2180, 'taux': 0.3, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 5, 'salaire': 100000, 'impôt': 4230, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.045796, Thread-3 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 2, 'salaire': 55555, 'impôt': 2814, 'surcôte': 0, 'taux': 0.14, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-6 : [index] requête : <Request 'http://127.0.0.1:5000/calculate-tax' [POST]>
2020-07-29 14:35:50.046825, Thread-6 : [index] {'réponse': {'results': [{'marié': 'oui', 'enfants': 2, 'salaire': 50000, 'impôt': 1384, 'surcôte': 0, 'taux': 0.14, 'décôte': 384, 'réduction': 347}, {'marié': 'oui', 'enfants': 3, 'salaire': 50000, 'impôt': 0, 'surcôte': 0, 'taux': 0.14, 'décôte': 720, 'réduction': 0}, {'marié': 'non', 'enfants': 2, 'salaire': 100000, 'impôt': 19884, 'surcôte': 4480, 'taux': 0.41, 'décôte': 0, 'réduction': 0}, {'marié': 'non', 'enfants': 3, 'salaire': 100000, 'impôt': 16782, 'surcôte': 7176, 'taux': 0.41, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-4 : [index] {'réponse': {'results': [{'marié': 'non', 'enfants': 0, 'salaire': 200000, 'impôt': 64210, 'surcôte': 7498, 'taux': 0.45, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 3, 'salaire': 200000, 'impôt': 42842, 'surcôte': 17283, 'taux': 0.41, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:35:50.046825, Thread-5 : [index] {'réponse': {'results': [{'marié': 'non', 'enfants': 0, 'salaire': 100000, 'impôt': 22986, 'surcôte': 0, 'taux': 0.41, 'décôte': 0, 'réduction': 0}, {'marié': 'oui', 'enfants': 2, 'salaire': 30000, 'impôt': 0, 'surcôte': 0, 'taux': 0.0, 'décôte': 0, 'réduction': 0}]}}
2020-07-29 14:41:03.341582, Thread-7 : [index] requête : <Request 'http://127.0.0.1:5000/get-admindata' [GET]>
2020-07-29 14:41:03.341582, Thread-7 : [index] {'réponse': {'result': {'limites': [9964.0, 27519.0, 73779.0, 156244.0, 13500.0], 'coeffr': [0.0, 0.14, 0.3, 0.41, 0.45], 'coeffn': [0.0, 1394.96, 5798.0, 13913.7, 20163.4], 'plafond_decote_couple': 1970.0, 'valeur_reduc_demi_part': 3797.0, 'plafond_revenus_celibataire_pour_reduction': 21037.0, 'plafond_qf_demi_part': 1551.0, 'abattement_dixpourcent_max': 12502.0, 'plafond_impot_celibataire_pour_decote': 1595.0, 'plafond_decote_celibataire': 1196.0, 'plafond_revenus_couple_pour_reduction': 42074.0, 'id': 1, 'abattement_dixpourcent_min': 437.0, 'plafond_impot_couple_pour_decote': 2627.0}}}
  • Zeile 5: die erste Anfrage des Clients [main];
  • Zeile 14: die letzte Antwort an den Client [main]. Zwischen den beiden liegen 6 Millisekunden und 647 Nanosekunden;
  • Zeilen 15–16: die einzige Anfrage des Clients [main2]. Die Antwort erfolgt sofort;