fastapi python2.7_Python-FastAPI 异步框架开发博客系统(一)数据篇

Frodo的第一个版本已经实现了,在下一个版本前,我将目前的开发思路整理成三篇文章,分别是数据篇、通信篇、异步篇。

简要系统分析

数据库设计是紧跟需求来的,在我本科学UML时,数据库设计是在需求分析和系统分析之后,架构设计之前的设计。但博客项目的需求比较简单,主要大需求:

内容管理(文章、用户、标签、评论、反馈、动态的增删改查)

管理员用户的验证、评论人用户的验证

小功能:边栏组件、归档、分类等

再简单地做一个系统分析:

博客前台页面(不需要认证,内容展示)

博文内容

博客作者

标签

访问量

管理页面(需要登录认证进行内容管理)

动态页面(需要认证)

评论(访问者需要登录认证)

接下来的工作就是根据功能需求设计前后台API,一般如果你是全栈自己开发的话,API形式可以随意些,因为后续还可以灵活调整。如果需要和前端同事合作的话,你需要严格按照restful风格编写,接口的参数、命名、方法和返回体的构造上严格体现需求。

API 格式理应最大化地体现功能需求

前后台API的形式也取决于所用技术,Frodo前台页面是选择模板渲染的,后台是使用Vue, 那么模板就可以在页面上编程,可以实时决定上下文,可以不事先指定。

后台API

url

method

params

response

info

api/posts

GET

limit:1

page: 页面数

with_tag

{'items': [post.*.,], 'total': number}

查询Posts

需要登录

api/posts/new

POST

FormData

title

slug

summary

content

is_page

can_comment

author_id

status

x

x

api/post/

GET/PUT/DELETE

x

items.*.created_at

items.*.author_id

items.*.slug

items.*.id

items.*.title

items.*.type

items.*._pageview

items.*.summary

status

items.*.can_comment

items.*.author_name

items.*.tags.*

total

需要登录

api/users

GET

x

{'items':[user.*.,], 'total': num}

需要登录

api/user/new

POST

FormData

active

name

email

password

avatar: avatar.png

x

需要登录

api/user/

GET/PUT

x

user.created_at

user.avatar

user.id

user.active

user.email

user.name

user.url(/user/3/)

ok (true)

需要登录

api/upload

POST/OPTIONS

x

x

na

api/user/search

GET

name

items.*.id

items.*.name

需要登录

api/tags

GET

x

items.*.name

需要登录

api/user/info

GET

user (token)

user{'name', 'avartar'}

相当于current_user

api/get_url_info

POST

url

x

na

api/status

POST

text, url, fids = ["png", ...]

r, msg, activity.id, activity.layout, activity.n_comments, activity.n_likes, activity.created_at, activity.can_comment, activity.attachments.*.layout, activity.attachments.*.url, activity.attachments.*.title, activity.attachments.*.size

数据库设计

设计数据库就是设计表、表字段、表关系。严格上要先绘制E-R模型图,他金石停留在逻辑层面的关系图。下一步根据E-R图,结合使用的数据库类型(关系、Nosql、KV还是图数据库)设计表关系图,随后要考虑如下几个方面:

数据存储在哪里?

小型记录数据存储在mysql (查询较快)

长数据如「博客内容」查询较慢 适合存储在内存数据库

分布式还是单一式存储?

那些是高频使用数据?

经常需要做查询的

需要经常累加、统计计算的

经常不变化的

持久化方案

数据库如何定期备份?

KV数据库的过期策略、定期存储策略

其实博客项目很多都不需要考虑,但再大的项目这些都需要考虑了。其实还应该考虑的是数据库并发访问的问题,这涉及到锁与同步机制,这部分我再通信 部分阐述。

思考过上述问题后,大致有如下图形:

上图中不同的颜色字段考虑了不同的特点,分别是:

数据库存储,选用mysql

KV存储,选用redis

高频字段项,需要缓存选用redis或memcached

ORM类设计模式

ORM 是简化SQL操作的产物,python将其做的最好的就是Django框架,主要做两件事:

类到数据库表的映射(通过改造元类实现,达到创建这些_类_时便有了table属性,注意不是类实例)

提供简化的面向对象的sql操作接口

表结构与表迁移

表结构在类中体现,Frodo使用的sqlalchemy是采用Column()类的形式。在类比较多是,建议先写一个_基类_,规定共有字段,在会面还可以规定共有方法。

from sqlalchemy import Column, Integer, String, DateTime, and_, desc

@as_declarative()

class Base():

__name__: str

@declared_attr

def __tablename__(cls) -> str:

return cls.__name__.lower()

@property

def url(self):

return f'/{self.__class__.__name__.lower()}/{self.id}/'

@property

def canonical_url(self):

pass

复制代码

上述的基类就规定了表名称为类名称的小写。接下来可以规定一些公共字段和方法:

id = Column(Integer, primary_key=True, index=True)

created_at = Column(DateTime)

@classmethod

