工程实践中的软件系统设计方案—问答系统后端
1. 项目背景介绍
工程实践项目是实现一个类似知乎的问答社区系统(后端)
- 
基本要求: - 支持问题、回答的发布和修改(文字)
- 问题拉链,支持按热度和时间序排序
- 回答点赞点踩
- 热门问题列表
 
- 
进阶要求: - 高并发
- 支持评论
- 问题、回答支持图片
- 支持第三方登录
- 支持第三方分享
 
2. 软件设计方案
2.1 系统架构
软件架构风格是描述某一特定应用领域中系统组织方式的惯用模式,常用的软件架构风格有如下几类:
- 
管道-过滤器 
- 
客户-服务器 
- 
P2P 
- 
发布-订阅 
- 
CRUD 
- 
层次化 
本项目是一个典型的 C/S (客户-服务器)架构模式,服务器负责所有数据的存储与组织,客户端通过 HTTP 访问服务器,请求相应的数据并向用户展示出来,同时接收用户输入,与用户做交互。
2.2 接口API
API 定义了客户端与服务端数据交互的基本格式,包括客户端以什么样的格式请求,请求参数,服务端以什么样的格式响应,正确响应与错误响应的不同内容等。
本项目采用 RestfulAPI 风格的接口设计,具体篇幅太大,就不展开叙述了,仅列举一下部分 API 的简要说明:
| 接口名称 | 接口地址 | 请求方式 | 请求参数 | 响应信息 | 备注 | 
|---|---|---|---|---|---|
| 用户注册 | /user/register | POST | 用户名,密码,密码确认 | 用户基本信息 | |
| 用户登录 | /user/login | POST | 用户名,密码 | 用户token,基本信息 | 用户token保存用户的登录状态,保存到本地,请求其它接口的时候带上。 | 
| 个人信息 | /user/me | POST | 用户token | 用户完整信息 | |
| 发布问题 | /questions | POST | 用户token,问题标题,可选的问题详细描述 | 问题基本信息 | 已登录用户才可发布问题,未携带token会拒绝服务 | 
| 查看问题 | /questions/{qid} | GET | 可选的用户token | 问题详细信息 | |
| 修改问题 | /questions/{qid} | PUT | 用户token,问题标题,可选的问题详细描述 | 问题基本信息 | 问题所属用户才可修改问题 | 
| 删除问题 | /questions/{qid} | DELETE | 用户token | ok | 问题所属用户才可删除问题 | 
| 首页推荐列表 | /questions | GET | limit请求数量,offset位置偏移 | 问题列表,每个问题包含一个一段时间内的热门回答信息 | |
| 问题热榜列表 | /hot_questions | GET | limit请求数量,offset位置偏移 | 问题列表 | |
| 回答问题 | /questions/{qid}/answers | POST | 用户token,回答内容 | 回答基本信息 | 已登录用户才可回答问题,未携带token会拒绝服务 | 
| 查看回答 | /questions/{qid}/answers/{aid} | GET | 可选的用户token | 回答详细信息 | |
| 修改回答 | /questions/{qid}/answers/{aid} | PUT | 用户token,回答内容 | 回答基本信息 | 问题所属用户才可修改回答 | 
| 删除回答 | /questions/{qid}/answers/{aid} | DELETE | 用户token | ok | 问题所属用户才可删除回答 | 
| 回答列表 | /questions/{qid}/answers | GET | limit请求数量,offset位置偏移,type排序方式 | 回答列表 | 
- 注:请求格式支持 form 和 json,响应格式统一为 json,错误响应均包含错误码和描述信息,以上仅说明正确响应
3. 软件架构的不同视图
软件架构模型是通过一组关键视图来描述的,同一个软件架构,由于选取的视角(Perspective)和抽象层次不同可以得到不同的视图,这样一组关键视图搭配起来可以完整地描述一个逻辑自洽的软件架构模型。一般来说,我们常用的几种视图有分解视图、依赖视图、泛化视图、执行视图、实现视图、部署视图和工作任务分配视图。
- 分解视图
对系统的常用分解方法有面向功能分解,面向数据分解,面向特征分解,面向并发分解等,根据本项目的特点,比较适合面向功能的分解方法,分解视图如下:
- 依赖视图
依赖视图展现了软件模块之间的依赖关系。本项目最前端使用 Nginx 部署应用,数据来源于后端的 MySQL 和 Redis 数据库,同时在应用层内各模块间也存在依赖关系,据此可以画出项目的依赖视图:
- 执行视图
执行视图展示了系统运行时的时序结构特点,比如流程图、时序图等。执行实体可以最终分解到软件的基本元素和软件的基本结构,因而与软件代码具有比较直接的映射关系。
本项目是一个后端项目,根据客户端的不同请求执行不同的操作,从整体上来说,每一次操作都可以用如下时序图概括:
- 实现视图
源代码的目录文件结构及其与软件架构的映射关系说明如下:
├── api              API控制层,负责处理请求
│   ├── v1           具体API版本,增量发布
├── cache            redis 缓存相关 
├── conf             项目的静态配置
├── middleware       中间件
├── model            数据库模型以及相关操作
├── routes           路由配置
├── serializer       将实体映射成不同的viewmodel,以及常用的响应信息
├── service          服务层,将比较复杂的业务从api层分离出来
├── utils            常用工具
| main.go            应用入口
- 部署视图
项目前期采用单点部署,各组件运行于一台服务器上,视图如下:
后期可升级为分布式结构来提高可靠性和提供更大的吞吐量:
- 分配视图
工作分配视图将系统分解成可独立完成的工作任务,以便分配给各项目团队和成员,本项目是一个面向 API 服务的程序,最终目标是实现一系列功能完备的接口,所以最好的分解方法就是按照接口分解,每个成员负责几个接口实现:
4. 核心数据结构设计
本项目采用 ORM(对象关系映射)来将项目的数据结构映射到数据库里,由框架负责管理数据库具体操作,核心数据结构如下:
- 用户模型
type User struct {
	gorm.Model
	Username    string      `gorm:"unique;not null;"`                              // 用户名
	Password    string      `gorm:"not null;"`                                     // 密码
	UserProfile UserProfile `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` // 关联用户信息
	Questions   []Question  `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` // 关联问题信息
	Answers     []Answer    `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` // 关联回答信息
}
type UserProfile struct {
	gorm.Model
	UserID      uint
	Nickname    string `gorm:"default:null"`         // 昵称
	Email       string `gorm:"unique;default:null;"` // 邮箱
	Avatar      string `gorm:"default:null;"`        // 头像
	Status      int    `gorm:"not null;default:0;"`  // 状态
	Description string `gorm:"default:null"`         // 个人描述
}
- 问题模型
type Question struct {
	gorm.Model
	UserID  uint     `gorm:"not null;"`                                     // 问题所属用户Id
	Title   string   `gorm:"not null;"`                                     // 标题
	Content string   `gorm:"type:text"`                                     // 内容
	Answers []Answer `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` // 关联回答信息
}
- 回答模型
type Answer struct {
	gorm.Model
	UserID     uint   `gorm:"not null;"`           // 回答所属用户Id
	QuestionID uint   `gorm:"not null;"`           // 回答所属问题Id
	Content    string `gorm:"type:text;not null;"` // 内容
}
5. 运行环境和技术选型
5.1 运行环境
项目使用 Docker 打包部署,可以运行于任何 Linux 服务器上。
5.2 技术选型
项目使用 Golang 开发,用到的组件主要有:
- Gin: 轻量级Web框架
- GORM: ORM工具,本项目需要配合MySQL使用
- Go-Redis: Golang Redis客户端,用于缓存相关功能
- godotenv: 开发环境下的环境变量工具,方便配置环境变量
- Jwt-Go: Golang JWT组件,本项目使用基于 jwt 实现的 token 来做身份验证
- crypto: Golang 加密算法库,本项目使用其中的 bcrypto 算法来加密用户密码
6. 项目概念原型的核心工作机制
项目概念原型的核心工作机制描述如下:
- 用户可以以游客身份进入系统,也可以登录进入,但游客身份只有问答系统的查看权限
- 从首页可以浏览推荐问题列表,或者从热榜查看热门问题,此外,也可以通过分类或者搜索查找自己感兴趣的问题
- 点击进入问题详情后,可以以一定的排序方式查看该问题的回答列表,可以对回答进行点赞点踩,评论等,当然也可以自己回答问题
- 首页提供了发布问题的按钮可以发出提问寻求解答
- 用户可以在在个人中心对个人信息和活动历史进行管理,比如更新自己的联系方式,查找自己曾经回答过的问题等等