項目地址前端
博客地址python
Frodo的第一個版本已經實現了,在下一個版本前,我將目前的開發思路整理成三篇文章,分別是數據篇、通訊篇、異步篇。
數據庫設計是緊跟需求來的,在我本科學UML時,數據庫設計是在需求分析和系統分析以後,架構設計以前的設計。但博客項目的需求比較簡單,主要大需求:mysql
再簡單地作一個系統分析:react
博客前臺頁面(不須要認證,內容展現)git
接下來的工做就是根據功能需求設計先後臺API,通常若是你是全棧本身開發的話,API形式能夠隨意些,由於後續還能夠靈活調整。若是須要和前端同事合做的話,你須要嚴格按照restful風格編寫,接口的參數、命名、方法和返回體的構造上嚴格體現需求。github
API 格式理應最大化地體現功能需求
先後臺API的形式也取決於所用技術,Frodo前臺頁面是選擇模板渲染的,後臺是使用Vue, 那麼模板就能夠在頁面上編程,能夠實時決定上下文,能夠不事先指定。redis
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/<post_id> | 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 password avatar: avatar.png |
x | 須要登陸 |
api/user/<user_id> | 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仍是圖數據庫)設計表關係圖,隨後要考慮以下幾個方面:sql
數據存儲在哪裏?數據庫
那些是高頻使用數據?編程
持久化方案
其實博客項目不少都不須要考慮,但再大的項目這些都須要考慮了。其實還應該考慮的是數據庫併發訪問的問題,這涉及到鎖與同步機制,這部分我再通訊
部分闡述。
思考過上述問題後,大體有以下圖形:
上圖中不一樣的顏色字段考慮了不一樣的特色,分別是:
ORM 是簡化SQL操做的產物,python將其作的最好的就是Django
框架,主要作兩件事:
table
屬性,注意不是類實例)表結構在類中體現,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
來自動遷移。
數據庫表創建完成,接下來就是最重要的,編寫數據類,涉及到增刪改查的基本操做和類特定的一些方法。此時從「需求」到「接口」再到「類方法」的設計須要考慮以下兩點:
@property
, __get__
、__call__
等特點函數能付發揮做用?本篇這是從類方法的功能設計來說的,具體實現細節牽涉到的東西,好比負責通訊的一些方法細節將在「通訊篇」介紹。
接下來咱們都能大體地畫一個圖:
上圖挑選了幾個表明性的類設計,不一樣的顏色表示不一樣的設計思路,固然了這些都是根據需求場景來的,這一步也能夠在開發過程當中不斷調整:
await cls.get_user_id() await cls.user
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 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的前端我沒有全程手寫,所以添加新功能模塊有些困難,說到底我還只是後端工程師-.-..., 不過向全棧邁出一小步也算是進步吧~