def to_dict(cls,

results: Union[RowProxy, List[RowProxy]]) -> Union[List[dict], dict]:

if not isinstance(results, list):

return {col: val for col, val in zip(results.keys(), results)}

list_dct = []

for row in results:

dct = {col: val for col, val in zip(row.keys(), row)}

list_dct.append(dct)

return list_dct

复制代码

如id和created_at 都是公共字段,而to_dict是非常常用的序列化方法。

接下来就是单独的表,如Post表:

class Post(BaseModel, CommentMixin, ReactMixin):

STATUSES = (

STATUS_UNPUBLISHED,

STATUS_ONLINE

) = range(2)

status = Column(SmallInteger(), default=STATUS_UNPUBLISHED)

(TYPE_ARTICLE, TYPE_PAGE) = range(2)

created_at = Column(DateTime, server_default=func.now(), nullable=False)

title = Column(String(100), unique=True)

author_id = Column(Integer())

slug = Column(String(100))

summary = Column(String(255))

can_comment = Column(Boolean(), default=True)

type = Column(Integer(), default=TYPE_ARTICLE)

pageview = Column(Integer(), default=0)

kind = config.K_POST

复制代码

这其中是Column的类属性才是对应到数据库的属性,其他的是类其他功能需要而设定的。

需要注意的Post类重写了created_at字段, 这是规定默认的创建日期。

为什么继承的是 Basemodel ,这一点采用了一些元类编程方法,主要原因是异步,Basemodel类的设计在「异步篇」阐述。

接下来就是迁移到数据库了,你可以直接使用sqlalchemy的metadata.create(engine),但这不利于调试,alembic是单独做数据库迁移管理的。把你写好的类都导入到models/__init__.py中:

from .base import Base

from .user import User, GithubUser

from .post import Post, PostTag, Tag

from .comment import Comment

from .react import ReactItem, ReactStats

from .activity import Status, Activity

复制代码

在alembic的env.py文件中导入Base, 再规定迁移产生的行为。这样后连每次修改类(增加字段、更新字段属性等)可以使用alembic migrate来自动迁移。

类设计模式

数据库表建立完成,接下来就是最重要的,编写数据类,涉及到增删改查的基本操作和类特定的一些方法。此时从「需求」到「接口」再到「类方法」的设计需要考虑如下两点:

语言的特性可以带来什么,比如Python类中的@property, __get__、__call__等特色函数能付发挥作用?

类设计的思考,类方法,实例方法 甚至是 虚拟方法?

设计模式的使用,比如Frodo使用到的Mixin模式

本篇这是从类方法的功能设计来讲的,具体实现细节牵涉到的东西,比如负责通信的一些方法细节将在「通信篇」介绍。

接下来我们都能大致地画一个图:

上图挑选了几个代表性的类设计,不同的颜色表示不同的设计思路,当然了这些都是根据需求场景来的,这一步也可以在开发过程中不断调整:

Classmethod: 类方法,不需要实例化的方法,因为数据库字段属性都是类属性,因此很多数据操作的方法都不需要实例化,适合设计为类方法

Property: 属性方法,适合的场景多是需要频繁访问,但又需要数据io的情况,比如很多类都依赖作者id:

await cls.get_user_id()

await cls.user

复制代码Cached Decorator: 需要将结果缓存在redis的方法使用此类装饰器,例如:

@classmethod

@cache(MC_KEY_ALL_POSTS % '{with_page}')

async def get_all(cls, with_page=True):

if with_page:

posts = await Post.async_filter(status=Post.STATUS_ONLINE)

else:

posts = await Post.async_filter(status=Post.STATUS_ONLINE,

type=Post.TYPE_ARTICLE)

return sorted(posts, key=lambda p: p['created_at'], reverse=True)

复制代码

@cache的处理规则将在「通信篇」介绍。

Cached Property: 需要将结果缓存在内存的方法使用此类装饰器,他的场景是在一个调用过程中需要反复使用的数据,但获取昂贵。

@cached_property

async def target(self) -> dict:

kls = None

if self.target_kind == config.K_POST:

kls = Post

data = await kls.cache(ident=self.target_id)

elif self.target_kind == config.K_STATUS:

kls = Status

data = await kls.cache(id=self.target_id)

if kls is None:

return

return await kls(**data).to_async_dict(**data)

复制代码

例如一个请求中需要多次使用到await self.target 而target的获取是十分昂贵的,此时可以存储在程序内存中。当然了这一特性早已进入python的标准库functools.lri_cached, 但还没支持异步,@cached_property是参考别人的项目创造的类装饰器,他的实现在models/utils.py.

总结:数据库设计是十分重要的第一步,后续API的开发效率很大程度取决于此。而数据关系到具体的语言实现又需要综合考虑场景的多种特性。

PS: 写此文时,Frodo下一步的打算是Golang重写后台API,算是把Go真正用起来。Frodo的前端我没有全程手写,因此添加新功能模块有些困难,说到底我还只是后端工程师-.-..., 不过向全栈迈出一小步也算是进步吧~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值