29. Exercício prático: versão 11
29.1. Introdução
Nas versões anteriores do aplicativo cliente/servidor de cálculo de impostos, a camada [métier], que implementa as regras de negócio desse cálculo, ficava no lado do servidor. Agora, propõe-se transferi-la para o lado do cliente. Qual é a vantagem? Parte do trabalho que era realizado pelo servidor será transferida para o lado do cliente. Se pensarmos na situação de um servidor consultado por N clientes, N cálculos de negócios do imposto serão realizados pelos clientes. Nas versões anteriores, o servidor realizava esses N cálculos de negócios. Como não realiza mais o cálculo de negócios, o servidor responderá mais rapidamente aos seus clientes e, assim, poderá atender a um número maior deles simultaneamente.
A arquitetura cliente/servidor passa a ser a seguinte:

- a camada [métier] [10] foi duplicada como [12] no cliente;
- um novo script [main2] [11] foi adicionado ao cliente;
O cliente web terá duas maneiras de calcular o imposto da lista de contribuintes encontrada em [3]:
- utilizar o método da versão anterior. Ele utiliza a camada [métier] [10] do servidor. O script [main] utilizará esse método;
- limitar-se a solicitar ao servidor os dados da administração fiscal [2-4] e, em seguida, utilizar a camada [métier] [12] local do cliente;
Compararemos o desempenho dos dois métodos.
29.2. O servidor web
A estrutura do servidor web será a seguinte:

