29. Ejercicio práctico: versión 11
29.1. Introducción
En las versiones anteriores de la aplicación cliente/servidor para el 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 se propone trasladarla al lado del cliente. ¿Cuál es el beneficio? Parte del trabajo que realizaba el servidor se trasladará al lado del cliente. Si pensamos en la situación de un servidor al que consultan N clientes, los N cálculos de negocio del impuesto serán realizados por los clientes. 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 clientes y, por lo tanto, 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 agregado 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 de la versión 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 luego 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:

- El archivo [http-servers/06] se obtiene inicialmente al copiar el archivo [http-servers/05]. De hecho, se conservarán las características de la versión 10 anterior. Simplemente se le agregará una nueva funcionalidad. Esta se materializa con la presencia de un nuevo controlador [get_admindata_controller] [1]. El otro controlador [calculate_tax_controller] no es más 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 [/] de la versión 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] de la versión anterior:
# 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 alcance 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 hubo 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 configuración 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 hilos 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 consulta ejecuta la consulta
controller = config['controllers'][action]
résultat, status_code = controller.execute(request, config)
# ¿Hubo 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 había abierto
if logger:
logger.close()
# cálculo del impuesto
@app.route('/calculate-tax', methods=['POST'])
@auth.login_required
def calculate_tax():
# se le pasa el control al controlador principal
return main_controller()
# se obtienen 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 y el [/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 versión anterior, con una sola 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] procese estos archivos;
- 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]. Dependiendo del caso, [request.path] equivale a [/get-admindata] o a [/calculate_tax]. La división de la línea 40 dará como resultado 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, se sabe 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] de la versión 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 y el [/get-admindata] deben generar la cadena jSON a partir de los datos de la administración tributaria;
- línea 6: estos datos fueron recuperados por el script principal [main] y almacenados en el diccionario [config] en forma de un objeto [AdminData]. Se devuelve el diccionario de este objeto;
29.6. Pruebas con Postman
Se inicia el servidor web, el SGBD y el servidor de correo [hMailServer]. Luego, con un cliente Postman, se calcula el impuesto de varios contribuyentes:

En la consola de Postman, el diálogo entre el cliente y el 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 [/get-admindata] junto con un GET:

El diálogo entre el cliente y el 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


El archivo [http-clients/06] se obtiene inicialmente al copiar el archivo [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];
- agregar un nuevo método a la capa [dao];
- escribir un script [main2] que se basará en la capa [métier] del cliente para calcular el impuesto 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",
]
Luego, 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 administra el acceso al sistema de archivos del cliente. Recordemos que cuenta con un método abstracto [get_admindata];
- líneas 7-10: el método [calculate_tax_in_bulk_mode] que definimos en la versión anterior 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 elemento principal
AbstractImpôtsDao.__init__(self, config)
# almacenamiento de elementos de la configuración
# configuración general
self.__config = config
# servidor
self.__config_server = config["server"]
# modo de depuración
self.__debug = config["debug"]
# registrador
self.__logger = None
def get_admindata(self) -> AdminData:
# se permite que las excepciones se propaguen
# URL del 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 de depuración?
if self.__debug:
# registrador
if not self.__logger:
self.__logger = self.__config['logger']
# se registra
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 permite que se reporten 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] analizado en la versión 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 manera 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, entonces 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 de la versión anterior. 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:
# se configura 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 guarda en la configuración
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 registran los resultados en el archivo jSON
dao.write_taxpayers_results(taxpayers)
# excepto BaseException como error:
# # visualización del error
# print(f"Se produjo 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 terminado
print("Travail terminé...")
- líneas 26-27: se recuperan los datos de la administración tributaria del servidor;
- líneas 28-31: luego, 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 esta manera, se podrá calcular el tiempo de ejecución del script. Hagamos algunas predicciones:
- el script [main] de la versión anterior:
- crea N subprocesos que se ejecutan simultáneamente;
- cada hilo procesa un lote de contribuyentes, para los cuales calcula el impuesto mediante una única consulta al servidor;
- debido a que los N hilos se ejecutan simultáneamente, la consulta N+1 se lanza antes de que la consulta N haya recibido su respuesta. Por lo tanto, las N consultas son más costosas que una sola consulta, 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 esta versión:
- realiza una sola consulta al servidor;
- realiza 11 cálculos de negocio localmente en el cliente;
Los cálculos de negocio tendrán la misma duración, ya sea en el servidor o en el cliente. La diferencia, por lo tanto, se dará en las consultas. Por lo tanto, cabe esperar que el tiempo de ejecución de [main] sea ligeramente mayor que el de [main2].
Iniciamos el servidor de la versión 11, el SGBD y el servidor de correo [hMailServer]. En el lado del servidor, establecemos el parámetro [sleep_time] en cero para que ambas pruebas se ejecuten en las mismas condiciones.
Ejecución 1 de [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 enviada al servidor y la última respuesta recibida por el cliente, transcurre el mismo tiempo: [051214-016079] (línea 15 – línea 1), es decir, 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 manera 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 tardó luego [349975-345084] (línea 3 – línea 2), es decir, 4 milisegundos y 91 nanosegundos. Es la consulta HTTP la que determina el tiempo de ejecución. Sorprendentemente, aquí vemos que la única consulta de [main2] duró más tiempo que las cuatro consultas simultáneas de [main] y [35 millisecondes].
Por parte 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;