29. Ejercicio de aplicación: version 11
29.1. Introducción
En las versiones anteriores de la aplicación cliente/servidor de cálculo de impuestos, la capa [métier] que implementa las reglas de negocio de este cálculo se encontraba en el lado del servidor. Ahora nos proponemos trasladarla al lado del cliente. ¿Cuál es la ventaja? Parte del trabajo que realizaba el servidor se trasladará al lado del cliente. Si pensamos en la situación de un servidor al que se le envían N clients, los N cálculos de negocio del impuesto serán realizados por los clients. En las versiones anteriores, el servidor realizaba estos N cálculos de negocio. Al dejar de realizar el cálculo de negocio, el servidor responderá más rápidamente a sus clients y podrá atender a más de ellos simultáneamente.
La arquitectura cliente/servidor queda así:

- la capa [métier] [10] se ha duplicado como [12] en el cliente;
- se ha añadido un nuevo script [main2] [11] al cliente;
El cliente web tendrá dos formas de calcular el impuesto de la lista de contribuyentes que se encuentra en [3]:
- utilizar el método del version anterior. Este utiliza la capa [métier] [10] del servidor. El script [main] utilizará este método;
- limitarse a solicitar al servidor los datos de la administración tributaria [2-4] y, a continuación, utilizar la capa [métier] [12] local del cliente;
Compararemos el rendimiento de ambos métodos.
29.2. El servidor web
La estructura del servidor web será la siguiente:

- La carpeta [http-servers/06] se obtiene inicialmente mediante una copia de la carpeta [http-servers/05]. De hecho, se conservarán los logros del anterior version 10. Simplemente se le añadirá una nueva funcionalidad. Esta se materializa en la presencia de un nuevo controlador [get_admindata_controller] [1]. El otro controlador [calculate_tax_controller] no es otro que el antiguo controlador [index_controller], al que se le ha cambiado el nombre;
29.3. Configuración
El servidor ofrecerá dos servicios URL:
- [/calculate-tax] para calcular el impuesto de una lista de contribuyentes pasada en el cuerpo de un POST. Por lo tanto, corresponde al URL [/] del version 10 anterior;
- [/get-admindata] genera la cadena jSON con los datos de la administración tributaria;
La configuración [config] asocia cada uno de estos URL al controlador que lo procesa:
# diccionario de controladores de la aplicación web
import calculate_tax_controller, get_admindata_controller
controllers = {"calculate-tax": calculate_tax_controller, "get-admindata": get_admindata_controller}
config['controllers'] = controllers
29.4. El script principal [main]
El script principal [main] reestructura el script [main] del anterior version:
# se espera un parámetro mysql o pgres
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()
# se configura la aplicación
import config
config = config.configure({'sgbd': sgbd})
# dependencias
…
# Recuperación de datos de la administración tributaria
erreur = False
try:
# admindata será un dato de ámbito de aplicación de solo lectura
config["admindata"] = config["layers"]["dao"].get_admindata()
# registro de éxito
logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
# se registra el error
…
…
# si se ha producido un error, se detiene
if erreur:
sys.exit(2)
# la aplicación Flask puede iniciarse
app = Flask(__name__)
# controlador principal
def main_controller() -> tuple:
# se recupera la acción solicitada
dummy, action=request.path.split('/')
logger = None
try:
# registrador
logger = Logger(config["logsFilename"])
# se almacena en una config asociada al hilo
thread_config = {"logger": logger}
thread_name = threading.current_thread().name
config[thread_name] = {"config": thread_config}
# se registra la solicitud
logger.write(f"[index] requête : {request}\n")
# se interrumpe el hilo si así se ha solicitado
sleep_time = config["sleep_time"]
if sleep_time != 0:
# la pausa es aleatoria para que algunos subprocesos se interrumpan y otros no
aléa = randint(0, 1)
if aléa == 1:
# registro antes de la pausa
logger.write(f"[index] mis en pause du thread pendant {sleep_time} seconde(s)\n")
# pausa
time.sleep(sleep_time)
# el controlador de la solicitud ejecuta la solicitud
controller = config['controllers'][action]
résultat, status_code = controller.execute(request, config)
# ¿Se ha producido un error fatal?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# se envía un correo electrónico al administrador de la aplicación
config_mail = config["adminMail"]
config_mail["logger"] = logger
SendAdminMail.send(config_mail, json.dumps(résultat, ensure_ascii=False))
# se registra la respuesta
logger.write(f"[index] {résultat}\n")
# se envía la respuesta
print(résultat)
return json_response(résultat, status_code)
except BaseException as erreur:
# se registra el error si es posible
if logger:
logger.write(f"[index] {erreur}")
# se prepara la respuesta para el cliente
résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
# se envía la respuesta
return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
finally:
# se cierra el archivo de registros si se ha abierto
if logger:
logger.close()
# cálculo del impuesto
@app.route('/calculate-tax', methods=['POST'])
@auth.login_required
def calculate_tax():
# se pasa el control al controlador principal
return main_controller()
# Obtener los datos de la administración tributaria
@app.route('/get-admindata', methods=['GET'])
@auth.login_required
def get_admindata():
# se pasa el control al controlador principal
return main_controller()
# solo control
if __name__ == '__main__':
# se inicia el servidor
app.config.update(ENV="development", DEBUG=True)
app.run(threaded=True)
- líneas 88-93: la función [calculate_tax] procesa el URL [/calculate-tax];
- líneas 95-100: la función [get_admindata] procesa URL y [/get-admindata];
- estas dos funciones no hacen nada por sí mismas. Ceden inmediatamente el control al controlador principal [main_controller] de las líneas 37-86;
- líneas 37-86: el controlador principal [main_controller] no es más que la función [index] de la anterior version, con una pequeña diferencia: mientras que la función [index] solo procesaba una única URL, aquí [main_controller] procesa dos URL. Por lo tanto, debe hacer que uno de los dos controladores [calculate_tax_controller, get_admin,data_controller] las procese;
- líneas 39-40: se recupera la acción solicitada [calculate_tax] o [get_admindata]. Esta información se encuentra en la ruta de URL [request.path]. Según el caso, [request.path] equivale a [/get-admindata] o [/calculate_tax]. La división de la línea 40 dará dos elementos:
- la cadena vacía para la parte que precede a la barra (/);
- el nombre de la acción solicitada para la parte que sigue a la barra (/);
- líneas 62-63: una vez recuperada la acción de URL, sabemos qué controlador utilizar para procesar URL. Esta información se encuentra en la configuración de [config];
29.5. Los controladores
El controlador [calculate_tax_controller] no es otro que el controlador [index_controller] del version anterior.
El controlador [get_admindata_controller] es el siguiente:
from flask_api import status
from werkzeug.local import LocalProxy
def execute(request: LocalProxy, config: dict) -> tuple:
# se devuelve la respuesta
return {"réponse": {"result": config["admindata"].asdict()}}, status.HTTP_200_OK
- el URL [/get-admindata] debe devolver la cadena jSON de los datos de la administración tributaria;
- línea 6: estos han sido recuperados por el script principal [main] y colocados en el diccionario [config] en forma de un objeto [AdminData]. Se devuelve el diccionario de este objeto;
29.6. Pruebas con Postman
Se inician el servidor web, el SGBD y el servidor de correo [hMailServer]. A continuación, con un cliente Postman, se calcula el impuesto de varios contribuyentes:

