Skip to content

28. Toepassingsopdracht: versie 10

28.1. Inleiding

In de voorbeelden van clients van de belastingberekeningsserver verstuurden de threads N verzoeken achtereenvolgens als ze N belastingplichtigen moesten verwerken. Het idee hier is om één enkel verzoek te versturen waarin de N belastingplichtigen zijn samengevat. Voor elk van hen moet de informatie [marié, enfants, salaire] worden verstuurd. Deze kan worden verstuurd als parameters:

  • van het verzoek URL. Dit leidt dan tot een lang, weinig betekenisvol verzoek URL;
  • in de body van het verzoek HTTP. We weten dat deze body verborgen blijft voor de gebruiker die een browser gebruikt;

In beide gevallen kunnen we een verzoek [GET] of [POST] gebruiken. We zullen een verzoek POST gebruiken met de parameters ingekapseld in de body van het verzoek HTTP.

De client/server-architectuur is niet veranderd:

Image

28.2. De webserver

Image

Het bestand [http-servers/05] wordt in eerste instantie verkregen door het bestand [http-servers/02] te kopiëren. We keren terug naar de uitwisselingen jSON tussen de client en de server. We hebben gezien dat de overgang van jSON naar XML heel eenvoudig is.

28.2.1. Configuratie

De configuratie van [config, config_database, config_layers] blijft gelijk aan die van de vorige versies. We gaan hier niet verder op in.

28.2.2. Het hoofdscript [main]

Het script [main] is identiek aan dat in de map [http-servers/02] die we hebben gekopieerd. Er is slechts één verschil:


# Home URL
@app.route('/', methods=['POST'])
@auth.login_required
def index():

  • regel 2: voortaan wordt de URL verkregen via een POST;

28.2.3. De controller [index_controller]

De controller [index_controller] ontwikkelt zich als volgt:


# afhankelijkheden importeren

import json

from flask_api import status
from werkzeug.local import LocalProxy


