Skip to content

29. تمرين تطبيقي: الإصدار 11

29.1. مقدمة

في الإصدارات السابقة من تطبيق حساب الضرائب بين العميل والخادم، كانت طبقة [منطق الأعمال] التي تنفذ قواعد الأعمال لهذا الحساب موجودة على جانب الخادم. نقترح الآن نقلها إلى جانب العميل. ما هي الفائدة من ذلك؟ سيتم نقل بعض الأعمال التي كان يقوم بها الخادم سابقًا إلى جانب العميل. لنفترض سيناريو يتم فيه استعلام الخادم من قبل N عميل؛ سيتم إجراء N عملية حساب ضريبي من قبل العملاء. في الإصدارات السابقة، كان الخادم يقوم بهذه العمليات الحسابية N. ونظرًا لأنه لم يعد يقوم بالحسابات التجارية، سيستجيب الخادم بشكل أسرع لعملائه وبالتالي سيكون قادرًا على خدمة عدد أكبر منهم في وقت واحد.

تصبح بنية العميل/الخادم كما يلي:

Image

  • تم نسخ طبقة [الأعمال] [10] [12] على العميل؛
  • تمت إضافة برنامج نصي جديد [main2] [11] إلى العميل؛

سيكون لدى عميل الويب طريقتان لحساب الضريبة لقائمة دافعي الضرائب الموجودة في [3]:

  • استخدم الطريقة الموجودة في الإصدار السابق. فهي تستخدم طبقة [الأعمال] الخاصة بالخادم [10]. وسيستخدم البرنامج النصي [الرئيسي] هذه الطريقة؛
  • ما عليك سوى طلب بيانات سلطة الضرائب من الخادم [2-4] ثم استخدام طبقة [الأعمال] من جانب العميل [12]؛

سنقارن أداء الطريقتين.

29.2. خادم الويب

ستكون بنية دليل خادم الويب كما يلي:

Image

  • يتم إنشاء الدليل [http-servers/06] في البداية عن طريق نسخ الدليل [http-servers/05]. سنحتفظ بالفعل بميزات الإصدار 10 السابق. سنقوم ببساطة بإضافة ميزة جديدة إليه. يتم تنفيذ ذلك من خلال وجود وحدة تحكم جديدة [get_admindata_controller] [1]. والمحرك الآخر [calculate_tax_controller] ليس سوى المحرك القديم [index_controller] الذي تم تغيير اسمه؛

29.3. التكوين

سيقدم الخادم عنواني URL للخدمة:

  • [/calculate-tax] لحساب الضريبة لقائمة من دافعي الضرائب التي تم تمريرها في نص طلب POST. وبالتالي، فإنه يتوافق مع عنوان URL [/] من الإصدار 10 السابق؛
  • [/get-admindata] يعرض سلسلة JSON لبيانات إدارة الضرائب؛

يربط التكوين [config] كل عنوان URL من هذه العناوين بوحدة التحكم التي تتولى معالجته:

1
2
3
4
    #  web application controller dictionary
    import calculate_tax_controller, get_admindata_controller
    controllers = {"calculate-tax": calculate_tax_controller, "get-admindata": get_admindata_controller}
    config['controllers'] = controllers

29.4. البرنامج النصي الرئيسي [main]

يعيد البرنامج النصي الرئيسي [main] هيكلة البرنامج النصي [main] من الإصدار السابق:

#  a mysql or pgres parameter is expected
import sys
syntaxe = f"{sys.argv[0]} mysql / pgres"
erreur = len(sys.argv) != 2
if not erreur:
    sgbd = sys.argv[1].lower()
    erreur = sgbd != "mysql" and sgbd != "pgres"
if erreur:
    print(f"syntaxe : {syntaxe}")
    sys.exit()

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

#  dependencies


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

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

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

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

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

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