En la consola de Postman, el diálogo cliente/servidor es el siguiente:
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…]}}
Ahora solicitemos el URL y el [/get-admindata] junto con un GET:

El diálogo cliente/servidor en la consola de Postman es el siguiente:
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. El cliente web


La carpeta [http-clients/06] se obtiene inicialmente copiando la carpeta [http-clients/05]. El trabajo de modificación consiste esencialmente en:
- modificar la configuración [config_layers] para que ahora incluya una capa [métier]. Anteriormente solo tenía una capa [dao];
- añadir un nuevo método a la capa [dao];
- escribir un script [main2] que se basará en la capa [métier] del cliente para calcular los impuestos de los contribuyentes;
29.7.1. Configuración de las capas del cliente
La configuración de las capas se realiza en dos puntos:
- en la configuración [config], que debe incluir en las dependencias del cliente la carpeta que contiene la implementación de la capa [métier]. Esta carpeta ya estaba incluida en las dependencias:
absolute_dependencies = [
# carpetas del proyecto
# 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, tramos
f"{root_dir}/impots/v05/entities",
# ImpôtsDaoWithHttpClient
f"{script_dir}/../services",
# scripts de configuración
script_dir,
# Registrador
f"{root_dir}/impots/http-servers/02/utilities",
]
A continuación, se debe modificar el archivo [config_layers]:
def configure(config: dict) -> dict:
# instanciación de las capas de la aplicación
# capa [métier]
from ImpôtsMétier import ImpôtsMétier
métier = ImpôtsMétier()
# capa dao
from ImpôtsDaoWithHttpClient import ImpôtsDaoWithHttpClient
dao = ImpôtsDaoWithHttpClient(config)
# se devuelve la configuración de las capas
return {
"dao": dao,
"métier": métier
}
- líneas 4-6: instanciación de la capa [métier];
- líneas 13-16: la capa [métier] se incluye en el diccionario de capas;
29.7.2. Implementación de la capa [dao]

