Skip to content

29. Anwendungsübung: Version 11

29.1. Einführung

In früheren Versionen der Client/Server-Anwendung zur Steuerberechnung befand sich die [Geschäftslogik]-Schicht, die die Geschäftsregeln für diese Berechnung implementiert, auf der Serverseite. Wir schlagen nun vor, sie auf die Client-Seite zu verlagern. Was ist der Vorteil? Ein Teil der Arbeit, die zuvor vom Server erledigt wurde, wird auf die Client-Seite verlagert. Stellen Sie sich ein Szenario vor, in dem ein Server von N Clients abgefragt wird; N steuerliche Geschäftsberechnungen werden von den Clients durchgeführt. In früheren Versionen führte der Server diese N Geschäftsberechnungen durch. Da er die Geschäftsberechnung nicht mehr durchführt, reagiert der Server schneller auf seine Clients und kann daher mehr von ihnen gleichzeitig bedienen.

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

Image

  • Die [Geschäfts-]Schicht [10] wurde auf dem Client dupliziert [12];
  • ein neues Skript [main2] [11] wurde auf dem Client hinzugefügt;

Der Web-Client verfügt über zwei Möglichkeiten, die Steuer für die in [3] gefundene Liste der Steuerzahler zu berechnen:

  • Verwenden Sie die Methode aus der vorherigen Version. Diese nutzt die [Business]-Schicht des Servers [10]. Das [Haupt]-Skript wird diese Methode verwenden;
  • Fordern Sie einfach die Daten der Steuerbehörde vom Server an [2-4] und nutzen Sie dann die clientseitige [Business]-Schicht [12];

Wir werden die Leistung der beiden Methoden vergleichen.

29.2. Der Webserver

Die Verzeichnisstruktur des Webservers sieht wie folgt aus:

Image

  • Das Verzeichnis [http-servers/06] wird zunächst durch Kopieren des Verzeichnisses [http-servers/05] erstellt. Wir werden die Funktionen der vorherigen Version 10 beibehalten. Wir werden lediglich eine neue Funktion hinzufügen. Dies wird durch einen neuen Controller [get_admindata_controller] [1] umgesetzt. Der andere Controller [calculate_tax_controller] ist nichts anderes als der alte [index_controller], der umbenannt wurde;

29.3. Konfiguration

Der Server wird zwei Service-URLs anbieten:

  • [/calculate-tax] zur Berechnung der Steuer für eine Liste von Steuerzahlern, die im Body einer POST-Anfrage übergeben wird. Sie entspricht somit der URL [/] aus der vorherigen Version 10;
  • [/get-admindata] gibt die JSON-Zeichenkette mit den Daten der Steuerverwaltung zurück;

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

1
2
3
4
    #  web application controller dictionary
    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:

#  a mysql or pgres parameter is expected
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()

#  configure the application
import config
config = config.configure({'sgbd': sgbd})

#  dependencies


#  data recovery from tax authorities
erreur = False
try:
    #  admindata will be read-only application data
    config["admindata"] = config["layers"]["dao"].get_admindata()
    #  success log
    logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
    #  we note the error
    

#  if there has been an error, we stop
if erreur:
    sys.exit(2)

#  the Flask application can be started
app = Flask(__name__)

#  main controller
def main_controller() -> tuple:
    #  the requested action is retrieved
    dummy, action=request.path.split('/')
    logger = None
    try:
        #  logger
        logger = Logger(config["logsFilename"])
        #  we store it in a config associated with the thread
        thread_config = {"logger": logger}
        thread_name = threading.current_thread().name
        config[thread_name] = {"config": thread_config}
        #  log the request
        logger.write(f"[index] requête : {request}\n")
        #  the thread is interrupted if requested
        sleep_time = config["sleep_time"]
        if sleep_time != 0:
            #  pause is randomized so that some threads are interrupted and others not
            aléa = randint(0, 1)
            if aléa == 1:
                #  log before break
                logger.write(f"[index] mis en pause du thread pendant {sleep_time} seconde(s)\n")
                #  break
                time.sleep(sleep_time)
        #  the request is executed by the request controller
        controller = config['controllers'][action]
        résultat, status_code = controller.execute(request, config)
        #  was there a fatal error?
        if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
            #  send an e-mail to the application administrator
            config_mail = config["adminMail"]
            config_mail["logger"] = logger
            SendAdminMail.send(config_mail, json.dumps(résultat, ensure_ascii=False))
        #  we log the answer
        logger.write(f"[index] {résultat}\n")
        #  we send the answer
        print(résultat)
        return json_response(résultat, status_code)
    except BaseException as erreur:
        #  log the error if possible
        if logger:
            logger.write(f"[index] {erreur}")
        #  we prepare the response to the customer
        résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
        #  we send the answer
        return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
    finally:
        #  close the log file if it has been opened
        if logger:
            logger.close()

