Im vorherigen Teil 1, so monolithisch wie möglich,
** POST-Anfrage erhalten und Notizen speichern **
** Erhalten Sie eine GET-Anfrage und beziehen Sie sich auf das gespeicherte Memo **
Wir haben nur eine Memo-API vorbereitet.
In diesem Artikel basiert die Erklärung auf dem folgenden Code, der im vorherigen Kapitel erstellt wurde.
Part1 : https://qiita.com/y_tom/items/ac6f6a08bdc374336dc4
Ich habe eine Anfrage erhalten, die Spezifikationen der in Teil 1 erstellten "API, die mit dem Flask-Framework erstellt wurde" zu ändern.
** "Nehmen wir FastAPI anstelle von Flask für das Webanwendungsframework an." **
In Teil 1 betrachten wir ein Design, das gegenüber Spezifikationsänderungen resistent ist, unter der Annahme dieser Spezifikationsänderungsanforderung.
Ich habe nicht viele Fälle erlebt, in denen ich das Framework ersetzen möchte, aber ich dachte, es wäre ein leicht verständlicher Fall als Einführung, also habe ich ihn übernommen.
Abgesehen davon ist dies meine jüngste Erfahrung, aber aufgrund von Änderungen der Marktbedingungen wurde plötzlich der Antwortheader einer bestimmten Webanwendung hinzugefügt. Es gab eine Situation, in der ich einen bestimmten Header geben wollte.
Da das Header-Attribut in den letzten Jahren hinzugefügt wurde, wurde das zu diesem Zeitpunkt verwendete Webanwendungsframework verwendet In einigen Fällen wurde das Header-Attribut nicht unterstützt und das Webanwendungsframework selbst musste geändert werden. (Am Ende habe ich den Header roh in den benutzerdefinierten Header geschrieben und geantwortet, und ich habe nichts bekommen, aber ...)
Kommen wir nun zur Geschichte zurück.
Derzeit werden die folgenden Prozesse in "main.py" zusammengefasst.
main.py : https://github.com/y-tomimoto/CleanArchitecture/blob/master/part1/app/main.py
Was passiert, wenn Sie das Framework ändern, das Sie in Ihrem aktuellen Design verwenden?
Wenn Sie versuchen, das Framework von Flask auf Fast API zu ändern
Sie werden wahrscheinlich die folgenden Änderungen an der vorhandenen main.py
vornehmen.
Wenn Sie das aktuelle Design beibehalten und tatsächliche Änderungen an der vorhandenen Datei "main.py" vornehmen, wird Folgendes angezeigt.
main.py
from http import HTTPStatus
- from flask import Flask, request, jsonify, make_response
+ from fastapi import FastAPI, Form, Response
+ import uvicorn
from mysql import connector
- app = Flask(__name__)
+ app = FastAPI()
#Einstellungen für die DB-Verbindung
config = {
...
}
def exist(memo_id: int) -> bool:
...
- @app.route('/memo/<int:memo_id>')
+ @app.get('/memo/{memo_id}')
def get(memo_id: int) -> str:
...
- return jsonify(
- {
- "message": f'memo : [{result[1]}]'
- }
- )
+ return JSONResponse(
+ content={"message": f'memo : [{result[1]}]'
+ )
- @app.route('/memo/<int:memo_id>', methods=['POST'])
+ @app.post('/memo/{memo_id}')
- def post(memo_id: int) -> str:
+ async def post(memo_id: int, memo: str = Form(...)) -> str:
...
- return jsonify(
- {
- "message": "saved."
- }
- )
+ return JSONResponse(
+ content={"message": "saved."}
+ )
- @app.errorhandler(NotFound)
- def handle_404(err):
- json = jsonify(
- {
- "message": err.description
- }
- )
- return make_response(json, HTTPStatus.NOT_FOUND)
+ @app.exception_handler(NotFound)
+ async def handle_404(request: Request, exc: NotFound):
+ return JSONResponse(
+ status_code=HTTPStatus.NOT_FOUND,
+ content={"message": exc.description},
+ )
- @app.errorhandler(Conflict)
- def handle_409(err):
- json = jsonify(
- {
- "message": err.description
- }
- )
- return make_response(json, HTTPStatus.CONFLICT)
+ @app.exception_handler(Conflict)
+ async def handle_409(request: Request, exc: Conflict):
+ return JSONResponse(
+ status_code=HTTPStatus.CONFLICT,
+ content={"message": exc.description},
+ )
if __name__ == '__main__':
- app.run(debug=True, host='0.0.0.0') # DELETE
+ uvicorn.run(app=fastapi_app, host="0.0.0.0", port=5000) # NEW
Obwohl es möglich ist, die Spezifikationen auf diese Weise mit Gewalt zu ändern, gibt es einige Bedenken.
Dieser Fix ändert den Framework-Code in main.py
.
In main.py
wird jedoch nicht nur der Code beschrieben, der sich auf das Framework bezieht, sondern auch der ** Prozess des Abrufens und Speicherns von Notizen **, der ursprünglich von der Anwendung erwartet wurde.
Prinzip der Einzelverantwortung: Prinzip der Einzelverantwortung: https://note.com/erukiti/n/n67b323d1f7c5
Zu diesem Zeitpunkt können Sie versehentlich unnötige Änderungen am "Prozess des Erfassens und Speicherns von Notizen" vornehmen, den Sie ursprünglich von der Anwendung erwartet hatten **.
Ich möchte die Situation vermeiden, in der ich Korrekturen vornehme, während ich denke, dass dies versehentlich einen Fehler im Code verursachen kann, der bereits funktioniert.
In diesem Beispiel gibt es nur zwei Endpunkte. Wenn es sich jedoch um einen großen Dienst handelt und Sie mehrere Endpunkte haben, ist dieses Problem noch größer.
Offenes / geschlossenes Prinzip: Offenes / geschlossenes Prinzip: https://medium.com/eureka-engineering/go-open-closed-principle-977f1b5d3db0
Bedenken: ** Kann unnötige Änderungen an vorhandenem Code vornehmen, der ordnungsgemäß funktioniert **
Dieses Problem ist auf die Tatsache zurückzuführen, dass main.py
nicht nur das Framework enthält, sondern auch den ** Prozess zum Abrufen und Speichern von ** Notizen **, der ursprünglich von der Anwendung erwartet wurde.
Daher ist die Sorge dieses Mal "main.py",
Es scheint gelöst zu werden, indem es in ** Framework ** und ** Verarbeitung unterteilt wird, die ursprünglich von der Anwendung erwartet werden **.
Wenn der Code in Rollen unterteilt werden soll, kann der Umfang der Änderung anscheinend auf diese Rolle beschränkt werden.
In main.py
,
Es gibt zwei Prozesse.
Mit anderen Worten, in Bezug auf CleanArchitecture,
ist.
Bei der Interpretation mit CleanArchitecture ist in der folgenden Abbildung Folgendes dargestellt:
Es scheint, dass "1" als Web (Teil der Frameworks & Drivers-Schicht) beschrieben werden kann.
In Bezug auf "2" scheint es, da es sich um eine Funktion handelt, die ursprünglich von der Anwendung erwartet wurde, entweder der Ebene "Anwendungsgeschäftsregeln" oder der Ebene "Unternehmensgeschäftsregeln" zu entsprechen. Hier jedoch "Speichern Sie das Memo oder rufen Sie das Memo ab" Beschreiben wir die Funktion als MemoHandler.
Es scheint ausgedrückt zu werden als.
Teilen wir nun main.py
in die Frameworks & Drivers-Ebene auf: Web und MemoHandler.
Rufen Sie in main.py
die Ebene Frameworks & Drivers auf: Web Router,
Entwerfen Sie, um "memo_handler.py" von jedem Router aufzurufen.
Wenn Sie mit diesem Design das Framework ändern möchten, ändern Sie einfach das in main.py
aufgerufene Framework.
Der vorhandene Prozess memo_handler.py
selbst wird nicht geändert, sodass der vorhandene Prozess nicht versehentlich geändert wird.
.
├── memo_handler.py
└── frameworks_and_drivers
└── web
├── fastapi_router.py
└── flask_router.py
frameworks_and_drivers/web/fastapi_router.py
from fastapi import FastAPI, Form, Request
from fastapi.responses import JSONResponse
from werkzeug.exceptions import Conflict, NotFound
from memo_handler import MemoHandler
from http import HTTPStatus
app = FastAPI()
@app.get('/memo/{memo_id}')
def get(memo_id: int) -> str:
return JSONResponse(
content={"message": MemoHandler().get(memo_id)}
)
@app.post('/memo/{memo_id}')
async def post(memo_id: int, memo: str = Form(...)) -> str:
return JSONResponse(
content={"message": MemoHandler().save(memo_id, memo)}
)
@app.exception_handler(NotFound)
async def handle_404(request: Request, exc: NotFound):
return JSONResponse(
status_code=HTTPStatus.NOT_FOUND,
content={"message": exc.description},
)
@app.exception_handler(Conflict)
async def handle_409(request: Request, exc: Conflict):
return JSONResponse(
status_code=HTTPStatus.CONFLICT,
content={"message": exc.description},
)
frameworks_and_drivers/web/flask_router.py
from flask import Flask, request , jsonify , make_response
from werkzeug.exceptions import Conflict,NotFound
from http import HTTPStatus
from memo_handler import MemoHandler
app = Flask(__name__)
@app.route('/memo/<int:memo_id>')
def get(memo_id: int) -> str:
return jsonify(
{
"message": MemoHandler().get(memo_id)
}
)
@app.route('/memo/<int:memo_id>', methods=['POST'])
def post(memo_id: int) -> str:
memo: str = request.form["memo"]
return jsonify(
{
"message": MemoHandler().save(memo_id, memo)
}
)
@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)
MemoHandler
memo_handler.py
from mysql import connector
from werkzeug.exceptions import Conflict, NotFound
#Konfiguration für SQL Client
config = {
'user': 'root',
'password': 'password',
'host': 'mysql',
'database': 'test_database',
'autocommit': True
}
class MemoHandler:
def exist(self, memo_id: int):
#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
def get(self, memo_id: int):
#Überprüfen Sie, ob eine bestimmte ID vorhanden ist
is_exist: bool = self.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 f'memo : [{result[1]}]'
def save(self, memo_id: int, memo: str):
#Überprüfen Sie, ob eine bestimmte ID vorhanden ist
is_exist: bool = self.exist(memo_id)
if is_exist:
raise Conflict(f'memo_id [{memo_id}] is already registered.')
#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 "saved."
main.py
Wechseln Sie das zu übernehmende Framework auf "main.py".
main.py
import uvicorn
from frameworks_and_drivers.flask_router import app as fastapi_app
from frameworks_and_drivers.flask_router import app as flask_app
---
#Bei der Übernahme der Flasche als Rahmen
flask_app.run(debug=True, host='0.0.0.0')
---
#Schnell wie ein Rahmen_Bei der Annahme von API
uvicorn.run(app=fastapi_app, host="0.0.0.0",port=5000)
Der endgültige Code ist hier. : https://github.com/y-tomimoto/CleanArchitecture/blob/master/part2
Durch Ausschneiden jedes Frameworks in die Ebene Frameworks & Drivers: Web und Ausschneiden der Verarbeitung, die ursprünglich von der Anwendung für "MemoHandler" erwartet wurde,
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. Ich tat.
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.
Recommended Posts