逻辑数据抽象层¶
当开始一个项目时,我们要做的第一件事就是创建逻辑数据抽象层。这一步,是为了描述Schema(资源结构),它表示哪些数据是完全映射数据库的数据结构,哪些是不完全映射。声明资源结构是使用Marshmallow / marshmallow-jsonapi框架。Marshmallow是一个非常著名的序列化和反序列化的框架,它提供了许多可以用来抽象数据结构的特性;marshmallow-jsonapi则更进一步实现了JSONAPI 1.0规范,并且支持Flask集成。
例:
本例中,我们假设已经有了Person和Computer的ORM model,我们需要为它们创建数据抽象层。
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
email = db.Column(db.String)
birth_date = db.Column(db.String)
password = db.Column(db.String)
class Computer(db.Model):
computer_id = db.Column(db.Integer, primary_key=True)
serial = db.Column(db.String)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
person = db.relationship('Person', backref=db.backref('computers'))
接下来,让我们创建逻辑数据抽象层,来解释model与资源的映射关系。
from marshmallow_jsonapi.flask import Schema, Relationship
from marshmallow_jsonapi import fields
class PersonSchema(Schema):
class Meta:
type_ = 'person'
self_view = 'person_detail'
self_view_kwargs = {'id': '<id>'}
self_view_many = 'person_list'
id = fields.Integer(as_string=True, dump_only=True)
name = fields.Str(required=True, load_only=True)
email = fields.Email(load_only=True)
birth_date = fields.Date()
display_name = fields.Function(lambda obj: "{} <{}>".format(obj.name.upper(), obj.email))
computers = Relationship(self_view='person_computers',
self_view_kwargs={'id': '<id>'},
related_view='computer_list',
related_view_kwargs={'id': '<id>'},
many=True,
schema='ComputerSchema',
type_='computer',
id_field='computer_id')
class ComputerSchema(Schema):
class Meta:
type_ = 'computer'
self_view = 'computer_detail'
self_view_kwargs = {'id': '<id>'}
id = fields.Str(as_string=True, dump_only=True, attribute='computer_id')
serial = fields.Str(required=True)
owner = Relationship(attribute='person',
self_view='computer_person',
self_view_kwargs={'id': '<id>'},
related_view='person_detail',
related_view_kwargs={'computer_id': '<id>'},
schema='PersonSchema',
type_='person')
我们能看到数据库model 与 schema的几个区别。
首先,让我们来比较下Person和PersonSchema:
- 我们能看到Person有password字段,但我们不想在API中暴露它,所以在PersonSchema中,没有设定该属性。
- PersonSchema中,有一个display_name属性,该属性实际上是拼接了name和email字段。
- 在PersonSchema中,我们定义了一个computers=Relationship(...),我们设定了id_field='computer_id',因为它是Computer(db.model)的主键。如果你没有设定id_field,那么它的默认值就是‘id’。
其次,让我们再来比较下Computer和ComputerSchema:
- computer_id字段,在api中,是以id属性呈现的。
- Computer与Person的关联字段person,在api中,是以owner属性呈现的,因为这样表达更直观。
最终,可以发现,基于数据库中的数据结构,我们能以非常灵活的方式,选择性地暴露数据。