Remise en question et développement d’une première Architecture

Au fil des quelques articles je me suis senti bloqué par un manque de recul. Je pensais pouvoir établir plein de Use Case, les relier entre eux et voir une architecture sortir d’elle même des schémas. 

A mon avis, c’était une erreur de penser comme ça. Du coup, j’ai pris un peu de recul et je me suis penché sur l’architecture globale que pourrait prendre l’application, et voilà ce que ça donne sous forme d’un schéma :

Pour l’instant, c’est la vision globale de l’architecture que j’ai dans ma tête.

L’application est construite sur 4 couches :

  1. Le Root Application, c’est l’instance de notre application, chaque package doit y être enregistré et instancié correctement. Il permettra à tous d’interagir entre eux.
    Chaque package n’aura qu’à appeler celui ou ceux dont il a besoin.
  2. L’Interface Graphique, qui sert à recevoir les commandes et les actions de l’utilisateur via les périphériques (clavier, souris, etc.). L’interface graphique (ou UI pour User Interface) est un des package interne, mais je voulais la voir à part pour pouvoir différencier le Frontend du Backend.
  3. Les Packages Internes, c’est la partie Backend de l’application, tout ce qui se fait en coulisses. Chaque package est connecté au Médiateur qui leur permettra d’être en communication avec tous les autres dès que le besoin s’en fait sentir. 
  4. Package Externes, ce sont des packages déjà existants dont on a besoin pour ne pas avoir à tout créer “from scratch” comme on dit, et surtout pour pouvoir profiter d’outil open source d’une qualité qu’on ne pourrait pas fournir nous même.

Petite remise en question

A vrai dire, je pense que j’ai alourdi les chose en allant directement faire des use case, et des schéma de différents packages alors même que je ne les avaient pas décrit et réfléchi plus que ça. Je pense même que nous venons de finir les phases d’inception et d’élaboration du projet dans son Itération 0.

On a décrit globalement le projet et je savais déjà que le projet est faisable (Phase d’Inception), on a schématisé la première architecture, les différentes composantes de l’application et on a un cahier des charges global, on sait vers où on va (Phase d’Élaboration).

Nous entrons donc en phase de Construction et nous allons pouvoir commencer nos différentes itérations pour la création des différents packages dont nous avons besoin.

Itération 0.0 : Le RootApplication – Phase d’inception

Le Root Application, c’est l’instance de notre application, chaque package doit y être enregistré et instancié correctement. Il permettra à tous d’interagir entre eux.
Chaque package n’aura qu’à appeler celui ou ceux dont il a besoin.

Je rajouterais que le Root Application c’est également ce qui va nous permettre de lancer et initialiser tout le système, donc c’est lui qui va définir comment les choses sont démarrée et quand.

Comme les choses sont assez simples, les phases d’inception et d’élaboration sont relativement vite passée.

J’ai défini les objectifs du RootApplication, j’ai défini sa structure et je me suis également permis de définir une structure de base à étendre pour les Packages. Structure que je rend abstraite car elle n’a pas besoin d’être instanciée elle même.

Enfin je décris les différentes actions possibles avec le RootApplication.

Nous avons une structure, un comportement, il n’y a plus qu’à construire tout ça. Et devinez quoi, je vais enfin pouvoir taper un peu de code nondidju !! xD

Premièrement on va créer notre classe RootApplication.

main.py

import sys


class RootApplication:
    def __init__(self):
        self.packages = {}

        self.register_package("State")
        self.register_package("Exchanges")
        self.register_package("Database")
        self.register_package("HistoricData")
        self.register_package("AI")
        self.register_package("UI")

        self.instantiate_package()

    @staticmethod
    def __import__(package_name):
        try:
            package = __import__(package_name)
            main_class = getattr(package, f'{package_name}Package')
            return main_class
        except ModuleNotFoundError:
            print(f"{package_name} does not exist")
        except:
            print("Unexpected error :", sys.exc_info()[0])

    def register_package(self, package_name):
        package_class = self.__import__(package_name)
        package = package_class(self)
        self.packages[package.name] = package

    def instantiate_package(self):
        for package in self.packages:
            self.packages[package].instantiate()

    def require(self, package_name):
        try:
            return self.packages[package_name]
        except KeyError:
            print(f"Package Error : [{package_name}] is not Registred")
        except:
            print("Unexpected error :", sys.exc_info()[0])

Évidemment si on essaye de créer une instance de RootApplication on obtiendra des erreurs du type : « State Package does not exist ».
Normal, on a pas encore créé nos packages. Ceci dit on peut déjà créer les première classes de nos packages pour continuer la logique de notre.
Pour se faire, on va d’abord créer notre classe abstraite pour avoir des méthodes commune à tous les packages :

Core\__init__.py

class AbstractPackage:
    def __init__(self, root_app, name):
        self.root_app = root_app
        self.name = name
        print(f'[{self.name}] is Registered')

    def instantiate(self):
        print(f'Instantiation of {self.name}')

    def require(self, package_name):
        return self.root_app.require(package_name)

Le code est très simple, lors de la création de n’importe quel package qui étend cette classe, il pourra accéder à la méthode « require » et il pourra aussi lancer la méthode « instantiate » qui permettra de lancer la configuration des packages.

Maintenant qu’on a notre classe abstraite, on va pouvoir l’utiliser pour tous les packages. Chaque package devra donc contenir une classe nommée de manière standard : NomDeClasse + Package (NomDeClassePacakge).
Créons directement le Package State et tapons le code de la classe directement dans le fichier __init__.py :

State\__init__.py

from Core import *


class StatePackage(AbstractPackage):
    def __init__(self, root_app):
        AbstractPackage.__init__(self, root_app, 'State')

Et voilà, on a une classe de Package de base, connectée au RootApplication directement depuis lequel elle peut charger d’autres packages instanciés.
Tout ce petit code permet donc à nos packages de se charger les uns et les autres et d’avoir accès aux méthodes de chacun.

On a désormais notre logique d’échange et de communication entre packages. Il manque quelque chose c’est une série de tests unitaires que je devrais rajouter prochainement. Les tests unitaires sont super importants pour savoir si l’évolution de notre application se fait sans conséquence sur le code déjà écrit.

Voilà, c’est la fin de cet article, et comme vous l’avez vu, on a bouclé assez rapidement une itération. On va pouvoir travailler sur les autres dès les prochains articles. Comme quoi, une petite remise en question, ça aide toujours 😉

Merci pour l’intérêt que vous portez à mon projet, n’hésitez surtout pas à vous inscrire et ajouter des commentaires critiques et/ou constructif 🙂

A bientôt !