逻辑数据抽象层

当开始一个项目时,我们要做的第一件事就是创建逻辑数据抽象层。这一步,是为了描述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属性呈现的,因为这样表达更直观。

最终,可以发现,基于数据库中的数据结构,我们能以非常灵活的方式,选择性地暴露数据。