This repo contains Flask-Blog
project, which is a basic blogging application.
We separate user_service
and post_service
out as a Flask-based web services:
-
user_service
is responsible for all the logics and information related to users, and talks toPostgreSQL
directly.- The user following system is also implemented in
user_service
, and presented in the main Flask-Blog app.
Defined resources:
-
UserList
Route:
/users
Method Description Request Form Schema Response Status Code GET Returns the user with a specified username or email Query: username
: stringemail
: string200 on success, 400 on no username or email privided, 404 on user not found POST Adds a user with the given name, email and password username
: stringemail
: stringpassword
: string201 on success, 400 on invalid data provided -
UserItem
Route:
/users/<int:id>
Method Description Request Form Schema Response Status Code GET Returns the user with the specified ID 200 on success PUT Updates the user with the specified ID username
: stringemail
: stringimage_filename
: string200 on success, 400 on invalid data provided -
UserAuth
Route:
/user-auth/
Method Description Request Form Schema Response Status Code GET Handles user authentication Query: email
: string
JSON:password
: string200 on success, 404 on user not found, 401 on authentication failed -
UserFollow
Route:
/user-follow/<int:follower_id>/<followed_username>
Method Description Request Form Schema Response Status Code POST Lets the given follower follow the user with the given username 201 on success, 404 on followee not found, 400 on invalid data provided DELETE Lets the given follower unfollow the user with the given username 204 on success, 404 on followee not found, 400 on invalid data provided
- The user following system is also implemented in
-
post_service
is responsible for all the logics and information related to user posts, and talks toPostgreSQL
directly.Defined resources:
-
PostList
Route:
/posts
Method Description Request Form Schema Response Status Code GET Returns all posts (optionally with some filters) Query user
: stringauthor
: string200 on success POST Adds a new post user_id
: inttitle
: stringcontent
: string201 on success -
PostItem
Route:
/posts/<int:id>
Method Description Request Form Schema Response Status Code GET Returns the post with the specified ID 200 on success, 404 on post not found POST Updates the post with the specified ID title
: stringcontent
: string200 on success DELETE Deletes the post with the specified ID 204 on success -
PostLike
Route:
/posts/<int:post_id>/likes
Method Description Request Form Schema Response Status Code POST Likes the given post 201 on success, 404 on post not found -
PostComment
Route:
posts/<int:post_id>/comments
Method Description Request Form Schema Response Status Code POST Comments on the given post user_id
: inttext
: string201 on success, 404 on post not found
-
-
Marshmallow/Flask-Marshmallow
is used for schema definition & deserialization (including validation) / serialization. -
Since these web services are backed by
PostgreSQL
database,Flask-SQLAlchemy
module is used for ORM-related tasks.
The communication between the main Flask-Blog app and the web services is through RESTful API, via JSON
.
In this way, the original Flask-Blog app now becomes a "skeleton" or a "gateway", which talks to user_service
and post_service
, uses the fetched data to render HTML templates.
-
The main Flask-Blog app uses
WTForms
andFlask-WTF
to implement forms. -
REST架构中要求client-server的communication应该是"无状态"的, 即一个request中必须包含server (service)处理该request的全部信息, 而在server-side不应保存任何与client-side有关的信息, 即server-side不应保存任何与某个client-side关联的session.
=> 然而, 我们应该区分"resource state"和"application state": REST要求的无状态应该是对resource的处理无状态, 然而在main application本身里面我们需要保存应用状态, 即user的login和session等.
Thus, in the original Flask-Blog app, we still use
Flask-Login
to handle user log-in/log-out and authentication issues, as well as session management.
In the original Flask-Blog app, we also provide OpenID Connect authentication options, from either Google or GitHub, to sign-in and log-in to the app.
For a general, high-level introduction to OAuth 2.0 authorization and OpenID Connect authentication, as well as different workflows, check out this note:
-
Google Sign-In
Google Sign-In workflow in my Flask-Blog app:
(Essentially, this is an Implicit Flow for OpenID Connect.)
- Front-end implementation according to https://developers.google.com/identity/sign-in/web/sign-in
- Back-end implementation according to https://developers.google.com/identity/sign-in/web/backend-auth
-
GitHub Sign-In
GitHub Sign-In workflow in my Flask-Blog app:
(Essentially, this is a simple variation of Authorization Code Flow for OAuth 2)
- Front-end implementation by myself
- Back-end implementation according to https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#web-application-flow
Side Note:
The workflows can also be implemented with Python frameworks. Here are some popular ones:
-
requests-oauthlib
(https://requests-oauthlib.readthedocs.io/en/latest/), which is built on top ofoauthlib
, usingrequests
- OAuth 2
-
flask-dance
(https://flask-dance.readthedocs.io/en/latest/), which is built on top ofrequests-oauthlib
, specifically as aFlask
extension- OAuth 2
- OpenID Connect
-
authlib
(https://docs.authlib.org/en/latest/), meant to be the "monolithic, ultimate solution"(The documentation too wordy for me to read, so I didn't... But feel free to check it out if you want)
-
-
As a presentation of the user following system, a logged-in user is able to choose to show only the posts authored by himself and the followed users.
-
Asynchronous tasks:
Some actions in the app takes long time to run, which blocks the server to handle the request.
Thus,
Celery
is used as an asynchronous task queue (withRedis
as the broker (message queue)) to handle those long-running tasks, like sending email to users.
$ pipenv --python=3.7
$ pipenv shell
# Install all the packages specified in Pipfile
$ pipenv install
Normal development...
For "Dockerized", check out https://github.com/Ziang-Lu/Flask-Blog/blob/master/Deployment%20Options.md#dockerization-linux-server--web-server-in-docker-container--python-web-app-wsgi-server-in-docker-container for details
If the dependencies ever changed:
# Update requirements.txt from Pipenv.lock
$ pipenv lock -r > requirements.txt
# Since flask_app, user_service, and post_service almost depend on the same Python dependencies, when putting them into separate Docker images:
# - We created a base image, which contains all the needed Python dependencies
# - Let separate images inherit from this base image, so that the Python dependencies are downloaded once in the base image, and can be reused among all the separate images.
# Build, tag, and push the base image, on which other images are dependent of
$ ./docker_base_exec.sh
To run the Dockerized application, every time some changes are made to the codes:
# Build and tag the service images
$ ./docker_services_exec.sh
# Use docker-compose to orchestrate the services (containers)
$ docker-compose up
After running the application:
$ docker-compose down -v # Also remove the volumes
This repo is distributed under the MIT license.