#  hand only
if __name__ == '__main__':
    #  start the server
    app.config.update(ENV="development", DEBUG=True)
    app.run(threaded=True)
  • الأسطر 88–93: تتولى الدالة [calculate_tax] معالجة عنوان URL [/calculate-tax]؛
  • الأسطر 95–100: تعالج الدالة [get_admindata] عنوان URL [/get-admindata]؛
  • هاتان الدالتان لا تقومان بأي شيء بمفردهما. فهما تسلمان التحكم فورًا إلى وحدة التحكم الرئيسية [main_controller] في الأسطر 37–86؛
  • الأسطر 37–86: وحدة التحكم الرئيسية [main_controller] ليست سوى دالة [index] من الإصدار السابق، مع اختلاف بسيط واحد: في حين أن دالة [index] كانت تتعامل مع عنوان URL واحد فقط، فإن [main_controller] هنا تتعامل مع عنواني URL. لذلك يجب أن تقوم بإرسالهما إلى إحدى وحدتي التحكم [calculate_tax_controller، get_admin_data_controller]؛
  • السطران 39–40: نستخرج الإجراء المطلوب [calculate_tax] أو [get_admindata]. توجد هذه المعلومات في مسار عنوان URL [request.path]. وحسب الحالة، يكون [request.path] إما [/get-admindata] أو [/calculate_tax]. سيؤدي التقسيم في السطر 40 إلى الحصول على عنصرين:
    • السلسلة الفارغة للجزء الذي يسبق /؛
    • اسم الإجراء المطلوب للجزء الذي يلي /؛
  • السطران 62-63: بمجرد استرداد إجراء URL، نعرف وحدة التحكم التي يجب استخدامها لمعالجة URL. توجد هذه المعلومات في التكوين [config]؛

29.5. وحدات التحكم

[calculate_tax_controller] ليس سوى [index_controller] من الإصدار السابق.

وحدة التحكم [get_admindata_controller] هي كما يلي:

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

def execute(request: LocalProxy, config: dict) -> tuple:
    #  we return the answer
    return {"réponse": {"result": config["admindata"].asdict()}}, status.HTTP_200_OK
  • يجب أن يعرض الرابط [/get-admindata] سلسلة JSON لبيانات إدارة الضرائب؛
  • السطر 6: تم استرداد هذه البيانات بواسطة البرنامج النصي الرئيسي [main] ووضعها في القاموس [config] ككائن [AdminData]. نُرجع قاموس هذا الكائن؛

29.6. اختبارات Postman

نقوم بتشغيل خادم الويب ونظام إدارة قواعد البيانات وخادم البريد [hMailServer]. ثم، باستخدام عميل Postman، نحسب الضريبة لعدد من دافعي الضرائب:

Image

في وحدة التحكم Postman، يكون الحوار بين العميل والخادم كما يلي:


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…]}}

الآن دعونا نطلب عنوان URL [/get-admindata] باستخدام طلب GET:

Image

يكون الحوار بين العميل والخادم في وحدة التحكم Postman كما يلي:


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. عميل الويب

Image

Image

يتم إنشاء المجلد [http-clients/06] في البداية عن طريق نسخ المجلد [http-clients/05]. تتكون أعمال التعديل بشكل أساسي من:

  • تعديل تكوين [config_layers] بحيث يتضمن الآن طبقة [business]. في السابق، كان يحتوي فقط على طبقة [DAO]؛
  • إضافة طريقة جديدة إلى طبقة [dao]؛
  • كتابة برنامج نصي [main2] يعتمد على طبقة [business] الخاصة بالعميل لحساب ضرائب دافعي الضرائب؛

29.7.1. تكوين طبقة العميل

يتم تكوين الطبقات في مكانين:

  • في تكوين [config]، الذي يجب أن يتضمن المجلد الذي يحتوي على تنفيذ طبقة [business] في تبعيات العميل. تم تضمين هذا المجلد بالفعل في التبعيات:

absolute_dependencies = [
        # dossiers du projet
        # BaseEntity, MyException
        f"{root_dir}/classes/02/entities",
        # InterfaceImpôtsDao, InterfaceImpôtsMétier, InterfaceImpôtsUi
        f"{root_dir}/impots/v04/interfaces",
        # AbstractImpôtsdao, ImpôtsConsole, ImpôtsMétier
        f"{root_dir}/impots/v04/services",
        # ImpotsDaoWithAdminDataInDatabase
        f"{root_dir}/impots/v05/services",
        # AdminData, ImpôtsError, TaxPayer
        f"{root_dir}/impots/v04/entities",
        # Constantes, tranches
        f"{root_dir}/impots/v05/entities",
        # ImpôtsDaoWithHttpClient
        f"{script_dir}/../services",
        # scripts de configuration
        script_dir,
        # Logger
        f"{root_dir}/impots/http-servers/02/utilities",
    ]

ثم يجب تعديل ملف [config_layers]:

def configure(config: dict) -> dict:
    #  instantiation of applicatuon layers

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

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

    #  make the layer configuration
    return {
        "dao": dao,
        "métier": métier
    }
  • الأسطر 4–6: إنشاء مثيل لطبقة [الأعمال]؛
  • الأسطر 13-16: يتم إرجاع طبقة [الأعمال] في قاموس الطبقات؛

29.7.2. تنفيذ طبقة [dao]

Image

ستنفذ طبقة [dao] واجهة [InterfaceImpôtsDaoWithHttpClient] التالية:

from abc import abstractmethod

from AbstractImpôtsDao import AbstractImpôtsDao

class InterfaceImpôtsDaoWithHttpClient(AbstractImpôtsDao):

    #  tAX CALCULATION
    @abstractmethod
    def calculate_tax_in_bulk_mode(self, taxpayers: list):
        pass
  • السطر 5: ترث الواجهة [InterfaceImpôtsDaoWithHttpClient] من الفئة المجردة [AbstractImpôtsDaoالتي تدير الوصول إلى نظام ملفات العميل. لاحظ أن لديها طريقة مجردة [get_admindata]؛
  • الأسطر 7–10: تسمح الطريقة [calculate_tax_in_bulk_mode] التي حددناها في الإصدار السابق بحساب الضريبة لقائمة من دافعي الضرائب؛

يتم تنفيذ هذه الواجهة بواسطة الفئة [ImpôtsDaoWithHttpClient] التالية:

#  imports

import json

import requests
from flask_api import status

from AbstractImpôtsDao import AbstractImpôtsDao
from AdminData import AdminData
from ImpôtsError import ImpôtsError
from InterfaceImpôtsDaoWithHttpClient import InterfaceImpôtsDaoWithHttpClient

class ImpôtsDaoWithHttpClient(InterfaceImpôtsDaoWithHttpClient):

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

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

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

        #  connection
        if config_server['authBasic']:
            response = requests.get(url_service,
                                    auth=(
                                        config_server["user"]["login"],
                                        config_server["user"]["password"]))
        else:
            response = requests.get(url_service)

        #  debug mode?
        if self.__debug:
            #  logger
            if not self.__logger:
                self.__logger = self.__config['logger']
            #  log on
            self.__logger.write(f"{response.text}\n")

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

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

        
  • السطر 13: تنفذ فئة [TaxDaoWithHttpClient] واجهة [TaxDaoWithHttpClientInterface]. وبالتالي فهي مشتقة من فئة [AbstractTaxDao]؛
  • السطران 65-66: الطريقة [calculate_tax_in_bulk_mode] التي تمت مناقشتها في الإصدار السابق؛
  • الأسطر 29-62: طريقة [get_admindata]، التي أعلنتها الفئة الأم [AbstractImpôtsDao] على أنها مجردة. وبالتالي، يتم تنفيذها في الفئة الفرعية؛
  • الأسطر 33-35: يتم تحديد عنوان URL لخدمة الويب التي يجب أن تستعلم عنها طريقة [get-admindata]. يتم تعريف عناوين URL لهذه الخدمات في تكوين [config] الخاص بالعميل:

       # le serveur de calcul de l'impôt
        "server": {
            "urlServer""http://127.0.0.1:5000",
            "authBasic"True,
            "user": {
                "login""admin",
                "password""admin"
            },
            "url_services": {
                "calculate-tax""/calculate-tax",
                "get-admindata""/get-admindata"
            }
        },
  • (تابع)
    • الأسطر 9–12: عنوانا URL لخادمي الويب؛
  • الأسطر 37–44: يتم الاستعلام عن عنوان URL للخدمة بشكل متزامن؛
  • الأسطر 46–42: إذا تطلبت التهيئة ذلك، يتم تسجيل استجابة الخادم؛
  • السطر 57: نعلم أن الخادم أرسل سلسلة JSON لقاموس؛
  • الأسطر 58-60: إذا لم تكن حالة HTTP للاستجابة هي 200، يتم إلقاء استثناء؛
  • السطور 61-62: يتم إرجاع الكائن [AdminData] الذي يغلف بيانات إدارة الضرائب المرسلة من الخادم؛

29.8. البرامج النصية [main، main2]

النص البرمجي [main] هو النص البرمجي من الإصدار السابق. يستخدم طريقة [calculate_tax_in_bulk_mode] من طبقة [dao] وبالتالي يستخدم طبقة [business] الخاصة بالخادم؛

يقوم البرنامج النصي [main2] بنفس ما يقوم به البرنامج النصي [main] ولكنه يستخدم طبقة [business] الخاصة بالعميل:

#  configure the application

import config
config = config.configure({})

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

logger = None
#  code
try:
    #  logger
    logger = Logger(config["logsFilename"])
    #  we save it in the config
    config["logger"] = logger
    #  start log
    logger.write("début du calcul de l'impôt des contribuables\n")
    #  we recover the [dao] layer
    dao = config["layers"]["dao"]
    #  we get the taxpayers back
    taxpayers = dao.get_taxpayers_data()["taxpayers"]
    #  taxpayers?
    if not taxpayers:
        raise ImpôtsError(36, f"Pas de contribuables valides dans le fichier {config['taxpayersFilename']}")
    #  we retrieve data from the tax authorities
    admindata = dao.get_admindata()
    #  calculation of taxpayers' taxes by the [business] layer
    métier = config['layers']['métier']
    for taxpayer in taxpayers:
        métier.calculate_tax(taxpayer, admindata)
    #  save the results in the jSON file
    dao.write_taxpayers_results(taxpayers)
#  except BaseException as error:
#      # error display
#      print(f "The following error has occurred: {error}")
finally:
    #  close the logger
    if logger:
        #  end log
        logger.write("fin du calcul de l'impôt des contribuables\n")
        #  closing the logger
        logger.close()
    #  we're done
    print("Travail terminé...")
  • السطور 26-27: استرداد البيانات من خادم مصلحة الضرائب؛
  • السطور 28-31: ثم يتم حساب ضريبة دافعي الضرائب محليًا؛

29.9. اختبارات العميل

في كل من البرامج النصية [main، main2 نقوم بتسجيل بداية ونهاية البرنامج النصي. وهذا يسمح لنا بحساب وقت تنفيذ البرنامج النصي. دعونا نضع بعض التوقعات:

  • البرنامج النصي [main] من الإصدار السابق:
    • يُنشئ N خيوطًا تعمل في وقت واحد؛
    • يعالج كل مؤشر ترابط مجموعة من دافعي الضرائب الذين يحسب الضريبة لهم عبر طلب واحد إلى الخادم؛
    • نظرًا لأن الخيوط N تعمل في وقت واحد، يتم إرسال الطلب N+1 قبل أن يتلقى الطلب N ردّه. وبالتالي، فإن تكلفة الطلبات N تزيد عن تكلفة الطلب الواحد، ولكن ربما ليس بفارق كبير. كما توجد 11 عملية حسابية (عدد دافعي الضرائب) على الخادم؛
  • البرنامج النصي [main2] في هذا الإصدار:
    • يُرسل طلبًا واحدًا إلى الخادم؛
    • يقوم بإجراء 11 عملية حسابية تجارية محليًا على العميل؛

ستستغرق الحسابات التجارية نفس القدر من الوقت سواء تم إجراؤها على الخادم أو على العميل. وبالتالي، سيكون الفرق في الطلبات. لذلك يمكننا توقع أن يكون وقت تنفيذ [main] أطول قليلاً من وقت تنفيذ [main2].

نقوم بتشغيل الخادم الإصدار 11 ونظام إدارة قواعد البيانات (DBMS) وخادم البريد [hMailServer]. على جانب الخادم، نقوم بتعيين المعلمة [sleep_time] على صفر حتى يتم تنفيذ كلا الاختبارين في ظل نفس الظروف.

التنفيذ 1 [main]

ينتج عن تنفيذ [main] السجلات التالية:


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

استغرق التنفيذ [051214-016079] نانوثانية (السطر 17 – السطر 1)، أي 35 مللي ثانية و 135 نانوثانية.

يمكننا أن نرى أنه بين الطلب الأول المقدم إلى الخادم والاستجابة الأخيرة التي تلقاها العميل، المدة هي نفسها [051214-016079] (السطر 15 – السطر 1)، أي 35 مللي ثانية و135 نانوثانية.

التنفيذ 2 [main2]

ينتج عن تنفيذ [main2] السجلات التالية:


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

استغرق التنفيذ [349975-303520] نانوثانية (السطر 3 - السطر 1)، أي 46 مللي ثانية و455 نانوثانية. وبشكل غير متوقع تمامًا، كان [main] أسرع من [main2].

نرى أن الطلب الفردي من [main2] استغرق [345084-303520] (السطر 2 – السطر 1)، أي 41 مللي ثانية و564 نانوثانية. ثم استغرق حساب الضريبة [349975-345084] (السطر 3 – السطر 2)، أي 4 مللي ثانية و91 نانو ثانية. إن طلب HTTP هو الذي يمثل وقت التنفيذ. والمثير للدهشة أننا نرى هنا أن الطلب الفردي من [main2] استغرق وقتًا أطول [41 مللي ثانية] من الطلبات الأربعة المتزامنة من [main] [35 مللي ثانية].

على جانب الخادم، السجلات هي كما يلي:


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}}}
  • السطر 5: الطلب الأول من العميل [main]؛
  • السطر 14: آخر استجابة للعميل [main]. هناك 6 مللي ثانية و647 نانو ثانية بين الاثنين؛
  • السطران 15-16: الطلب الوحيد من العميل [main2]. الاستجابة فورية؛