- A pasta [http-servers/06] é inicialmente obtida por meio da cópia da pasta [http-servers/05]. De fato, vamos manter o que já foi desenvolvido na versão 10 anterior. Vamos simplesmente adicionar uma nova funcionalidade. Essa funcionalidade se concretiza com a presença de um novo controlador [get_admindata_controller] [1]. O outro controlador [calculate_tax_controller] nada mais é do que o antigo controlador [index_controller], que foi renomeado;
29.3. Configuração
O servidor oferecerá dois serviços URL:
- [/calculate-tax] para calcular o imposto de uma lista de contribuintes passada no corpo de um POST. Portanto, ele corresponde ao URL [/] da versão 10 anterior;
- [/get-admindata] gera a cadeia jSON com os dados da administração tributária;
A configuração [config] associa cada um desses URL ao controlador que o processa:
# dicionário dos controladores da aplicação web
import calculate_tax_controller, get_admindata_controller
controllers = {"calculate-tax": calculate_tax_controller, "get-admindata": get_admindata_controller}
config['controllers'] = controllers
29.4. O script principal [main]
O script principal [main] reestrutura o script [main] da versão anterior:
# espera-se um parâmetro mysql ou 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()
# configura-se a aplicação
import config
config = config.configure({'sgbd': sgbd})
# dependências
…
# recuperação de dados da administração fiscal
erreur = False
try:
# admindata será um dado de escopo da aplicação, somente para leitura
config["admindata"] = config["layers"]["dao"].get_admindata()
# registro de sucesso
logger.write("[serveur] connexion à la base de données réussie\n")
except ImpôtsError as ex:
# registra-se o erro
…
…
# se houver erro, a execução é interrompida
if erreur:
sys.exit(2)
# a aplicação Flask pode ser iniciada
app = Flask(__name__)
# controlador principal
def main_controller() -> tuple:
# recuperamos a ação solicitada
dummy, action=request.path.split('/')
logger = None
try:
# registrador
logger = Logger(config["logsFilename"])
# armazenando-a em uma configuração associada à thread
thread_config = {"logger": logger}
thread_name = threading.current_thread().name
config[thread_name] = {"config": thread_config}
# registra-se a solicitação
logger.write(f"[index] requête : {request}\n")
# interrompe-se o thread, caso tenha sido solicitado
sleep_time = config["sleep_time"]
if sleep_time != 0:
# a pausa é aleatória para que alguns threads sejam interrompidos e outros não
aléa = randint(0, 1)
if aléa == 1:
# registro antes da pausa
logger.write(f"[index] mis en pause du thread pendant {sleep_time} seconde(s)\n")
# pausa
time.sleep(sleep_time)
# a solicitação é executada pelo controlador de solicitações
controller = config['controllers'][action]
résultat, status_code = controller.execute(request, config)
# houve algum erro fatal?
if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
# envia-se um e-mail ao administrador do aplicativo
config_mail = config["adminMail"]
config_mail["logger"] = logger
SendAdminMail.send(config_mail, json.dumps(résultat, ensure_ascii=False))
# a resposta é registrada no log
logger.write(f"[index] {résultat}\n")
# envia-se a resposta
print(résultat)
return json_response(résultat, status_code)
except BaseException as erreur:
# registra-se o erro, se possível
if logger:
logger.write(f"[index] {erreur}")
# prepara-se a resposta para o cliente
résultat = {"réponse": {"erreurs": [f"{erreur}"]}}
# envia-se a resposta
return json_response(résultat, status.HTTP_500_INTERNAL_SERVER_ERROR)
finally:
# fecha-se o arquivo de logs, caso tenha sido aberto
if logger:
logger.close()
# cálculo do imposto
@app.route('/calculate-tax', methods=['POST'])
@auth.login_required
def calculate_tax():
# passa o controle para o controlador principal
return main_controller()
# obter os dados da administração fiscal
@app.route('/get-admindata', methods=['GET'])
@auth.login_required
def get_admindata():
# passa-se o controle para o controlador principal
return main_controller()
# somente controle
if __name__ == '__main__':
# inicialização do servidor
app.config.update(ENV="development", DEBUG=True)
app.run(threaded=True)
- linhas 88-93: a função [calculate_tax] processa o URL e o [/calculate-tax];
- linhas 95-100: a função [get_admindata] processa as funções URL e [/get-admindata];
- essas duas funções não realizam nenhuma ação por conta própria. Elas passam imediatamente o controle para o controlador principal [main_controller] das linhas 37-86;
- linhas 37-86: o controlador principal [main_controller] nada mais é do que a função [index] da versão anterior, com uma única diferença: enquanto a função [index] processava apenas um único URL, aqui a função [main_controller] processa dois URL. Portanto, é necessário que ela as encaminhe para um dos dois controladores [calculate_tax_controller, get_admin,data_controller];
- linhas 39-40: recupera-se a ação solicitada [calculate_tax] ou [get_admindata]. Essa informação está no caminho do URL [request.path]. Dependendo do caso, [request.path] corresponde a [/get-admindata] ou [/calculate_tax]. A divisão da linha 40 resultará em dois elementos:
- a string vazia para a parte que precede o /;
- o nome da ação solicitada para a parte que segue o /;
- linhas 62-63: uma vez recuperada a ação do URL, sabemos qual controlador usar para processar o URL. Essas informações estão na configuração [config];
29.5. Os controladores
O controlador [calculate_tax_controller] nada mais é do que o controlador [index_controller] da versão anterior.
Já o controlador [get_admindata_controller] é o seguinte:
from flask_api import status
from werkzeug.local import LocalProxy
def execute(request: LocalProxy, config: dict) -> tuple:
# retornando a resposta
return {"réponse": {"result": config["admindata"].asdict()}}, status.HTTP_200_OK
- o URL [/get-admindata] deve gerar a sequência jSON a partir dos dados da administração fiscal;
- linha 6: esses dados foram recuperados pelo script principal [main] e inseridos no dicionário [config] na forma de um objeto [AdminData]. Retorna-se o dicionário desse objeto;
29.6. Testes no Postman
Iniciamos o servidor web, o SGBD e o servidor de e-mails [hMailServer]. Em seguida, com um cliente Postman, calculamos o imposto de vários contribuintes:

No console do Postman, o diálogo cliente/servidor é o seguinte:
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…]}}
Agora, vamos solicitar o URL e o [/get-admindata] junto com um GET:

O diálogo cliente/servidor no console do Postman é o seguinte:
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. O cliente web


A pasta [http-clients/06] é obtida inicialmente por meio da cópia da pasta [http-clients/05]. O trabalho de modificação consiste essencialmente em:
- modificar a configuração [config_layers] para que ela passe a incluir uma camada [métier]. Anteriormente, ela possuía apenas uma camada [dao];
- adicionar um novo método à camada [dao];
- escrever um script [main2] que se baseará na camada [métier] do cliente para calcular o imposto dos contribuintes;
29.7.1. Configuração das camadas do cliente
A configuração das camadas ocorre em dois pontos:
- na configuração [config], que deve incluir, nas dependências do cliente, a pasta contendo a implementação da camada [métier]. Essa pasta já estava incluída nas dependências:
absolute_dependencies = [
# pastas do projeto
# 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, faixas
f"{root_dir}/impots/v05/entities",
# ImpôtsDaoWithHttpClient
f"{script_dir}/../services",
# scripts de configuração
script_dir,
# Logger
f"{root_dir}/impots/http-servers/02/utilities",
]
Em seguida, o arquivo [config_layers] deve ser modificado:
def configure(config: dict) -> dict:
# instanciação das camadas da aplicação
# camada [métier]
from ImpôtsMétier import ImpôtsMétier
métier = ImpôtsMétier()
# camada DAO
from ImpôtsDaoWithHttpClient import ImpôtsDaoWithHttpClient
dao = ImpôtsDaoWithHttpClient(config)
# configuramos as camadas
return {
"dao": dao,
"métier": métier
}
- linhas 4-6: instanciação da camada [métier];
- linhas 13-16: a camada [métier] é incluída no dicionário de camadas;
29.7.2. Implementação da camada [dao]

