[CleanArchitecture avec Python] Appliquez CleanArchitecture à une API simple étape par étape, et essayez de comprendre "quel type de changement est fort" dans la base de code.

Cliquez ici pour le dernier exemple de code d'architecture propre utilisant Python: https://github.com/y-tomimoto/CleanArchitecture/tree/master/part9

app
├── application_business_rules
│   ├── __init__.py
│   ├── boundary
│   │   ├── __init__.py
│   │   ├── input_port
│   │   │   ├── __init__.py
│   │   │   └── memo_input_port.py
│   │   └── output_port
│   │       ├── __init__.py
│   │       └── memo_output_port.py
│   └── memo_handle_interactor.py
├── enterprise_business_rules
│   ├── __init__.py
│   ├── dto
│   │   ├── __init__.py
│   │   ├── input_memo_dto.py
│   │   └── output_memo_dto.py
│   ├── entity
│   │   ├── __init__.py
│   │   └── memo.py
│   ├── memo_data.py
│   └── value_object
│       ├── __init__.py
│       └── memo_author.py
├── frameworks_and_drivers
│   ├── __init__.py
│   ├── db
│   │   ├── __init__.py
│   │   ├── mysql.py
│   │   └── postgres.py
│   └── web
│       ├── __init__.py
│       ├── fastapi_router.py
│       └── flask_router.py
├── interface_adapters
│   ├── __init__.py
│   ├── controller
│   │   ├── __init__.py
│   │   └── flask_controller.py
│   ├── gataways
│   │   ├── __init__.py
│   │   └── memo_repository_gateway.py
│   └── presenter
│       ├── __init__.py
│       ├── ad_presenter.py
│       └── default_presenter.py
└── main.py


Résumé: Quelle est la force du changement pour chaque couche que vous adoptez?

Présentation de chaque avantage obtenu en appliquant une architecture propre étape par étape

https___qiita-image-store.s3.amazonaws.com_0_293368_7ce1fb10-504e-16e0-8930-278b8a7f942d.jpeg

Partie 2: Couche Frameworks & Drivers: l'émergence du Web

En découpant chaque framework d'application Web que vous souhaitez adopter dans la couche Frameworks & Drivers: Web, et en découpant le traitement initialement attendu de l'application vers MemoHandler, En appelant simplement le routeur que vous souhaitez adopter avec main.py, vous pouvez modifier de manière flexible le framework ** sans modifier memo_handler.py, qui est le processus que vous attendiez à l'origine de l'application. ..

Cette conception implémente l'une des règles CleanArchitecture, ** Framework Independence **.

Clean Architecture (traduit par The Clean Architecture): https://blog.tai2.net/the_clean_architecture.html

Indépendance du framework: L'architecture ne repose pas sur la disponibilité d'une bibliothèque logicielle riche en fonctionnalités. Cela permet à de tels cadres d'être utilisés comme des outils et ne force pas le système à être forcé dans les contraintes limitées du cadre.

Partie 3: Présentation de la couche de règles d'entreprise d'entreprise et de la couche de règles d'entreprise d'application

Memo_handler.py qui décrit le traitement que vous attendiez à l'origine de l'application

Divisée en.

Cela rendra memo_handler.py,

  1. Traitement de principe dans l'application et
  2. Les utiliser pour le traitement des fluides qui répond aux spécifications de l'application

En divisant en, lors de la modification des spécifications de l'application, il est conçu de sorte que les spécifications puissent être modifiées et étendues de manière flexible sans affecter le traitement de principe existant.

Partie 4: Couche des adaptateurs d'interface: Présentation des contrôleurs

En exploitant le contrôleur dans la couche des adaptateurs d'interface La partie consistant à changer le "format de demande externe" fréquemment mis à jour en un format adapté au traitement réel, J'ai pu le couper du cadre.

Cela vous permet de modifier le format des demandes que votre application peut accepter. Il est conçu pour vous permettre de modifier votre code sans tenir compte des frameworks d'application Web ou des règles métier existants.

Part5: ~ Extra Edition ~ Utilisation de DTO