def execute(request: LocalProxy, config: dict) -> tuple:
    # afhankelijkheden
    from ImpôtsError import ImpôtsError
    from TaxPayer import TaxPayer

    # de hoofdtekst van het bericht wordt opgehaald – er wordt gewacht op een lijst met woordenboeken
    msg_erreur = None
    list_dict_taxpayers = None
    # de tekst jSON van POST
    request_text = request.data
    try:
        # die wordt omgezet in een lijst met woordenboeken
        list_dict_taxpayers = json.loads(request_text)
    except BaseException as erreur:
        # de fout wordt genoteerd
        msg_erreur = f"le corps du POST n'est pas une chaîne jSON valide : {erreur}"
    # hebben we een niet-lege lijst?
    if not msg_erreur and (not isinstance(list_dict_taxpayers, list) or len(list_dict_taxpayers) == 0):
        # de fout wordt genoteerd
        msg_erreur = "le corps du POST n'est pas une liste ou alors cette liste est vide"
    # hebben we een lijst met woordenboeken?
    if not msg_erreur:
        erreur = False
        i = 0
        while not erreur and i < len(list_dict_taxpayers):
            erreur = not isinstance(list_dict_taxpayers[i], dict)
            i += 1
        # fout?
        if erreur:
            msg_erreur = "le corps du POST doit être une liste de dictionnaires"
    # fout?
    if msg_erreur:
        # er wordt een foutmelding naar de client gestuurd
        résultats = {"réponse": {"erreurs": [msg_erreur]}}
        return résultats, status.HTTP_400_BAD_REQUEST

    # de TaxPayers-codes worden één voor één gecontroleerd
    # in eerste instantie geen fouten
    list_erreurs = []
    for dict_taxpayer in list_dict_taxpayers:
        # er wordt een TaxPayer aangemaakt op basis van dict_taxpayer
        msg_erreur = None
        try:
            # de volgende bewerking verwijdert de gevallen waarin de parameters niet
            # van de eigenschappen van de klasse TaxPayer, evenals de gevallen waarin hun waarden
            # onjuist zijn
            TaxPayer().fromdict(dict_taxpayer)
        except BaseException as erreur:
            msg_erreur = f"{erreur}"
        # bepaalde sleutels moeten in het woordenboek aanwezig zijn
        if not msg_erreur:
            # de sleutels [marié, enfants, salaire] moeten in het woordenboek voorkomen
            keys = dict_taxpayer.keys()
            if 'marié' not in keys or 'enfants' not in keys or 'salaire' not in keys:
                msg_erreur = "le dictionnaire doit inclure les clés [marié, enfants, salaire]"
        # fouten?
        if msg_erreur:
            # de fout wordt vastgesteld in TaxPayer zelf
            dict_taxpayer['erreur'] = msg_erreur
            # TaxPayer wordt toegevoegd aan de lijst met fouten
            list_erreurs.append(dict_taxpayer)

    # alle belastingplichtigen zijn verwerkt – zijn er fouten?
    if list_erreurs:
        # er wordt een foutmelding naar de klant verzonden
        résultats = {"réponse": {"erreurs": list_erreurs}}
        return résultats, status.HTTP_400_BAD_REQUEST

    # geen fouten, we kunnen verdergaan
    # gegevens ophalen bij de belastingdienst
    admindata = config["admindata"]
    métier = config["layers"]["métier"]
    try:
        # de TaxPayer-bestanden worden één voor één verwerkt
        list_taxpayers = []
        for dict_taxpayer in list_dict_taxpayers:
            # belastingberekening
            taxpayer = TaxPayer().fromdict(
                {'marié': dict_taxpayer['marié'], 'enfants': dict_taxpayer['enfants'],
                 'salaris': dict_taxpayer['salaire']})
            métier.calculate_tax(taxpayer, admindata)
            # het resultaat wordt opgeslagen als een woordenboek
            list_taxpayers.append(taxpayer.asdict())
        # we sturen het antwoord naar de klant
        return {"réponse": {"results": list_taxpayers}}, status.HTTP_200_OK
    except ImpôtsError as erreur:
        # er wordt een foutmelding naar de klant gestuurd
        return {"réponse": {"erreurs": f"[{erreur}]"}}, status.HTTP_500_INTERNAL_SERVER_ERROR
  • regel 9: de controller ontvangt:
      • het verzoek [request] van de client;
      • de configuratie [config] van de server;
  • regels 14-18: de body van POST wordt opgehaald. De parameters die in de body van het verzoek HTTP zijn ingekapseld, kunnen op verschillende manieren worden gecodeerd. We zijn er al één tegengekomen: [x-www-form-urlencoded]. We gaan hier een andere codering gebruiken: jSON;
  • regel 18: Met [request.data] kan de body van het verzoek HTTP worden opgehaald. Hier wordt tekst opgehaald en we weten dat deze tekst afkomstig is van jSON, dat een lijst met woordenboeken [marié, enfants, salaire] vertegenwoordigt;
  • regels 19-24: we halen deze woordenboeklijst op;
  • regels 22-24: als het ophalen van jSON is mislukt, wordt de fout genoteerd;
  • regels 26-28: als blijkt dat het opgehaalde object geen lijst is of dat het een lege lijst is, wordt de fout genoteerd;
  • regels 29-38: als er wel een lijst is opgehaald, wordt gecontroleerd of het inderdaad een lijst met woordenboeken is;
  • regels 40-43: als er een fout is opgetreden, stoppen we hier en sturen we een foutmelding naar de klant;
  • regels 45-69: nu wordt elk woordenboek gecontroleerd:
    • ze moeten de sleutels [marié, enfants, salaire] bevatten;
    • ze moeten het mogelijk maken om een geldig [TaxPayer]-object samen te stellen;
  • regels 65-69: als er een fout is gedetecteerd in een woordenboek, dan wordt deze in datzelfde woordenboek opgeslagen onder de sleutel ‘fout’;
  • regels 72-75: de woordenboeken met fouten zijn verzameld in de lijst [list_erreurs]. Als deze lijst niet leeg is, wordt deze meegestuurd in een foutmelding aan de klant;
  • regel 77: op dit punt weten we dat we een lijst met objecten van het type [TaxPayer] kunnen aanmaken op basis van de inhoud van het door de klant verzonden verzoek;
  • regels 84-91: we verwerken de lijst met ontvangen woordenboeken;
  • regel 86: op basis van een woordenboek maken we een object van het type [TaxPayer] aan;
  • regel 89: de belasting van dit [TaxPayer] wordt berekend;
  • regel 91: we weten dat [taxpayer] is gewijzigd door de belastingberekening. We zetten het om in een woordenboek en voegen het toe aan een resultatenlijst;
  • regel 93: deze resultatenlijst wordt naar de klant verzonden;