#  tAX CALCULATION
@app.route('/calculate-tax', methods=['POST'])
@auth.login_required
def calculate_tax():
    #  we hand over to the main controller
    return main_controller()

#  obtain data from tax authorities
@app.route('/get-admindata', methods=['GET'])
@auth.login_required
def get_admindata():
    #  we hand over to the main controller
    return main_controller()

#  hand only
if __name__ == '__main__':
    #  start the server
    app.config.update(ENV="development", DEBUG=True)
    app.run(threaded=True)
  • Zeilen 88–93: Die Funktion [calculate_tax] verarbeitet die URL [/calculate-tax];
  • Zeilen 95–100: Die Funktion [get_admindata] verarbeitet die URL [/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, mit einem kleinen Unterschied: Während die Funktion [index] nur eine einzige URL bearbeitete, bearbeitet [main_controller] hier zwei URLs. Er muss diese daher von einem der beiden Controller [calculate_tax_controller, get_admin_data_controller] verarbeiten lassen;
  • Zeilen 39–40: Wir rufen die angeforderte Aktion [calculate_tax] oder [get_admindata] ab. Diese Information befindet sich im URL-Pfad [request.path]. Je nach Fall ist [request.path] entweder [/get-admindata] oder [/calculate_tax]. Die Aufteilung in Zeile 40 liefert zwei Elemente:
    • die leere Zeichenfolge für den Teil vor dem /;
    • den Namen der angeforderten Aktion für den Teil nach dem /;
  • Zeilen 62–63: Sobald die URL-Aktion abgerufen wurde, wissen wir, welcher Controller zur Verarbeitung der URL verwendet werden muss. Diese Information befindet sich in der Konfiguration [config];

29.5. Controller

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

Der Controller [get_admindata_controller] sieht wie folgt aus:

1
2
3
4
5
6
from flask_api import status
from werkzeug.local import LocalProxy

def execute(request: LocalProxy, config: dict) -> tuple:
    #  we return the answer
    return {"réponse": {"result": config["admindata"].asdict()}}, status.HTTP_200_OK
  • Die URL [/get-admindata] muss die JSON-Zeichenkette der Steuerverwaltungsdaten zurückgeben;
  • Zeile 6: Diese Daten wurden vom Hauptskript [main] abgerufen und als [AdminData]-Objekt im Wörterbuch [config] abgelegt. Wir geben das Wörterbuch dieses Objekts zurück;

29.6. Postman-Tests

Wir starten den Webserver, das DBMS und den Mailserver [hMailServer]. Anschließend berechnen wir mithilfe eines Postman-Clients 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 senden wir eine GET-Anfrage an die URL [/get-admindata]:

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 Web-Client

Image

Image

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

  • Änderung der Konfiguration [config_layers], sodass sie nun eine [business]-Schicht enthält. Zuvor gab es nur eine [DAO]-Schicht;
  • Hinzufügen einer neuen Methode zur [dao]-Schicht;
  • Schreiben eines Skripts [main2], das sich auf die [business]-Schicht des Clients stützt, um die Steuern der Steuerzahler zu berechnen;

29.7.1. Konfiguration der Client-Ebene

Die Konfiguration der Schichten erfolgt an zwei Stellen:

  • in der [config]-Konfiguration, die den Ordner mit der [business]-Schicht-Implementierung in die Abhängigkeiten des Clients aufnehmen muss. Dieser Ordner war bereits in den Abhängigkeiten enthalten:

absolute_dependencies = [
        # dossiers du projet
        # 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",
        # Constantes, tranches
        f"{root_dir}/impots/v05/entities",
        # ImpôtsDaoWithHttpClient
        f"{script_dir}/../services",
        # scripts de configuration
        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:
    #  instantiation of applicatuon layers

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

    #  dao layer
    from ImpôtsDaoWithHttpClient import ImpôtsDaoWithHttpClient
    dao = ImpôtsDaoWithHttpClient(config)

    #  make the layer configuration
    return {
        "dao": dao,
        "métier": métier
    }
  • Zeilen 4–6: Instanziierung der [Business]-Schicht;
  • Zeilen 13–16: Die [Business]-Schicht wird im Layer-Dictionary zurückgegeben;

29.7.2. Implementierung der [dao]-Schicht

Image

Die [dao]-Schicht implementiert die folgende Schnittstelle [InterfaceImpôtsDaoWithHttpClient]:

from abc import abstractmethod

from AbstractImpôtsDao import AbstractImpôtsDao

class InterfaceImpôtsDaoWithHttpClient(AbstractImpôtsDao):

    #  tAX CALCULATION
    @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. Beachten Sie, dass sie eine abstrakte Methode [get_admindata] enthält;
  • 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 Steuerzahlern;

Diese Schnittstelle wird von der folgenden Klasse [ImpôtsDaoWithHttpClient] implementiert:

#  imports

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):

    #  manufacturer
    def __init__(self, config: dict):
        #  parent initialization
        AbstractImpôtsDao.__init__(self, config)
        #  saving configuration items
        #  general configuration
        self.__config = config
        #  server
        self.__config_server = config["server"]
        #  debug mode
        self.__debug = config["debug"]
        #  logger
        self.__logger = None

    def get_admindata(self) -> AdminData:
        #  we let the exceptions rise

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

        #  connection
        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 mode?
        if self.__debug:
            #  logger
            if not self.__logger:
                self.__logger = self.__config['logger']
            #  log on
            self.__logger.write(f"{response.text}\n")

        #  status code
        status_code = response.status_code
        #  result in the form of a dictionary
        résultat = json.loads(response.text)
        #  if status code other than 200 OK
        if status_code != status.HTTP_200_OK:
            raise ImpôtsError(58, résultat['réponse']['erreurs'])
        #  return the result (a dictionary)
        return AdminData().fromdict(résultat["réponse"]["result"])

    #  bulk tax calculation
    def calculate_tax_in_bulk_mode(self, taxpayers: list):
        #  we let the exceptions rise

        
  • Zeile 13: Die Klasse [TaxDaoWithHttpClient] implementiert die Schnittstelle [TaxDaoWithHttpClientInterface]. Sie leitet sich daher von der Klasse [AbstractTaxDao] 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 die übergeordnete Klasse [AbstractImpôtsDao] als abstrakt deklariert hat. Sie wird daher in der untergeordneten Klasse implementiert;
  • Zeilen 33–35: Die URL des Webdienstes, den die Methode [get-admindata] abfragen muss, wird bestimmt. Diese Dienst-URLs sind in der Konfiguration [config] des Clients definiert:

       # le serveur de calcul de l'impôt
        "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 Webserver-URLs;
  • Zeilen 37–44: Die Service-URL wird synchron abgefragt;
  • Zeilen 46–42: Falls die Konfiguration dies erfordert, wird die Antwort des Servers protokolliert;
  • Zeile 57: Wir wissen, dass der Server eine JSON-Zeichenkette in Form eines Wörterbuchs gesendet hat;
  • Zeilen 58–60: Wenn der HTTP-Status der Antwort nicht 200 ist, wird eine Ausnahme ausgelöst;
  • Zeilen 61–62: Das [AdminData]-Objekt, das die vom Server gesendeten Steuerverwaltungsdaten kapselt, 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] aus der [dao]-Schicht und greift somit auf die [business]-Schicht des Servers zu;

