在构建三层Web应用程序项目时,我们遇到了一个问题,那就是如何将业务逻辑与数据访问分离。我们有两个可行的方案,但需要在项目开始前做出决定。
方案1:
将代码分为三层:
- 第一层:Web界面
- 第二层:信函处理
- 第三层:ORM模型(sqlalchemy)
方案2:
将代码分为相同的三层,但在第二层只执行对用户集合的循环。生成HTML、发票和文章列表的方法都作为方法添加到ORM提供的第三层模型定义中。第二层执行循环,但实际功能封装在第三层的模型类中。
2、解决方案 经过讨论,我们认为这两种方案都可以,各有优缺点:
方案1的优点:
- 完全将业务逻辑与数据库访问分离
- 阻止导入ORM模型也导入我们可能不需要的大量方法/功能,还可以使模型类的代码更加紧凑
- 在为测试模拟ORM模型时可能更容易使用
方案1的缺点:
- 当需要在多个视图中使用业务逻辑时,可能会导致代码重复
- 当需要测试业务逻辑时,可能会更难模拟
方案2的优点:
- 似乎与Django在Python中处理事情的方式一致
- 允许简单地访问方法:当存在模型实例时,可以立即调用它执行的任何函数
- 可以传递实例,并拥有所有适当的方法
方案2的缺点:
- 将所有业务逻辑都放在模型类中可能会导致代码难以维护
- 在需要测试业务逻辑时,可能会更难模拟模型类
最终,我们选择方案1,因为我们认为它更能满足我们的要求。我们认为,将业务逻辑与数据访问分离可以使我们的代码更容易维护和测试。
代码示例:
# 第一层:Web界面
@app.route('/letters')
def letters():
letters = Letter.query.all()
return render_template('letters.html', letters=letters)
# 第二层:信函处理
class LetterService:
def generate_html(self, letter):
html = """
<html>
<body>
<h1>Dear {letter.recipient_name},</h1>
<p>Your order has been processed and is on its way to you.</p>
<ul>
{% for item in letter.items %}
<li>{item.name} - {item.price}</li>
{% endfor %}
</ul>
<p>Total: {letter.total_price}</p>
</body>
</html>
"""
return html.format(letter=letter)
def generate_invoice(self, letter):
invoice = """
Invoice No.: {letter.invoice_number}
Date: {letter.date}
Customer: {letter.recipient_name}
Address: {letter.recipient_address}
Items:
{% for item in letter.items %}
{item.name} - {item.price}
{% endfor %}
Total: {letter.total_price}
"""
return invoice.format(letter=letter)
# 第三层:ORM模型
class Letter(db.Model):
id = db.Column(db.Integer, primary_key=True)
recipient_name = db.Column(db.String(255))
recipient_address = db.Column(db.String(255))
invoice_number = db.Column(db.String(255))
date = db.Column(db.Date)
items = db.relationship('Item', backref='letter')
total_price = db.Column(db.Float)
class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
price = db.Column(db.Float)
letter_id = db.Column(db.Integer, db.ForeignKey('letter.id'))