28.2.4. Servertesten

We gaan de server testen met een Postman-client:

  • we starten de webserver, de SGBD, en de mailserver [hMailServer];
  • we starten de Postman-client en de bijbehorende console (Ctrl-Alt-C);

Image

  • in [1]: we verzenden een verzoek naar [POST];
  • in [2]: de URL van de server;
  • in [3]: de body van het verzoek HTTP;
  • in [5]: wordt aangegeven dat deze body moet worden verzonden in de vorm van een tekenreeks jSON;
  • in [4]: we schakelen over naar de modus [raw] om een tekenreeks jSON te kunnen kopiëren en plakken;
  • in [6]: de tekenreeks jSON, afkomstig uit een van de bestanden [résultats.json] van de verschillende versies, wordt geplakt. Vervolgens worden voor elke belastingplichtige alleen de eigenschappen [marié, salaire, enfants] behouden;

Image

  • in [7] bekijken we de headers HTTP die de Postman-client naar de server gaat sturen;
  • in [8] zien we dat hij een header [Content-Type] naar de server zal sturen, waarin wordt aangegeven dat het verzoek een body bevat die is gecodeerd in jSON. Dit vloeit voort uit de eerder gemaakte keuze [5];

Image

  • in [9-12]: we voegen de door de server verwachte identificatiegegevens toe aan het verzoek;

We verzenden dit verzoek. Het antwoord van de server is als volgt:

Image

  • in [3], we hebben jSON ontvangen;
  • in [4], de belasting van de belastingplichtigen;

Laten we in de Postman-console (Ctrl-Alt-C) de client/server-dialoog bekijken die heeft plaatsgevonden:

De Postman-client heeft de volgende tekst verzonden:

POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 03c4aa28-5a5d-4bb5-ac51-7ad51968c71d
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 824

[
  {
    "marié": "oui",
    "enfants": 2,
    "salaire": 55555
  },
  {
    "marié": "oui",
    "enfants": 2,
    "salaire": 50000
  },
  {
    "marié": "oui",
    "enfants": 3,
    "salaire": 50000
  },
  {
    "marié": "non",
    "enfants": 2,
    "salaire": 100000
  },
  {
    "marié": "non",
    "enfants": 3,
    "salaire": 100000
  },
  {
    "marié": "oui",
    "enfants": 3,
    "salaire": 100000
  },
  {
    "marié": "oui",
    "enfants": 5,
    "salaire": 100000
  },
  {
    "marié": "non",
    "enfants": 0,
    "salaire": 100000
  },
  {
    "marié": "oui",
    "enfants": 2,
    "salaire": 30000
  },
  {
    "marié": "non",
    "enfants": 0,
    "salaire": 200000
  },
  {
    "marié": "oui",
    "enfants": 3,
    "salaire": 200000
  }
]
  • regel 1: de POST naar de server;
  • regel 2: de authenticatieheader HTTP;
  • regel 3: de client laat de server weten dat hij een tekenreeks jSON verstuurt en dat deze tekenreeks 824 bytes groot is (regel 11);
  • regels 13-69: de hoofdtekst jSON van het verzoek;

De server stuurde hem de volgende tekst terug:


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: Tue, 28 Jul 2020 07:16:34 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}, {"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}, {"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}, {"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}, {"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}]}}
  • regel 1: het verzoek is geslaagd;
  • regel 2: de inhoud van het antwoord van de server is een tekenreeks jSON. Deze is 1461 bytes lang (regel 3);
  • regel 7: het antwoord jSON van de server;

Laten we nu enkele foutgevallen testen.

Geval 1: we sturen willekeurige gegevens


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 47652706-9744-46a0-a682-de010e5406c0
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 3

abc

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 125
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:43:27 GMT

{"réponse": {"erreurs": ["le corps du POST n'est pas une chaîne jSON valide : Expecting value: line 1 column 1 (char 0)"]}}
  • regel 13: we hebben de tekenreeks [abc] verzonden, die geen geldige jSON-tekenreeks is (regel 3);
  • regel 15: de server reageert met foutcode 400;
  • regel 21: het antwoord jSON van de server;

Geval 2: laten we een geldige tekenreeks jSON verzenden die geen lijst is


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 03b64735-9239-47b3-b92d-be7c9ebc7559
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 17

