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 路径和查询参数来控制模型的动作。