Klicken Sie hier, um den endgültigen Beispielcode für Clean Architecture mit Python anzuzeigen: 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
Durch Ausschneiden jedes Webanwendungsframeworks, das Sie in die Ebene Frameworks & Drivers: Web übernehmen möchten, und Ausschneiden der ursprünglich von der Anwendung erwarteten Verarbeitung in "MemoHandler". Durch einfaches Aufrufen des Routers, den Sie mit "main.py" übernehmen möchten, können Sie das Framework ** flexibel ändern, ohne "memo_handler.py" zu ändern. Dies ist der Prozess, den Sie ursprünglich von der Anwendung erwartet hatten. ..
Dieses Design implementiert eine der CleanArchitecture-Regeln, ** Framework Independence **.
Saubere Architektur (übersetzt von The Clean Architecture): https://blog.tai2.net/the_clean_architecture.html
Framework-Unabhängigkeit: Die Architektur ist nicht auf die Verfügbarkeit einer funktionsreichen Softwarebibliothek angewiesen. Dies ermöglicht die Verwendung solcher Frameworks als Werkzeuge und zwingt das System nicht dazu, in die begrenzten Einschränkungen des Frameworks gezwungen zu werden.
Memo_handler.py
beschreibt die Verarbeitung, die Sie ursprünglich von der Anwendung erwartet hatten
Eingeteilt in.
Dies macht memo_handler.py
,
Durch die Aufteilung in, wenn die Spezifikationen der Anwendung geändert werden, wird sie so konzipiert, dass die Spezifikationen flexibel geändert und erweitert werden können, ohne die bestehende prinzipielle Verarbeitung zu beeinträchtigen.
Durch Nutzung des Controllers in der Ebene "Schnittstellenadapter" Der Teil der Änderung des häufig aktualisierten "externen Anforderungsformats" in ein Format, das für die tatsächliche Verarbeitung geeignet ist, Ich konnte es aus dem Rahmen herausschneiden.
Auf diese Weise können Sie das Format der Anforderungen ändern, die Ihre Anwendung annehmen kann. Es soll Ihnen ermöglichen, Ihren Code zu ändern, ohne vorhandene Webanwendungsframeworks oder Geschäftsregeln zu berücksichtigen.
Durch die Einführung von DTO wird der Datenzugriff zwischen Schichten erleichtert und gleichzeitig Es wurde entwickelt, um die Auswirkungen auf jede Schicht zu minimieren, wenn sich die von der Anwendung verarbeitete Datenstruktur ändert.
Neben der Implementierung von Presenter haben wir auch OutputPort implementiert.
Wenn Sie die Benutzeroberfläche ändern, ist sie daher so konzipiert, dass nur die Benutzeroberfläche unabhängig geändert werden kann, ohne das vorhandene Webanwendungsframework oder die Geschäftsregeln zu berücksichtigen.
Mit der Einführung dieses Präsentators wurden CleanArchitecture-Regeln und die Unabhängigkeit der Benutzeroberfläche erreicht.
Saubere Architektur (übersetzt von The Clean Architecture): https://blog.tai2.net/the_clean_architecture.html
Die Benutzeroberfläche kann leicht geändert werden. Der Rest des Systems muss nicht geändert werden. Beispielsweise kann die Web-Benutzeroberfläche durch die Konsolen-Benutzeroberfläche ersetzt werden, ohne die Geschäftsregeln zu ändern.
Wir haben DB in der DB-Schicht implementiert und Gataways übernommen.
Wenn Sie die Datenbank ändern, ist sie daher so konzipiert, dass die Datenbank ohne Berücksichtigung jeder Schicht umgeschaltet werden kann.
Infolgedessen wurden CleanArchitecture-Regeln und Datenbankunabhängigkeit erreicht.
Saubere Architektur (übersetzt von The Clean Architecture): https://blog.tai2.net/the_clean_architecture.html
Datenbankunabhängig. Sie können Oracle oder SQL Server durch Mongo, BigTable, CoucheDB oder etwas anderes ersetzen. Geschäftsregeln sind nicht an die Datenbank gebunden.
Kommunizieren Sie mit der Datenbank über Entity, ein Objekt mit derselben Struktur wie die Datenbank Um streng vertrauliche Eigenschaften zu verbergen, haben wir DTO in jede Geschäftsregel übernommen.
Infolgedessen ist jede Geschäftsregel so konzipiert, dass sie Werte in der Datenbank verarbeiten kann, ohne vertrauliche Eigenschaften zu kennen.
Darüber hinaus wird die Validierung und Verarbeitung jeder Eigenschaft durch die Übernahme von ValueObject von der Entität unabhängig gemacht. Daher ist es beim Erstellen oder Ändern einer Entität nicht mehr erforderlich, sie unter Berücksichtigung bestimmter Eigenschaften zu implementieren.
Vor kurzem wurde ich einem Projekt zugewiesen, das technisch herausgefordert werden konnte, und so übernahm ich Clean Architecture.
Ich wollte das, was ich gelernt hatte, als ich ihn anstellte, neu verbalisieren.
Als ich es implementierte, dachte ich, es wäre besser gewesen, wenn es einen Artikel gäbe, der die Probleme erklärt, die jede Schicht in der Codebasis lösen würde.
Ich habe beschlossen, diesen Artikel zu schreiben.
Wie oben erwähnt, sind die derzeit veröffentlichten Artikel über Clean Architecture Ich persönlich denke, dass es oft aus den folgenden zwei Teilen besteht.
** Bei der Vorstellung, "gegen welche Art von Änderungen die saubere Architektur besonders resistent ist" **
Es ist keine Struktur, die den Code des bereits fertiggestellten Artefakts von Anfang an darstellt
Ich werde es schaffen.
Was ich in diesem Artikel klarstellen möchte, ist
** "Gegen welche Änderungen ist Clean Architecture besonders resistent?" **
ist.
In diesem Artikel werden wir Clean Architecture mit der folgenden Entwicklung anwenden.
Lass uns anfangen.
In Teil 1 erstellen wir eine API, die die Grundlage für die Erläuterung des folgenden Teils bildet.
Beim Erstellen
Ich habe versucht, diese API so zu implementieren, dass sie absichtlich monolithisch ist, ohne eine Spezifikationsänderung anzunehmen **.
Durch die absichtliche Monolithisierung soll die Visualisierung der Vorteile des Designs bei der Anwendung von CleanArchitecture vereinfacht werden.
Lassen Sie uns in den folgenden Abschnitten beobachten, wie die Datei allmählich durch die Verantwortung geteilt wird und die Kombination allmählich locker wird.
Diesmal
** POST-Anfrage erhalten und Notizen speichern **
** Erhalten Sie eine GET-Anfrage und beziehen Sie sich auf das gespeicherte Memo **
Machen Sie einfach eine Notiz-API.
Verwenden Sie das Webanwendungsframework Flask, um eine einfache API zu erstellen.
Ich werde die Anforderungen erneut veröffentlichen, aber die diesmal erstellte API ist
ist.
Implementierungen, die die Anforderungen erfüllen, behandeln "memo" mit "memo_id" als Primärschlüssel.
Bereiten Sie zunächst einen Endpunkt vor, der die beiden oben genannten Punkte ausführt.
Verwenden Sie Flask, um einen Endpunkt für den Empfang von POST-Anforderungen und das Speichern von Notizen vorzubereiten.
from flask import Flask, request
app = Flask(__name__)
@app.route('/memo/<int:memo_id>', methods=['POST'])
def post(memo_id: int) -> str:
#Holen Sie sich Wert von Anfrage
memo: str = request.form["memo"]
pass
Bereiten Sie auf ähnliche Weise einen Endpunkt für den ** Empfang einer GET-Anforderung und den Verweis auf das gespeicherte Memo ** vor.
@app.route('/memo/<int:memo_id>')
def get(memo_id: int) -> str:
pass
Beschreiben wir nun die Interaktion mit der Datenbank, in der das Memo auf diesem Endpunkt gespeichert ist. Dieses Mal wird MySQL als Datenbank zum Speichern verwendet.
Bereiten Sie zunächst eine Funktion vor, um zu überprüfen, ob in memo_id ein Memo vorhanden ist.
from mysql import connector
#Einstellungen für die DB-Verbindung
config = {
'user': 'root',
'password': 'password',
'host': 'mysql',
'database': 'test_database',
'autocommit': True
}
def exist(memo_id: int) -> bool:
#Erstellen Sie einen DB-Client
conn = connector.connect(**config)
cursor = conn.cursor()
# memo_Überprüfen Sie, ob eine ID vorhanden ist
query = "SELECT EXISTS(SELECT * FROM test_table WHERE memo_id = %s)"
cursor.execute(query, [memo_id])
result: tuple = cursor.fetchone()
#Schließen Sie den DB-Client
cursor.close()
conn.close()
#Überprüfen Sie die Existenz, indem Sie prüfen, ob es ein Suchergebnis gibt
if result[0] == 1:
return True
else:
return False
Fügen Sie als Nächstes die ** Antwort auf die POST-Anforderung hinzu und speichern Sie die Notiz ** zum erstellten Endpunkt.
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:
#Überprüfen Sie, ob eine bestimmte ID vorhanden ist
is_exist: bool = exist(memo_id)
if is_exist:
raise Conflict(f'memo_id [{memo_id}] is already registered.')
#Holen Sie sich Wert von Anfrage
memo: str = request.form["memo"]
#Erstellen Sie einen DB-Client
conn = connector.connect(**config)
cursor = conn.cursor()
#Memo speichern
query = "INSERT INTO test_table (memo_id, memo) VALUES (%s, %s)"
cursor.execute(query, (memo_id, memo))
#Schließen Sie den DB-Client
cursor.close()
conn.close()
return jsonify(
{
"message": "saved."
}
)
Implementieren Sie als Nächstes den ** Prozess, der die ** GET-Anforderung empfängt und auf das in der externen Datenbank gespeicherte Memo verweist **.
from werkzeug.exceptions import NotFound
@app.route('/memo/<int:memo_id>')
def get(memo_id: int) -> str:
#Überprüfen Sie, ob eine bestimmte ID vorhanden ist
is_exist: bool = exist(memo_id)
if not is_exist:
raise NotFound(f'memo_id [{memo_id}] is not registered yet.')
#Erstellen Sie einen DB-Client
conn = connector.connect(**config)
cursor = conn.cursor()
# memo_Führen Sie eine Suche nach ID durch
query = "SELECT * FROM test_table WHERE memo_id = %s"
cursor.execute(query, [memo_id])
result: tuple = cursor.fetchone()
#Schließen Sie den DB-Client
cursor.close()
conn.close()
return jsonify(
{
"message": f'memo : [{result[1]}]'
}
)
Stellen Sie als Nächstes den Fehlerbehandler ein.
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)
Schließlich wird der Vorgang des Startens von "App" mit jedem bisher generierten Router in der Datei beschrieben.
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
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__)
#Einstellungen für die DB-Verbindung
config = {
'user': 'root',
'password': 'password',
'host': 'mysql',
'database': 'test_database',
'autocommit': True
}
def exist(memo_id: int) -> bool:
#Erstellen Sie einen DB-Client
conn = connector.connect(**config)
cursor = conn.cursor()
# memo_Überprüfen Sie, ob eine ID vorhanden ist
query = "SELECT EXISTS(SELECT * FROM test_table WHERE memo_id = %s)"
cursor.execute(query, [memo_id])
result: tuple = cursor.fetchone()
#Schließen Sie den DB-Client
cursor.close()
conn.close()
#Überprüfen Sie die Existenz, indem Sie prüfen, ob es ein Suchergebnis gibt
if result[0] == 1:
return True
else:
return False
@app.route('/memo/<int:memo_id>')
def get(memo_id: int) -> str:
#Überprüfen Sie, ob eine bestimmte ID vorhanden ist
is_exist: bool = exist(memo_id)
if not is_exist:
raise NotFound(f'memo_id [{memo_id}] is not registered yet.')
#Erstellen Sie einen DB-Client
conn = connector.connect(**config)
cursor = conn.cursor()
# memo_Führen Sie eine Suche nach ID durch
query = "SELECT * FROM test_table WHERE memo_id = %s"
cursor.execute(query, [memo_id])
result: tuple = cursor.fetchone()
#Schließen Sie den DB-Client
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:
#Überprüfen Sie, ob eine bestimmte ID vorhanden ist
is_exist: bool = exist(memo_id)
if is_exist:
raise Conflict(f'memo_id [{memo_id}] is already registered.')
#Holen Sie sich Wert von Anfrage
memo: str = request.form["memo"]
#Erstellen Sie einen DB-Client
conn = connector.connect(**config)
cursor = conn.cursor()
#Memo speichern
query = "INSERT INTO test_table (memo_id, memo) VALUES (%s, %s)"
cursor.execute(query, (memo_id, memo))
#Schließen Sie den DB-Client
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')
Jetzt haben Sie eine API, die die folgenden zwei Dinge ausführt:
In den folgenden Artikeln wird für jeden Teil der gesamte Code einschließlich der Containerumgebung im folgenden Repository gespeichert Wenn Sie es zur Hand bewegen möchten, lesen Sie bitte Folgendes.
Part1: https://github.com/y-tomimoto/CleanArchitecture/tree/master/part1
Nehmen wir ab dem nächsten Teil eine Spezifikationsänderungsanforderung für diese API an und wenden Sie die saubere Architektur Schritt für Schritt an.