A camada [dao] apresentará a seguinte interface [InterfaceImpôtsDaoWithHttpClient]:
from abc import abstractmethod
from AbstractImpôtsDao import AbstractImpôtsDao
class InterfaceImpôtsDaoWithHttpClient(AbstractImpôtsDao):
# cálculo do imposto
@abstractmethod
def calculate_tax_in_bulk_mode(self, taxpayers: list):
pass
- linha 5: a interface [InterfaceImpôtsDaoWithHttpClient] herda da classe abstrata [AbstractImpôtsDao], que gerencia o acesso ao sistema de arquivos do cliente. Vale lembrar que ela possui um método abstrato [get_admindata];
- linhas 7-10: o método [calculate_tax_in_bulk_mode], que definimos na versão anterior, permite o cálculo do imposto de uma lista de contribuintes;
Essa interface é implementada pela seguinte classe [ImpôtsDaoWithHttpClient]:
# importações
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):
# fabricante
def __init__(self, config: dict):
# inicialização do pai
AbstractImpôtsDao.__init__(self, config)
# armazenamento dos elementos da configuração
# configuração geral
self.__config = config
# servidor
self.__config_server = config["server"]
# modo de depuração
self.__debug = config["debug"]
# registrador
self.__logger = None
def get_admindata(self) -> AdminData:
# permitir que as exceções sejam propagadas
# URL do serviço
config_server = self.__config_server
config_services = config_server['url_services']
url_service = f"{config_server['urlServer']}{config_services['get-admindata']}"
# conexão
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 depuração?
if self.__debug:
# registrador
if not self.__logger:
self.__logger = self.__config['logger']
# está registrando
self.__logger.write(f"{response.text}\n")
# código de status
status_code = response.status_code
# resultado na forma de um dicionário
résultat = json.loads(response.text)
# se o código de status for diferente de 200 OK
if status_code != status.HTTP_200_OK:
raise ImpôtsError(58, résultat['réponse']['erreurs'])
# retorna o resultado (um dicionário)
return AdminData().fromdict(résultat["réponse"]["result"])
# cálculo do imposto em modo em lote
def calculate_tax_in_bulk_mode(self, taxpayers: list):
# permite que as exceções sejam reportadas
…
- linha 13: a classe [ImpôtsDaoWithHttpClient] implementa a interface [InterfaceImpôtsDaoWithHttpClient]. Portanto, ela deriva da classe [AbstractImpôtsDao];
- linhas 65-66: o método [calculate_tax_in_bulk_mode] analisado na versão anterior;
- linhas 29-62: o método [get_admindata], que a classe pai [AbstractImpôtsDao] declarou como abstrato. Portanto, ele é implementado na classe filha;
- linhas 33-35: determina-se o URL do serviço web que o método [get-admindata] deve consultar. Esses URL do serviço são definidos na configuração [config] do cliente:
# o servidor de cálculo do imposto
"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"
}
},
- (continuação)
- linhas 9-12: os dois URL do servidor web;
- linhas 37-44: o URL de serviço é consultado de forma síncrona;
- linhas 46-42: se a configuração assim exigir, a resposta do servidor é registrada;
- linha 57: sabe-se que o servidor enviou uma string jSON de um dicionário;
- linhas 58-60: se o status HTTP da resposta não for 200, então é lançada uma exceção;
- linhas 61-62: retorna-se o objeto [AdminData] que encapsula os dados da administração fiscal enviados pelo servidor;
29.8. Os scripts [main, main2]
O script [main] é o da versão anterior. Ele utiliza o método [calculate_tax_in_bulk_mode] da camada [dao] e, portanto, utiliza a camada [métier] do servidor;
O script [main2] faz o mesmo que o script [main], mas utilizando a camada [métier] do cliente:
# configuração do aplicativo
import config
config = config.configure({})
# dependências
from ImpôtsError import ImpôtsError
from Logger import Logger
logger = None
# código
try:
# registrador
logger = Logger(config["logsFilename"])
# armazenando na configuração
config["logger"] = logger
# log de início
logger.write("début du calcul de l'impôt des contribuables\n")
# recuperamos a camada [dao]
dao = config["layers"]["dao"]
# recuperam-se os contribuintes
taxpayers = dao.get_taxpayers_data()["taxpayers"]
# dos contribuintes?
if not taxpayers:
raise ImpôtsError(36, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
# recupera-se os dados da administração fiscal
admindata = dao.get_admindata()
# cálculo do imposto dos contribuintes pela camada [métier]
métier = config['layers']['métier']
for taxpayer in taxpayers:
métier.calculate_tax(taxpayer, admindata)
# os resultados são gravados no arquivo jSON
dao.write_taxpayers_results(taxpayers)
# exceto BaseException como erro:
# # exibição do erro
# print(f"Ocorreu o seguinte erro: {erro}")
finally:
# fechando o logger
if logger:
# log de encerramento
logger.write("fin du calcul de l'impôt des contribuables\n")
# encerramento do registrador de logs
logger.close()
# concluído
print("Travail terminé...")
- linhas 26-27: os dados da administração fiscal são obtidos do servidor;
- linhas 28-31: em seguida, o cálculo do imposto dos contribuintes é feito localmente;
29.9. Testes do cliente
Em cada um dos scripts [main, main2], registramos o início e o fim do script. Assim, poderemos calcular o tempo de execução do script. Vamos fazer algumas previsões:
- o script [main] da versão anterior:
- cria N threads que são executadas simultaneamente;
- cada thread processa um lote de contribuintes, cujo imposto é calculado por meio de uma única consulta ao servidor;
- como as N threads são executadas simultaneamente, a consulta N+1 é enviada antes que a consulta N tenha recebido sua resposta. Assim, as N consultas custam mais do que uma única consulta, mas provavelmente não muito mais. Além disso, há 11 (o número de contribuintes) cálculos de negócio no servidor;
- o script [main2] desta versão:
- faz uma única consulta ao servidor;
- realiza 11 cálculos de negócios localmente no cliente;
Os cálculos de negócio terão a mesma duração, seja no servidor ou no cliente. A diferença, portanto, estará nas consultas. É de se esperar, então, que o tempo de execução do [main] seja ligeiramente superior ao do [main2].
Iniciamos o servidor da versão 11, o SGBD e o servidor de e-mails [hMailServer]. No lado do servidor, definimos o parâmetro [sleep_time] como zero para que os dois testes sejam executados nas mesmas condições.
Execução 1 [main]
A execução do [main] gera os seguintes logs:
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
A duração da execução foi de [051214-016079] nanossegundos (linha 17 – linha 1), ou seja, 35 milissegundos e 135 nanossegundos.
Percebe-se que, entre a primeira solicitação feita ao servidor e a última resposta recebida pelo cliente, há o mesmo tempo [051214-016079] (linha 15 – linha 1), ou seja, 35 milissegundos e 135 nanossegundos.
Execução 2 [main2]
A execução de [main2] gera os seguintes 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
A duração da execução foi de [349975-303520] nanossegundos (linha 3 – linha 1), ou seja, 46 milissegundos e 455 nanossegundos. De forma totalmente inesperada, [main] é mais rápido que [main2].
Vemos que a única consulta de [main2] durou [345084-303520] (linha 2 – linha 1), ou seja, 41 milissegundos e 564 nanossegundos. O cálculo do imposto levou, em seguida, [349975-345084] (linha 3 – linha 2), ou seja, 4 milissegundos e 91 nanossegundos. É a consulta HTTP que determina o tempo de execução. Surpreendentemente, vemos aqui que a única consulta de [main2] demorou mais tempo ([41 millisecondes]) do que as quatro consultas simultâneas de [main] e [35 millisecondes].
No lado do servidor, os logs são os seguintes:
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}}}
- linha 5: a primeira solicitação do cliente [main];
- linha 14: a última resposta ao cliente [main]. Há 6 milissegundos e 647 nanossegundos entre as duas;
- linhas 15-16: a única solicitação do cliente [main2]. A resposta é instantânea;