# 逻辑数据抽象层 当开始一个项目时,我们要做的第一件事就是创建逻辑数据抽象层。这一步,是为了描述Schema(资源结构),它表示哪些数据是完全映射数据库的数据结构,哪些是不完全映射。声明资源结构是使用[Marshmallow](https://marshmallow.readthedocs.io/en/latest/) / [marshmallow-jsonapi](https://marshmallow-jsonapi.readthedocs.io/)框架。Marshmallow是一个非常著名的序列化和反序列化的框架,它提供了许多可以用来抽象数据结构的特性;marshmallow-jsonapi则更进一步实现了JSONAPI 1.0规范,并且支持Flask集成。 例: 本例中,我们假设已经有了Person和Computer的ORM model,我们需要为它们创建数据抽象层。 ```python 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与资源的映射关系。 ```python 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': ''} 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': ''}, related_view='computer_list', related_view_kwargs={'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 = 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': ''}, related_view='person_detail', related_view_kwargs={'computer_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属性呈现的,因为这样表达更直观。 最终,可以发现,基于数据库中的数据结构,我们能以非常灵活的方式,选择性地暴露数据。