API 路由函数与现实世界对象方法的 API 设计中的代码重复
Code duplication in API design for URL route functions vs. real world object methods
我的 API 对象方法与 URL 路由函数设计中存在代码重复:
# door_model.py
class Door:
def open(self): # "Door.open" written once...
...
# http_api.py (the HTTP server is separated from the real-world object models)
@app.route('/api/door/open') # ... written twice
def dooropen(): # ... written three times
d.open() # ... written four times!
d = Door()
如何在类似的 API 设计中避免这种不必要的名称重复?(同时保持真实世界对象模型与 HTTP 服务器之间的分离)。
在使用对象模型(带有方法)和 URL 路由函数时,是否有一种通用模式可以避免不必要的名称重复? (几乎是模型视图控制器模式)
另见 Associate methods of an object to API URL routes with Python Flask。
您可以创建动态路由。您的案例的动态路线是 api/door/<action>
.
创建这样的路线以获得动态 url:
@app.route('api/door/<action:str>')
def door(action):
door = Door()
if action in door.actions:
if action.lower() == 'open':
door.open()
r = 'oppened door'
return r
创建一个名为 actions 的 class 变量以使代码运行。例如像这样 actions = ['open','close']
您可以利用烧瓶蓝图模式
在你的http_api.py
中使用
app = Flask(__name__)
# ... configs to app instance ...
app.register_blueprint(door_routes, url_prefix="/api/door")
在你的api/door.py
door_routes = Blueprint("door_routes", __name__)
door = Door()
@door_routes.route("/open")
def open():
d.open()
return
#other routes
或者,您可以使用:
class Door():
def open(self):
print("opened")
d = Door()
@app.route("/api/door/<action>", methods=["POST", "GET"])
def catch_all(action):
try:
function = getattr(d,action)
resp = function()
#return jsonify(resp),200 or the below line
return f"Executed {action}"
except Exception as e:
return f"URL {action} not found"
如果我们为每个模型动作声明一个路由并为每个动作做同样的事情(在你的情况下,调用相应的方法有或没有参数),它会重复代码。通常,人们使用设计模式(主要用于大型项目)和算法来避免代码重复。我想展示一个简单的示例,它定义一个通用路由并在一个处理程序函数中处理所有请求。
假设我们有以下文件结构。
application/
├─ models/
│ ├─ door.py
│ ├─ window.py
├─ main.py
Door
的原型看起来像
# door.py
class Door:
def open(self):
try:
# open the door
return 0
except:
return 1
def close(self):
try:
# close the door
return 0
except:
return 1
def openlater(self, waitseconds=2):
print("Waiting for ", waitseconds)
try:
# wait and open the door
return 0
except:
return 1
我有条件地设置 C 的退出代码,0
表示成功,1
表示错误或失败。
我们必须将模型动作分开并组合成一个,因为它们具有共同的结构。
+----------+----------+------------+----------------------+
| API base | model | action | arguments (optional) |
+----------+----------+------------+----------------------+
| /api | /door | /open | |
| /api | /door | /close | |
| /api | /door | /openlater | ?waitseconds=10 |
| /api | /window | /open | |
| /api | /<model> | /<action> | |
+----------+----------+------------+----------------------+
在我们通过使用界面将我们的组分开后,我们可以为每个组实现一个通用处理程序。
通用handler
实现
# main.py
from flask import Flask, Response, request
import json
from models.door import Door
from models.window import Window
app = Flask(__name__)
door = Door()
window = Window()
MODELS = {
"door": door,
"window": window,
}
@app.route("/api/<model>/<action>")
def handler(model, action):
model_ = MODELS.get(model)
action_ = getattr(model_, action, None)
if callable(action_):
try:
error = action_(**request.args)
if not error:
return Response(json.dumps({
"message": "Operation succeeded"
}), status=200, mimetype="application/json")
return Response(json.dumps({
"message": "Operation failed"
}), status=400, mimetype="application/json")
except (TypeError, Exception):
return Response(json.dumps({
"message": "Invalid parameters"
}), status=400, mimetype="application/json")
return Response(json.dumps({
"message": "Wrong action"
}), status=404, mimetype="application/json")
if __name__ == "__main__":
app.run()
因此您可以通过使用不同的 API 路径和查询参数来控制模型的动作。
我的 API 对象方法与 URL 路由函数设计中存在代码重复:
# door_model.py
class Door:
def open(self): # "Door.open" written once...
...
# http_api.py (the HTTP server is separated from the real-world object models)
@app.route('/api/door/open') # ... written twice
def dooropen(): # ... written three times
d.open() # ... written four times!
d = Door()
如何在类似的 API 设计中避免这种不必要的名称重复?(同时保持真实世界对象模型与 HTTP 服务器之间的分离)。
在使用对象模型(带有方法)和 URL 路由函数时,是否有一种通用模式可以避免不必要的名称重复? (几乎是模型视图控制器模式)
另见 Associate methods of an object to API URL routes with Python Flask。
您可以创建动态路由。您的案例的动态路线是 api/door/<action>
.
创建这样的路线以获得动态 url:
@app.route('api/door/<action:str>')
def door(action):
door = Door()
if action in door.actions:
if action.lower() == 'open':
door.open()
r = 'oppened door'
return r
创建一个名为 actions 的 class 变量以使代码运行。例如像这样 actions = ['open','close']
您可以利用烧瓶蓝图模式
在你的http_api.py
中使用
app = Flask(__name__)
# ... configs to app instance ...
app.register_blueprint(door_routes, url_prefix="/api/door")
在你的api/door.py
door_routes = Blueprint("door_routes", __name__)
door = Door()
@door_routes.route("/open")
def open():
d.open()
return
#other routes
或者,您可以使用:
class Door():
def open(self):
print("opened")
d = Door()
@app.route("/api/door/<action>", methods=["POST", "GET"])
def catch_all(action):
try:
function = getattr(d,action)
resp = function()
#return jsonify(resp),200 or the below line
return f"Executed {action}"
except Exception as e:
return f"URL {action} not found"
如果我们为每个模型动作声明一个路由并为每个动作做同样的事情(在你的情况下,调用相应的方法有或没有参数),它会重复代码。通常,人们使用设计模式(主要用于大型项目)和算法来避免代码重复。我想展示一个简单的示例,它定义一个通用路由并在一个处理程序函数中处理所有请求。
假设我们有以下文件结构。
application/
├─ models/
│ ├─ door.py
│ ├─ window.py
├─ main.py
Door
的原型看起来像
# door.py
class Door:
def open(self):
try:
# open the door
return 0
except:
return 1
def close(self):
try:
# close the door
return 0
except:
return 1
def openlater(self, waitseconds=2):
print("Waiting for ", waitseconds)
try:
# wait and open the door
return 0
except:
return 1
我有条件地设置 C 的退出代码,0
表示成功,1
表示错误或失败。
我们必须将模型动作分开并组合成一个,因为它们具有共同的结构。
+----------+----------+------------+----------------------+
| API base | model | action | arguments (optional) |
+----------+----------+------------+----------------------+
| /api | /door | /open | |
| /api | /door | /close | |
| /api | /door | /openlater | ?waitseconds=10 |
| /api | /window | /open | |
| /api | /<model> | /<action> | |
+----------+----------+------------+----------------------+
在我们通过使用界面将我们的组分开后,我们可以为每个组实现一个通用处理程序。
通用handler
实现
# main.py
from flask import Flask, Response, request
import json
from models.door import Door
from models.window import Window
app = Flask(__name__)
door = Door()
window = Window()
MODELS = {
"door": door,
"window": window,
}
@app.route("/api/<model>/<action>")
def handler(model, action):
model_ = MODELS.get(model)
action_ = getattr(model_, action, None)
if callable(action_):
try:
error = action_(**request.args)
if not error:
return Response(json.dumps({
"message": "Operation succeeded"
}), status=200, mimetype="application/json")
return Response(json.dumps({
"message": "Operation failed"
}), status=400, mimetype="application/json")
except (TypeError, Exception):
return Response(json.dumps({
"message": "Invalid parameters"
}), status=400, mimetype="application/json")
return Response(json.dumps({
"message": "Wrong action"
}), status=404, mimetype="application/json")
if __name__ == "__main__":
app.run()
因此您可以通过使用不同的 API 路径和查询参数来控制模型的动作。