{"att1":"value1"}

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 97
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:50:11 GMT

{"réponse": {"erreurs": ["le corps du POST n'est pas une liste ou alors cette liste est vide"]}}

Geval 3: laten we een tekenreeks jSON verzenden die een lijst is waarvan niet alle elementen woordenboeken zijn


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: a1528a5f-777c-413f-b3be-7d4e9955b12a
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 7

[0,1,2]

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 85
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:52:10 GMT

{"réponse": {"erreurs": ["le corps du POST doit être une liste de dictionnaires"]}}

Geval 4: laten we een lijst met woordenboeken verzenden, met daarin een woordenboek dat niet de juiste sleutels bevat


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: ba964d81-c9d9-46ff-a521-b4c4e5639484
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 19

[{"att1":"value1"}]

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 112
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:54:33 GMT

{"réponse": {"erreurs": [{"att1": "value1", "erreur": "MyException[2, la clé [att1] n'est pas autorisée]"}]}}

Geval 5: laten we een lijst met woordenboeken verzenden waarin één woordenboek ontbrekende sleutels bevat:


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 98aec51d-f37d-4c14-81cd-c7ffcbbcdc65
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 18

[{"marié":"oui"}]

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 125
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:56:40 GMT

{"réponse": {"erreurs": [{"marié": "oui", "erreur": "le dictionnaire doit inclure les clés [marié, enfants, salaire]"}]}}

Geval 6: laten we een lijst met woordenboeken verzenden met een woordenboek dat de juiste sleutels heeft, maar waarbij sommige waarden onjuist zijn:


POST / HTTP/1.1
Authorization: Basic YWRtaW46YWRtaW4=
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.2
Accept: */*
Cache-Control: no-cache
Postman-Token: 3083e601-dee4-4e15-9ea4-fc0328d0fcf0
Host: localhost:5000
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 46

[{"marié":"x", "enfants":"x", "salaire":"x"}]

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json; charset=utf-8
Content-Length: 167
Server: Werkzeug/1.0.1 Python/3.8.1
Date: Tue, 28 Jul 2020 07:59:32 GMT

{"réponse": {"erreurs": [{"marié": "x", "enfants": "x", "salaire": "x", "erreur": "MyException[31, l'attribut marié [x] doit avoir l'une des valeurs oui / non]"}]}}

28.3. De webclient

Image

De map [http-clients/05] (versie 10) wordt in eerste instantie verkregen door de map [http-clients/02] (versie 7) te kopiëren. Vervolgens wordt deze gewijzigd.

28.3.1. De laag [dao]

De laag [dao] wordt geïmplementeerd door de volgende klasse [ImpôtsDaoWithHttpClient]:


# imports

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ôtsMétier import InterfaceImpôtsMétier
from TaxPayer import TaxPayer


class ImpôtsDaoWithHttpClient(AbstractImpôtsDao, InterfaceImpôtsMétier):

    # constructor
    def __init__(self, config: dict):
        

    # ongebruikte methode
    def get_admindata(self) -> AdminData:
        pass

    # belastingberekening
    def calculate_tax(self, taxpayer: TaxPayer, admindata: AdminData = None):
        

    # belastingberekening in bulkmodus
    def calculate_tax_in_bulk_mode(self, taxpayers: list) -> list:
        # uitzonderingen doorgeven

        # belastingplichtigen worden omgezet in een lijst met woordenboeken
        # alleen de eigenschappen bewaren [marié, enfants, salaire]
        list_dict_taxpayers = list(
            map(lambda taxpayer:
                taxpayer.asdict(included_keys=[
                    '_TaxPayer__marié',
                    '_TaxPayer__enfants',
                    '_TaxPayer__salaire']),
                taxpayers))

        # verbinding met de server
        config_server = self.__config_server
        if config_server['authBasic']:
            response = requests.post(config_server['urlServer'], json=list_dict_taxpayers,
                                     auth=(config_server["user"]["login"],
                                           config_server["user"]["password"]))
        else:
            response = requests.post(config_server['urlServer'], json=list_dict_taxpayers)
        # debugmodus?
        if self.__debug:
            # logboek
            if not self.__logger:
                self.__logger = self.__config['logger']
            # wordt ingelogd
            self.__logger.write(f"{response.text}\n")
        # statuscode van het antwoord HTTP
        status_code = response.status_code
        # het antwoord jSON wordt in een woordenboek opgeslagen
        résultat = response.json()
        # fout als de statuscode niet 200 is OK
        if status_code != status.HTTP_200_OK:
            # we weten dat de fouten zijn gekoppeld aan de sleutel [erreurs] van het antwoord
            raise ImpôtsError(93, résultat['réponse']['erreurs'])
        # we weten dat het resultaat is gekoppeld aan de sleutel [results] uit het antwoord
        list_dict_taxpayers2 = résultat['réponse']['results']
        # de oorspronkelijke lijst met belastingplichtigen wordt bijgewerkt met de ontvangen resultaten
        for i in range(len(taxpayers)):
            # bijwerking van taxpayers[i]
            taxpayers[i].fromdict(list_dict_taxpayers2[i])
        # hier is de parameter [taxpayers] bijgewerkt met de resultaten van de server
  • regels 1-26: de code blijft ongewijzigd ten opzichte van versie 7 en andere versies;
  • regels 27-70: er wordt een nieuwe methode [calculate_tax_in_bulk_mode] geïntroduceerd, die tot taak heeft de belasting te berekenen voor een lijst van belastingplichtigen;
  • regel 28: [taxpayers] is deze lijst met belastingplichtigen;
  • regels 31-39: met behulp van de functie [map] wordt een lijst met objecten van het type [TaxPayer] omgezet in een lijst met woordenboeken;
  • regels 34-38: de gebruikte lambda-functie zet een object van het type [TaxPayer] om in een woordenboek van het type [dict] met uitsluitend de sleutels [marié, enfants, salaire]. Hiervoor wordt de parameter met de naam [included_keys] van de methode [BaseEntity.asdict] gebruikt. Ter herinnering: om de exacte namen te weten te komen van de eigenschappen die in de parameters [excluded_keys, included_keys] moeten worden opgenomen, moet het vooraf gedefinieerde woordenboek [taxpayer.__dict__] worden gebruikt;
  • regels 41-48: verbinding maken met de server en vervolgens het antwoord ophalen HTTP;
  • regels 44, 48:
    • de statische methode [requests.post] wordt gebruikt om een POST naar de server te verzenden;
    • de parameter met de naam [json] wordt gebruikt om aan te geven dat de inhoud van de POST een tekenreeks jSON is. Dit heeft twee gevolgen:
      • het object dat is toegewezen aan de parameter met de naam [json], in dit geval een lijst met woordenboeken, wordt omgezet in de tekenreeks jSON;
      • de header
Content-Type: application/json

wordt opgenomen in de headers HTTP van POST;

  • regel 59: het antwoord jSON van de server wordt gedeserialiseerd in het woordenboek [résultat];
  • regels 61-63: een eventuele foutmelding van de server wordt afgehandeld;
  • regel 65: de resultaten van de belastingberekening staan in een lijst met woordenboeken;
  • regels 67-69: deze resultaten worden gebruikt om de oorspronkelijke lijst met belastingplichtigen [taxpayers] bij te werken, die aanvankelijk via de methode op regel 28 was ontvangen;
  • regel 70: hier is de oorspronkelijke lijst met belastingplichtigen bijgewerkt met de resultaten van de belastingberekening;

28.3.2. Het hoofdscript [main]

Het hoofdscript [main] verandert als volgt: alleen de functie [thread_function], die wordt uitgevoerd door de door de client aangemaakte threads, wordt gewijzigd. De rest van de code blijft ongewijzigd.


# uitvoering van de laag [dao] in een thread
# taxpayers is een lijst met belastingplichtigen
def thread_function(dao: ImpôtsDaoWithHttpClient, logger: Logger, taxpayers: list):
    # logboek: start van de thread
    thread_name = threading.current_thread().name
    nb_taxpayers = len(taxpayers)
    # log
    logger.write(f"début du calcul de l'impôt des {nb_taxpayers} contribuables\n")
    # de belasting van de belastingplichtigen wordt berekend
    dao.calculate_tax_in_bulk_mode(taxpayers)
    # log
    logger.write(f"fin du calcul de l'impôt des {nb_taxpayers} contribuables\n")
  • regels 9-10: waar voorheen een lus was die achtereenvolgens elke belastingplichtige doorgeeft aan de methode [dao.calculate_tax], wordt hier slechts één keer de methode [dao.calculate_tax_in_bulk_mode] aangeroepen, waaraan alle belastingplichtigen worden doorgegeven;

28.3.3. Uitvoering van de client

We gaan de uitvoeringstijden van de versies vergelijken:

  • 7, waarbij voor elke belastingplichtige een afzonderlijke aanvraag HTTP wordt uitgevoerd;
  • 10 (deze versie), waarbij belastingplichtigen worden gebundeld in één enkele aanvraag HTTP;

Allereerst versie 6. Om de twee versies te vergelijken, stellen we de eigenschap [sleep_time] van de server in op nul, zodat er geen gedwongen wachttijd voor de threads is. De logbestanden van de client zijn als volgt:


2020-07-28 14:20:45.811347, Thread-1 : début du thread [Thread-1] avec 4 contribuable(s)
2020-07-28 14:20:45.811347, Thread-1 : début du calcul de l'impôt de {"id": 1, "marié": "oui", "enfants": 2, "salaire": 55555}

2020-07-28 14:20:45.913065, Thread-3 : fin du calcul de l'impôt de {"id": 11, "marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0}
2020-07-28 14:20:45.913065, Thread-3 : fin du thread [Thread-3]

De uitvoeringstijd van de client voor het berekenen van de belasting van 11 belastingplichtigen is dus [913065-811347= 101718], d.w.z. ongeveer 102 milliseconden.

Laten we hetzelfde doen met versie 10 (sleep_time van de server met nul). De logbestanden van de client zijn dan als volgt:


2020-07-28 14:25:31.871428, Thread-1 : début du calcul de l'impôt des 4 contribuables
2020-07-28 14:25:31.873594, Thread-2 : début du calcul de l'impôt des 3 contribuables
2020-07-28 14:25:31.877429, Thread-3 : début du calcul de l'impôt des 3 contribuables
2020-07-28 14:25:31.882855, Thread-4 : début du calcul de l'impôt des 1 contribuables
2020-07-28 14:25:31.930723, Thread-2 : {"réponse": {"results": [{"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0}, {"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-28 14:25:31.935958, Thread-4 : fin du calcul de l'impôt des 1 contribuables
2020-07-28 14:25:31.935958, Thread-1 : fin du calcul de l'impôt des 4 contribuables

De uitvoeringstijd van de client om de belasting van 11 belastingplichtigen te berekenen is dus [935958-871428= 64530 ns] (regel 8 – regel 1), d.w.z. ongeveer 65 milliseconden. Deze nieuwe versie 10 levert dus een winst op van ongeveer 57 % ten opzichte van versie 7.

28.3.4. Tests van de [dao]-laag van de client

Image

De [TestHttpClientDao]-test van de client van versie 10 komt sterk overeen met die van versie 7:


import unittest

from Logger import Logger


class TestHttpClientDao(unittest.TestCase):

    def test_1(self) -> None:
        from TaxPayer import TaxPayer

        # {'getrouwd': 'ja', 'kinderen': 2, 'salaris': 55555,
        # 'belasting': 2814, 'toeslag': 0, 'korting': 0, 'vermindering': 0, 'tarief': 0,14}
        taxpayer = TaxPayer().fromdict({"marié": "oui", "enfants": 2, "salaire": 55555})
        dao.calculate_tax_in_bulk_mode([taxpayer])
        # controle
        self.assertAlmostEqual(taxpayer.impôt, 2815, delta=1)
        self.assertEqual(taxpayer.décôte, 0)
        self.assertEqual(taxpayer.réduction, 0)
        self.assertAlmostEqual(taxpayer.taux, 0.14, delta=0.01)
        self.assertEqual(taxpayer.surcôte, 0)

    

if __name__ == '__main__':
    # de applicatie wordt geconfigureerd
    import config
    config = config.configure({})

    # logboek
    logger = Logger(config["logsFilename"])
    # opslaan in de configuratie
    config["logger"] = logger
    # de laag wordt opgehaald [dao]
    dao = config["layers"]["dao"]

    # de testmethoden worden uitgevoerd
    print("tests en cours...")
    unittest.main()
  • regel 14: in plaats van de methode [dao.calculate_tax] aan te roepen, wordt de methode [dao.calculate_tax_in_bulk_mode] aangeroepen, waaraan een lijst (met haakjes) van een belastingplichtige wordt doorgegeven;

Alle tests zijn geslaagd.