La capa [dao] presentará la siguiente interfaz [InterfaceImpôtsDaoWithHttpClient]:
from abc import abstractmethod
from AbstractImpôtsDao import AbstractImpôtsDao
class InterfaceImpôtsDaoWithHttpClient(AbstractImpôtsDao):
# cálculo del impuesto
@abstractmethod
def calculate_tax_in_bulk_mode(self, taxpayers: list):
pass
- línea 5: la interfaz [InterfaceImpôtsDaoWithHttpClient] hereda de la clase abstracta [AbstractImpôtsDao], que gestiona el acceso al sistema de archivos del cliente. Recordemos que tiene un método abstracto [get_admindata];
- líneas 7-10: el método [calculate_tax_in_bulk_mode] que hemos definido en la anterior version permite calcular el impuesto de una lista de contribuyentes;
Esta interfaz está implementada por la siguiente clase [ImpôtsDaoWithHttpClient]:
# importaciones
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):
# constructor
def __init__(self, config: dict):
# inicialización del padre
AbstractImpôtsDao.__init__(self, config)
# almacenamiento de elementos de configuración
# config general
self.__config = config
# servidor
self.__config_server = config["server"]
# modo debug
self.__debug = config["debug"]
# registrador
self.__logger = None
def get_admindata(self) -> AdminData:
# se permite que las excepciones se propaguen
# url de servicio
config_server = self.__config_server
config_services = config_server['url_services']
url_service = f"{config_server['urlServer']}{config_services['get-admindata']}"
# conexión
if config_server['authBasic']:
response = requests.get(url_service,
auth=(
config_server["user"]["login"],
config_server["user"]["password"]))
else:
response = requests.get(url_service)
# modo debug ?
if self.__debug:
# iniciador de sesión
if not self.__logger:
self.__logger = self.__config['logger']
# iniciar sesión
self.__logger.write(f"{response.text}\n")
# código de estado
status_code = response.status_code
# resultado en forma de diccionario
résultat = json.loads(response.text)
# si el código de estado es distinto de 200 OK
if status_code != status.HTTP_200_OK:
raise ImpôtsError(58, résultat['réponse']['erreurs'])
# se devuelve el resultado (un diccionario)
return AdminData().fromdict(résultat["réponse"]["result"])
# cálculo del impuesto en modo masivo
def calculate_tax_in_bulk_mode(self, taxpayers: list):
# se permiten las excepciones
…
- línea 13: la clase [ImpôtsDaoWithHttpClient] implementa la interfaz [InterfaceImpôtsDaoWithHttpClient]. Por lo tanto, deriva de la clase [AbstractImpôtsDao];
- líneas 65-66: el método [calculate_tax_in_bulk_mode] estudiado en la clase version anterior;
- líneas 29-62: el método [get_admindata] que la clase padre [AbstractImpôtsDao] ha declarado como abstracto. Por lo tanto, se implementa en la clase hija;
- líneas 33-35: se determina el URL del servicio web al que debe consultar el método [get-admindata]. Estos URL del servicio se definen en la configuración [config] del cliente:
# el servidor de cálculo de impuestos
"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"
}
},
- (continuación)
- líneas 9-12: los dos URL del servidor web;
- líneas 37-44: se consulta de forma sincrónica el URL de servicio;
- líneas 46-42: si la configuración lo requiere, se registra la respuesta del servidor;
- línea 57: sabemos que el servidor ha enviado una cadena jSON de un diccionario;
- líneas 58-60: si el estado HTTP de la respuesta no es 200, se lanza una excepción;
- líneas 61-62: se devuelve el objeto [AdminData] que encapsula los datos de la administración tributaria enviados por el servidor;
29.8. Los scripts [main, main2]
El script [main] es el del anterior version. Utiliza el método [calculate_tax_in_bulk_mode] de la capa [dao] y, por lo tanto, utiliza la capa [métier] del servidor;
El script [main2] hace lo mismo que el script [main], pero utilizando la capa [métier] del cliente:
# configuración de la aplicación
import config
config = config.configure({})
# dependencias
from ImpôtsError import ImpôtsError
from Logger import Logger
logger = None
# código
try:
# registrador
logger = Logger(config["logsFilename"])
# se almacena en el config
config["logger"] = logger
# registro de inicio
logger.write("début du calcul de l'impôt des contribuables\n")
# se recupera la capa [dao]
dao = config["layers"]["dao"]
# se recuperan los contribuyentes
taxpayers = dao.get_taxpayers_data()["taxpayers"]
#de los contribuyentes?
if not taxpayers:
raise ImpôtsError(36, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
# se recuperan los datos de la administración tributaria
admindata = dao.get_admindata()
# cálculo del impuesto de los contribuyentes mediante la capa [métier]
métier = config['layers']['métier']
for taxpayer in taxpayers:
métier.calculate_tax(taxpayer, admindata)
# se guardan los resultados en el archivo jSON
dao.write_taxpayers_results(taxpayers)
# excepto BaseException as error:
# # visualización del error
# print(f"Se ha producido el siguiente error: {error}")
finally:
# se cierra el registrador
if logger:
# registro de fin
logger.write("fin du calcul de l'impôt des contribuables\n")
# cierre del registrador
logger.close()
# se ha completado
print("Travail terminé...")
- líneas 26-27: se recuperan los datos de la administración tributaria del servidor;
- líneas 28-31: a continuación, el cálculo del impuesto de los contribuyentes se realiza localmente;
29.9. Pruebas del cliente
En cada uno de los scripts [main, main2] se registra el inicio y el final del script. De este modo, se podrá calcular la duración de la ejecución del script. Hagamos algunas previsiones:
- el script [main] del anterior version:
- crea N subprocesos que se ejecutan simultáneamente;
- cada subproceso procesa un lote de contribuyentes cuyo impuesto se calcula mediante una única consulta al servidor;
- dado que los N subprocesos se ejecutan simultáneamente, la solicitud N+1 se lanza antes de que la solicitud N haya recibido su respuesta. Así, las N solicitudes cuestan más que una sola solicitud, pero probablemente no mucho más. Además, hay 11 (el número de contribuyentes) cálculos de negocio en el servidor;
- el script [main2] de este version:
- realiza una única consulta al servidor;
- realiza 11 cálculos de negocio localmente en el cliente;
Los cálculos de negocio tendrán la misma duración tanto en el servidor como en el cliente. La diferencia se dará entonces en las consultas. Por lo tanto, cabe esperar que la duración de ejecución de [main] sea ligeramente superior a la de [main2].
Iniciamos el servidor de version 11, el de SGBD y el servidor de correo [hMailServer]. En el lado del servidor, se establece el parámetro [sleep_time] en cero para que ambas pruebas se ejecuten en las mismas condiciones.
Ejecución 1 [main]
La ejecución de [main] genera los siguientes registros:
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
La duración de la ejecución fue de [051214-016079] nanosegundos (línea 17 – línea 1), es decir, 35 milisegundos y 135 nanosegundos.
Se observa que entre la primera solicitud realizada al servidor y la última respuesta recibida por el cliente, transcurre el mismo tiempo [051214-016079] (línea 15 – línea 1), 35 milisegundos y 135 nanosegundos.
Ejecución 2 [main2]
La ejecución de [main2] genera los siguientes registros:
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
La duración de la ejecución fue de [349975-303520] nanosegundos (línea 3 - línea 1), es decir, 46 milisegundos y 455 nanosegundos. De forma totalmente inesperada, [main] es más rápido que [main2].
Se observa que la única consulta de [main2] duró [345084-303520] (línea 2 – línea 1), es decir, 41 milisegundos y 564 nanosegundos. El cálculo del impuesto duró a continuación [349975-345084] (línea 3 – línea 2), es decir, 4 milisegundos y 91 nanosegundos. Es la consulta HTTP la que determina la duración de la ejecución. Sorprendentemente, vemos aquí que la única solicitud de [main2] duró más tiempo que las cuatro solicitudes simultáneas de [main] y [35 millisecondes].
En el lado del servidor, los registros son los siguientes:
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}}}
- línea 5: la primera solicitud del cliente [main];
- línea 14: la última respuesta al cliente [main]. Hay 6 milisegundos y 647 nanosegundos entre ambas;
- líneas 15-16: la única solicitud del cliente [main2]. La respuesta es instantánea;