Das Skript [main2] führt dieselben Schritte aus wie das Skript [main], nutzt jedoch die [business]-Schicht des Clients:

#  configure the application

import config
config = config.configure({})

#  dependencies
from ImpôtsError import ImpôtsError
from Logger import Logger

logger = None
#  code
try:
    #  logger
    logger = Logger(config["logsFilename"])
    #  we save it in the config
    config["logger"] = logger
    #  start log
    logger.write("début du calcul de l'impôt des contribuables\n")
    #  we recover the [dao] layer
    dao = config["layers"]["dao"]
    #  we get the taxpayers back
    taxpayers = dao.get_taxpayers_data()["taxpayers"]
    #  taxpayers?
    if not taxpayers:
        raise ImpôtsError(36, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
    #  we retrieve data from the tax authorities
    admindata = dao.get_admindata()
    #  calculation of taxpayers' taxes by the [business] layer
    métier = config['layers']['métier']
    for taxpayer in taxpayers:
        métier.calculate_tax(taxpayer, admindata)
    #  save the results in the jSON file
    dao.write_taxpayers_results(taxpayers)
#  except BaseException as error:
#      # error display
#      print(f "The following error has occurred: {error}")
finally:
    #  close the logger
    if logger:
        #  end log
        logger.write("fin du calcul de l'impôt des contribuables\n")
        #  closing the logger
        logger.close()
    #  we're done
    print("Travail terminé...")
  • Zeilen 26–27: Daten vom Server der Steuerbehörde abrufen;
  • Zeilen 28–31: Anschließend wird die Steuer der Steuerzahler lokal berechnet;

29.9. Client-Tests

In jedem der Skripte [main, main2] protokollieren wir den Start und das Ende des Skripts. So können wir die Ausführungszeit des Skripts berechnen. Lassen Sie uns einige Vorhersagen treffen:

  • Das Skript [main] aus der vorherigen Version:
    • erzeugt N Threads, die gleichzeitig laufen;
    • jeder Thread verarbeitet eine Gruppe von Steuerzahlern, für die er die Steuer über eine einzige Anfrage an den Server berechnet;
    • Da die N Threads gleichzeitig laufen, wird die N+1-Anfrage gesendet, bevor die N-Anfrage ihre Antwort erhalten hat. Somit verursachen die N Anfragen höhere Kosten als eine einzelne Anfrage, aber wahrscheinlich nicht wesentlich mehr. Außerdem werden auf dem Server 11 (die Anzahl der Steuerzahler) geschäftliche Berechnungen durchgeführt;
  • das Skript [main2] in dieser Version:
    • sendet eine einzige Anfrage an den Server;
    • führt 11 Geschäftsberechnungen lokal auf dem Client durch;

Die Geschäftsberechnungen nehmen unabhängig davon, ob sie auf dem Server oder auf dem Client durchgeführt werden, die gleiche Zeit in Anspruch. Der Unterschied liegt daher in den Anfragen. Wir können daher davon ausgehen, dass die Ausführungszeit von [main] etwas länger ist als die von [main2].

Wir starten den Server der Version 11, das DBMS und den Mailserver [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] erzeugt 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ührungszeit betrug [051214-016079] Nanosekunden (Zeile 17 – Zeile 1), d. h. 35 Millisekunden und 135 Nanosekunden.

Wir sehen, dass zwischen der ersten Anfrage an den Server und der letzten vom Client empfangenen Antwort die Dauer identisch ist [051214-016079] (Zeile 15 – Zeile 1), also 35 Millisekunden und 135 Nanosekunden.

Ausführung 2 [main2]

Die Ausführung von [main2] liefert die folgenden 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ührungszeit betrug [349975-303520] Nanosekunden (Zeile 3 – Zeile 1), d. h. 46 Millisekunden und 455 Nanosekunden. Völlig unerwartet ist [main] schneller als [main2].

Wir sehen, dass die einzelne Anfrage von [main2] [345084-303520] (Zeile 2 – Zeile 1) dauerte, d. h. 41 Millisekunden und 564 Nanosekunden. Die Steuerberechnung dauerte dann [349975-345084] (Zeile 3 – Zeile 2), d. h. 4 Millisekunden und 91 Nanosekunden. Es ist die HTTP-Anfrage, die für die Ausführungszeit verantwortlich ist. Überraschenderweise sehen wir hier, dass die einzelne Anfrage von [main2] länger dauerte [41 Millisekunden] als die vier gleichzeitigen Anfragen von [main] [35 Millisekunden].

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 vom Client [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 vom Client [main2]. Die Antwort erfolgt sofort;