En adoptant DTO, l'accès aux données entre les couches est facilité et en même temps Il est conçu pour minimiser l'impact sur chaque couche lorsque la structure de données gérée par l'application change.

Partie 6: Couche des adaptateurs d'interface: le présentateur apparaît

En plus d'implémenter Presenter, nous avons également implémenté OutputPort.

Par conséquent, lors de la modification de l'interface utilisateur, elle est conçue de sorte que seule l'interface utilisateur puisse être modifiée indépendamment sans tenir compte du cadre d'application Web ou des règles métier existants.

Avec l'introduction de ce Presenter, les règles CleanArchitecture et l'indépendance de l'interface utilisateur ont été atteintes.

Clean Architecture (traduit par The Clean Architecture): https://blog.tai2.net/the_clean_architecture.html

L'interface utilisateur peut être facilement modifiée. Pas besoin de changer le reste du système. Par exemple, l'interface utilisateur Web peut être remplacée par l'interface utilisateur de la console sans modifier les règles métier.

Partie 7: Niveau Frameworks & Drivers: niveau DB et Adaptateurs d'interface: les passerelles sont ici

Nous avons implémenté DB dans la couche DB et adopté Gataways,

En conséquence, lors du changement de DB, il est conçu de sorte que le DB puisse être commuté sans considérer chaque couche.

En conséquence, les règles CleanArchitecture et l'indépendance de la base de données ont été atteintes.

Clean Architecture (traduit par The Clean Architecture): https://blog.tai2.net/the_clean_architecture.html

Indépendant de la base de données. Vous pouvez remplacer Oracle ou SQL Server par Mongo, BigTable, CoucheDB ou autre. Les règles métier ne sont pas liées à la base de données.

Partie 8: Couche de règles d'entreprise d'entreprise: Adopting Entity & Value Object

Communiquer avec DB en utilisant Entity, un objet avec la même structure que la base de données Afin de masquer les propriétés hautement confidentielles, nous l'avons conçu pour adopter le DTO dans chaque règle métier.

En conséquence, chaque règle métier est conçue de manière à pouvoir gérer les valeurs de la base de données sans connaître les propriétés confidentielles.

De plus, la validation et le traitement de chaque propriété sont rendus indépendants de l'entité en adoptant ValueObject. Par conséquent, lors de la création ou de la modification d'une entité, il n'est plus nécessaire de l'implémenter avec des propriétés spécifiques à l'esprit.

Pourquoi écrire cet article

Récemment, j'ai été affecté à un projet qui pouvait être techniquement contesté, j'ai donc adopté Clean Architecture.

https___qiita-image-store.s3.amazonaws.com_0_293368_7ce1fb10-504e-16e0-8930-278b8a7f942d.jpeg

Je voulais re-verbaliser ce que j'avais appris quand je l'ai embauché.

Quand je l'ai implémenté, j'ai pensé qu'il aurait été préférable qu'il y ait un article expliquant les problèmes que chaque couche résoudrait dans la base de code.

J'ai décidé d'écrire cet article.

Comment rédiger un article

Comme mentionné ci-dessus, les articles actuellement publiés sur Clean Architecture sont Je pense personnellement qu'il se compose souvent des deux parties suivantes.

  1. Le code de l'artefact créé par * Clean Architecture ressemble à ceci. *
  2. Le code de * ○○ correspond à la couche ○○, et la couche ○○ joue un tel rôle. *

** En imaginant "à quels types de changements l'architecture propre résiste spécifiquement" **

Ce n'est pas une structure qui présente le code de l'artefact déjà terminé depuis le début

    • Nous allons progressivement résoudre les problèmes que rencontrent les livrables existants lors de la modification des spécifications *
    • La configuration finale est CleanArchitecture *

Je vais y arriver.

À propos de l'histoire de chaque partie

Ce que je souhaite clarifier dans cet article est

** "À quels types de changements Clean Architecture résiste-t-il spécifiquement?" **

est.

Ainsi, dans l'article, nous appliquerons une architecture propre avec le développement suivant.

Commençons.

table des matières

[Part1: Créer une API de base simple](https://qiita.com/y_tom/items/ac6f6a08bdc374336dc4#part1-%E3%83%99%E3%83%BC%E3%82%B9%E3 % 81% A8% E3% 81% AA% E3% 82% 8B% E3% 82% B7% E3% 83% B3% E3% 83% 97% E3% 83% AB% E3% 81% AA-api-% E3% 82% 92% E4% BD% 9C% E6% 88% 90% E3% 81% 99% E3% 82% 8B-1)

Partie 2: Couche Frameworks & Drivers: Présentation du Web

Partie 3: Présentation de la couche de règles d'entreprise d'entreprise et de la couche de règles d'entreprise d'application

Partie 4: Couche des adaptateurs d'interface: Présentation des contrôleurs

Part5: ~ Extra Edition ~ Utilisation de DTO

Partie 6: Couche des adaptateurs d'interface: le présentateur apparaît

Partie 7: Niveau Frameworks & Drivers: niveau DB et Adaptateurs d'interface: les passerelles sont ici

Partie 8: Couche de règles d'entreprise d'entreprise: adoption d'entité et d'objet de valeur

Partie 9: Testable ~ Résumé

Partie 1: créer une API de base simple

Dans la partie 1, nous allons créer une API qui servira de base à l'explication de la partie suivante.

Lors de la création

J'ai essayé d'implémenter cette API afin qu'elle soit intentionnellement monolithique sans supposer un changement de spécification **.

En le rendant volontairement monolithique, l'objectif est de faciliter la visualisation des avantages du design lors de l'application de CleanArchitecture.

Observons dans les parties suivantes comment le fichier est progressivement divisé par responsabilité et la combinaison se desserre progressivement.

Le premier livrable à appliquer une architecture propre par étapes

Cette fois

  1. ** Recevoir la demande POST et enregistrer les notes **

  2. ** Recevez une demande GET et reportez-vous au mémo enregistré **

Faites juste une note API.

la mise en oeuvre

Utilisez le framework d'application Web Flask pour créer une API simple.

1. Préparez le point final

Je republierai les exigences, mais l'API créée cette fois est

  1. ** Recevoir la demande POST et enregistrer les notes **
  2. ** Recevez une demande GET et reportez-vous au mémo enregistré **

est.

Les implémentations qui satisfont aux exigences traiteront memo avec memo_id comme clé primaire.

Tout d'abord, préparez un point de terminaison qui exécute les deux points ci-dessus.


  1. Utilisez Flask pour préparer un point de terminaison pour ** recevoir des requêtes POST et enregistrer des notes **.

    from flask import Flask, request
    app = Flask(__name__)
    
    @app.route('/memo/<int:memo_id>', methods=['POST'])
    def post(memo_id: int) -> str:
        #Obtenez de la valeur de la demande
        memo: str = request.form["memo"]
        pass
    
  2. De même, préparez un point de terminaison pour ** recevoir une requête GET et référencer le mémo enregistré **.

    @app.route('/memo/<int:memo_id>')
    def get(memo_id: int) -> str:
        pass
    

2. Préparez une pièce pour l'échange de notes avec DB

Décrivons maintenant l'interaction avec la base de données qui stocke le mémo sur ce point de terminaison. Cette fois, mysql est utilisé comme base de données à sauvegarder.


  1. Tout d'abord, préparez une fonction pour vérifier si memo existe dans memo_id.

    from mysql import connector
    
    #Paramètres de connexion à la base de données
    config = {
        'user': 'root',
        'password': 'password',
        'host': 'mysql',
        'database': 'test_database',
        'autocommit': True
    }
    
    
    def exist(memo_id: int) -> bool:
        #Créer un client DB
        conn = connector.connect(**config)
        cursor = conn.cursor()
    
        # memo_Vérifiez s'il y a un identifiant
        query = "SELECT EXISTS(SELECT * FROM test_table WHERE memo_id = %s)"
        cursor.execute(query, [memo_id])
        result: tuple = cursor.fetchone()
    
        #Fermez le client DB
        cursor.close()
        conn.close()
    
        #Vérifier l'existence en vérifiant s'il y a un résultat de recherche
        if result[0] == 1:
            return True
        else:
            return False
    
  2. Ensuite, ajoutez la ** réponse à la demande POST et enregistrez le processus de note ** au point de terminaison créé.

    from flask import Flask, request, jsonify
    from mysql import connector
    from werkzeug.exceptions import Conflict
    app = Flask(__name__)
    
    @app.route('/memo/<int:memo_id>', methods=['POST'])
    def post(memo_id: int) -> str:
    
        #Vérifiez s'il y a un identifiant spécifié
        is_exist: bool = exist(memo_id)
    
        if is_exist:
            raise Conflict(f'memo_id [{memo_id}] is already registered.')
    
        #Obtenez de la valeur de la demande
        memo: str = request.form["memo"]
    
        #Créer un client DB
        conn = connector.connect(**config)
        cursor = conn.cursor()
    
        #Enregistrer le mémo
        query = "INSERT INTO test_table (memo_id, memo) VALUES (%s, %s)"
        cursor.execute(query, (memo_id, memo))
    
        #Fermez le client DB
        cursor.close()
        conn.close()
    
        return jsonify(
            {
                "message": "saved."
            }
        )
    
    

  1. Ensuite, implémentez le ** processus qui reçoit la requête ** GET et fait référence au mémo enregistré dans la base de données externe **.

    from werkzeug.exceptions import NotFound
    
    
    @app.route('/memo/<int:memo_id>')
    def get(memo_id: int) -> str:
    
        #Vérifiez s'il y a un identifiant spécifié
        is_exist: bool = exist(memo_id)
    
        if not is_exist:
            raise NotFound(f'memo_id [{memo_id}] is not registered yet.')
    
        #Créer un client DB
        conn = connector.connect(**config)
        cursor = conn.cursor()
    
        # memo_Effectuer une recherche par identifiant
        query = "SELECT * FROM test_table WHERE memo_id = %s"
        cursor.execute(query, [memo_id])
        result: tuple = cursor.fetchone()
    
        #Fermez le client DB
        cursor.close()
        conn.close()
    
        return jsonify(
            {
                "message": f'memo : [{result[1]}]'
            }
        )
    
    
  2. Ensuite, définissez le gestionnaire d'erreurs.

    from http import HTTPStatus
    from flask import make_response
    
    @app.errorhandler(NotFound)
    def handle_404(err):
        json = jsonify(
            {
                "message": err.description
            }
        )
        return make_response(json, HTTPStatus.NOT_FOUND)
    
    
    @app.errorhandler(Conflict)
    def handle_409(err):
        json = jsonify(
            {
                "message": err.description
            }
        )
        return make_response(json, HTTPStatus.CONFLICT)
    

3. Lancez l'application

Enfin, le processus de démarrage de ʻapp` avec chaque routeur généré jusqu'à présent est décrit dans le fichier.

    
   if __name__ == '__main__':
      app.run(debug=True, host='0.0.0.0')

4. Code final

main.py

from http import HTTPStatus
from flask import Flask, request, jsonify, make_response
from mysql import connector
from werkzeug.exceptions import Conflict, NotFound

app = Flask(__name__)

#Paramètres de connexion à la base de données
config = {
    'user': 'root',
    'password': 'password',
    'host': 'mysql',
    'database': 'test_database',
    'autocommit': True
}


def exist(memo_id: int) -> bool:
    #Créer un client DB
    conn = connector.connect(**config)
    cursor = conn.cursor()

    # memo_Vérifiez s'il y a un identifiant
    query = "SELECT EXISTS(SELECT * FROM test_table WHERE memo_id = %s)"
    cursor.execute(query, [memo_id])
    result: tuple = cursor.fetchone()

    #Fermez le client DB
    cursor.close()
    conn.close()

    #Vérifier l'existence en vérifiant s'il y a un résultat de recherche
    if result[0] == 1:
        return True
    else:
        return False


@app.route('/memo/<int:memo_id>')
def get(memo_id: int) -> str:
    #Vérifiez s'il y a un identifiant spécifié
    is_exist: bool = exist(memo_id)

    if not is_exist:
        raise NotFound(f'memo_id [{memo_id}] is not registered yet.')

    #Créer un client DB
    conn = connector.connect(**config)
    cursor = conn.cursor()

    # memo_Effectuer une recherche par identifiant
    query = "SELECT * FROM test_table WHERE memo_id = %s"
    cursor.execute(query, [memo_id])
    result: tuple = cursor.fetchone()

    #Fermez le client DB
    cursor.close()
    conn.close()

    return jsonify(
        {
            "message": f'memo : [{result[1]}]'
        }
    )


@app.route('/memo/<int:memo_id>', methods=['POST'])
def post(memo_id: int) -> str:
    #Vérifiez s'il y a un identifiant spécifié
    is_exist: bool = exist(memo_id)

    if is_exist:
        raise Conflict(f'memo_id [{memo_id}] is already registered.')

    #Obtenez de la valeur de la demande
    memo: str = request.form["memo"]

    #Créer un client DB
    conn = connector.connect(**config)
    cursor = conn.cursor()

    #Enregistrer le mémo
    query = "INSERT INTO test_table (memo_id, memo) VALUES (%s, %s)"
    cursor.execute(query, (memo_id, memo))

    #Fermez le client DB
    cursor.close()
    conn.close()

    return jsonify(
        {
            "message": "saved."
        }
    )


@app.errorhandler(NotFound)
def handle_404(err):
    json = jsonify(
        {
            "message": err.description
        }
    )
    return make_response(json, HTTPStatus.NOT_FOUND)


@app.errorhandler(Conflict)
def handle_409(err):
    json = jsonify(
        {
            "message": err.description
        }
    )
    return make_response(json, HTTPStatus.CONFLICT)


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')


Après la partie 1

Vous disposez maintenant d'une API qui effectue les deux opérations suivantes:

  1. ** Recevoir la demande POST et enregistrer les notes **
  2. ** Recevez une demande GET et reportez-vous au mémo enregistré **

Dans les articles suivants, pour chaque partie, tout le code, y compris l'environnement du conteneur, est stocké dans le référentiel suivant, donc Si vous souhaitez le déplacer à portée de main, veuillez vous référer à ce qui suit.

Part1: https://github.com/y-tomimoto/CleanArchitecture/tree/master/part1

À partir de la partie suivante, supposons une demande de modification de spécification pour cette API et appliquons une architecture propre étape par étape.

Recommended Posts

[CleanArchitecture avec Python] Appliquez CleanArchitecture à une API simple étape par étape, et essayez de comprendre "quel type de changement est fort" dans la base de code.
Essayer d'implémenter et de comprendre les arborescences de segments étape par étape (python)
[Python] J'ai visualisé les paroles d'Arashi avec WordCloud et j'ai essayé de démêler ce que je voulais transmettre aux fans en 20e année de formation.
[Introduction aux statistiques] Quel type de distribution est la distribution t, la distribution chi carré et la distribution F? Un petit résumé de l'utilisation de [python]
Une histoire sur le portage du code de "Essayez de comprendre comment fonctionne Linux" sur Rust
Si vous essayez de créer un nuage de mots avec des commentaires de mangas WEB, il est intéressant de comprendre visuellement de quel genre de manga il s'agit.
[Introduction à Python] Quelle est la différence entre une liste et un taple?
Exemple de code pour obtenir oauth_token et oauth_token_secret de l'API Twitter en Python 2.7
Quel genre de livre est le "Python Crash Course" le plus vendu au monde?
Liste des codes de langue utilisés dans Twitter (y compris l'API) (avec dictionnaire Python). Quelle est la langue la plus couramment utilisée?
Introduction et utilisation de la bouteille Python ・ Essayez de configurer un serveur Web simple avec une fonction de connexion
[Introduction à Python] Quelle est la méthode de répétition avec l'instruction continue?
Essayez de résoudre le problème du voyageur de commerce avec un algorithme génétique (code Python)
Comment obtenir une liste de fichiers dans le même répertoire avec python
Utilisez Ruby et Python pour trouver la probabilité qu'une carte avec un nombre naturel de 1 à 100 soit un multiple de 3 et non un multiple de 5.
Une introduction au logiciel d'interface graphique de la plate-forme de classe fait avec Python / Tkinter! (Et de nombreux Try and Error)! (Au milieu de l'écriture)
Utilisez tkinter pour déplacer le code de sortie en tant que "A et prétendant être B" en python
Automatisez la suppression de l'arrière-plan pour les derniers portraits dans un répertoire avec Python et API
Comment identifier l'élément avec le plus petit nombre de caractères dans une liste Python?
[Python] Changer la couleur du texte et la couleur d'arrière-plan d'un mot clé spécifique dans la sortie d'impression
Comment vérifier en Python si l'un des éléments d'une liste est dans une autre liste
Il est facile d'exécuter SQL avec Python et de générer le résultat dans Excel
Qu'est-ce que Dieu? Créez un chatbot simple avec python
Notez ce que vous voulez faire à l'avenir avec Razpai
J'ai créé une classe pour obtenir le résultat de l'analyse par MeCab dans ndarray avec python
Obtenez de manière récursive la liste Excel dans un dossier spécifique avec python et écrivez-la dans Excel.
J'ai aussi essayé d'imiter la fonction monade et la monade d'état avec le générateur en Python
J'ai écrit un doctest dans "J'ai essayé de simuler la probabilité d'un jeu de bingo avec Python"
[Python] Qu'est-ce qu'un tuple? Explique comment utiliser sans toucher et comment l'utiliser avec des exemples.
Essayez d'importer dans la base de données en manipulant ShapeFile d'informations numériques sur les terres nationales avec Python
Essayez de gratter les données COVID-19 Tokyo avec Python
Essayez d'utiliser l'API Twitter rapidement et facilement avec Python
Changer la destination de sortie standard en un fichier en Python
Je souhaite améliorer l'efficacité avec Python même dans le système expérimental (5) Je souhaite envoyer une notification à la fin de l'expérience avec l'API slack
Essayez d'ouvrir une sous-fenêtre avec PyQt5 et Python
Essayez d'automatiser le fonctionnement des périphériques réseau avec Python
Essayez simplement de recevoir un webhook avec ngrok et Python
[Python] Qu'est-ce qu'une tranche? Une explication facile à comprendre de son utilisation avec un exemple concret
Écrivez un script dans Shell et Python pour vous avertir dans Slack lorsque le processus est terminé
J'ai essayé de vérifier l'identification du locuteur par l'API de reconnaissance du locuteur d'Azure Cognitive Services avec Python. # 1
[Python] Qu'est-ce que pip? Expliquez la liste des commandes et comment l'utiliser avec des exemples réels
J'ai essayé de vérifier l'identification du locuteur par l'API de reconnaissance du locuteur d'Azure Cognitive Services avec Python. # 2
Que faire lorsqu'une partie de l'image d'arrière-plan devient transparente lorsque l'image transparente est combinée avec Oreiller
[Python] Le rôle de l'astérisque devant la variable. Divisez la valeur d'entrée et affectez-la à une variable
Une raison simple pour laquelle la valeur de retour de round (2.675,2) est de 2,67 en python (elle devrait être de 2,68 en réalité ...)
Connaissances minimales requises pour traiter les «angles» et les «coordonnées» dans Ruby + Dans quelle direction est M. B du point de vue de M. A? Algorithme
Faites fonctionner Jupyter avec l'API REST pour extraire et enregistrer le code Python
Comment déterminer l'existence d'un élément sélénium en Python
Une analyse simple des données de Bitcoin fournie par CoinMetrics en Python
Rubyist a essayé de créer une API simple avec Python + bouteille + MySQL
Essayez d'obtenir la liste des fils du bulletin d'information (je n'aime pas) avec Python.
Comment donner et signifier l'option des contraintes dans scipy.optimize.minimize
Comment vérifier la taille de la mémoire d'une variable en Python
Ce à quoi j'étais accro avec json.dumps dans l'encodage base64 de Python
Premier python ② Essayez d'écrire du code tout en examinant les fonctionnalités de python
Essayez de créer un environnement python avec Visual Studio Code et WSL