这一节我想阐述现代前后端工程中,前后端分离范式的作用、常用做法以及工程实践。
当涉及现代前后端开发中的前后端分离范式时,它旨在将前端与后端的开发和部署过程解耦,以提高灵活性、可维护性和协作效率。在这种范式下,前端和后端可以独立开发,测试和部署,通过API进行通信。这样做的好处包括:
灵活性与独立部署:前后端可以独立进行开发和部署,不需要相互依赖。这意味着可以选择最适合项目需求的技术栈,并且容易进行横向扩展。
协作效率:团队可以并行开发,前端和后端开发人员可以专注于自己的领域,通过明确定义的API接口进行协作,提高开发效率。
技术栈灵活性:采用前后端分离范式,可以选择更适合特定需求的前端和后端技术,而不会受到对方技术栈的限制。
参见知乎高赞文章:API是什么: 一篇讲透API - 吕攻城师的文章 - 知乎 https://zhuanlan.zhihu.com/p/347125981
总结一下,API = Application Programming Interface,是一种约定。编写API也就是通过编程语言(Java/Go/...)通过后端框架(Springboot/gin/...)向别的开发者(别的后端程序开发者或前端程序开发者)提供一组操作。
例如,win MFC提供了一组基于C++语言开发的操控windows界面的API,pytorch提供了一组基于python语言开发的操控GPU进行深度学习计算的API。这些都叫做API。
在上述API中,它们是通过源码/二进制进行分发的,也就是为了使用MFC提供的API,你必须有msvc相关的开发套件,为了使用pytorch提供的API,你必须在你的python环境中安装pytorch库。而通常在前后端开发的语境中,API指的是通过网络应用层协议HTTP方法(https://en.wikipedia.org/wiki/HTTP#Request_methods) 或RPC(https://en.wikipedia.org/wiki/Remote_procedure_call )进行调用,而无需安装的应用程序接口。
接下来,我们将会以一个online todo list为例,使用Java SpringBoot框架完成后端API的构建。
根据一般todolist的功能,我们可以做出以下设计:
1. 创建待办事项 端点: POST /api/todos 描述: 创建一个新的待办事项 请求体: 包含待办事项的描述、截止日期、优先级等信息 响应: 返回新创建的待办事项的信息 2. 获取所有待办事项 端点: GET /api/todos 描述: 获取所有待办事项列表 响应: 返回所有待办事项的列表 3. 获取单个待办事项 端点: GET /api/todos/:id 描述: 获取特定 ID 的待办事项 响应: 返回特定待办事项的信息 4. 更新待办事项 端点: PUT /api/todos/:id 描述: 更新特定 ID 的待办事项信息 请求体: 包含待办事项的描述、截止日期、优先级等信息 响应: 返回更新后的待办事项信息 5. 删除待办事项 端点: DELETE /api/todos/:id 描述: 删除特定 ID 的待办事项 响应: 返回成功/失败的消息 6. 完成待办事项 端点: PUT /api/todos/:id/complete 描述: 标记特定 ID 的待办事项为已完成 响应: 返回已完成的待办事项信息 7. 获取已完成的待办事项 端点: GET /api/todos/completed 描述: 获取所有已完成的待办事项列表 响应: 返回已完成的待办事项列表
这里出现了一些新词汇。例如“端点”(endpoint),endpoint通俗来说,就是HTTP方法+除去服务器地址的url部分。端点中出现的"
"实际上是指特定的一个数字,例如获取单个待办事项时,会向/api/todos/3这个url发起GET请求。一个todolist会包含很多条待办事项(todo),使用Todo表存储每条todo,每个todo项包含一些字段(field),可以设计如下:
用SQL语句描述就是
sqlCREATE TABLE todos (
id INTEGER PRIMARY KEY,
description TEXT,
deadline DATE,
priority TEXT,
completed BOOLEAN
);
说明: id 是主键,用于唯一标识每个待办事项。 description 是文本字段,存储待办事项的描述信息。 deadline 是日期字段,用于存储截止日期。 priority 是文本字段,存储待办事项的优先级。 completed 是布尔字段,用于标识待办事项是否已完成。可能的取值为 true 或 false。
这是一个简单的设计,可以根据实际需求进行调整和扩展。例如,你可以添加更多字段来记录创建时间、最后更新时间,或者将数据拆分为不同的表以支持更多的功能,比如用户身份验证和授权。
使用Navicat,连接postgresql 并新建数据库与表 可以看到,现在的数据库表是空的
使用IDEA创建springboot项目 然后添加这些依赖项 其中Spring Web用于创建RESTful API的应用程序,使用了Apache Tomcat作为默认的网页服务器 Mybatis是一个持久层框架,用于方便地操作数据库 Postgresql Driver是数据库驱动,JDBC的一种
(感兴趣的话可以查一查上面这些词有什么更深层的含义,例如RESTful、Apache Tomcat、持久层、JDBC等)
然后我们可以看到这样的文件结构
接着创建如下的目录结构
其中main目录下controller文件夹为控制器部分,service文件夹为服务层,model为模型层。resources目录下mapper为持久化映射层,存放mybatis xml文件。
首先要在项目配置文件中写入数据库相关配置
yml# postgresql configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/todolist
spring.datasource.username=xuqi
spring.datasource.password=
spring.datasource.driver-class-name=org.postgresql.Driver
然后在model层新建一个todo.java文件,内容为
javaimport java.util.Date;
public class Todo {
private Integer id;
private String description;
private Date deadline;
private String priority;
private boolean completed;
// 省略构造函数和getter/setter方法,使用IDEA自带的生成功能生成
}
然后在mapper文件夹中新建一个TodoMapper.xml,内容为
xml<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xuqi.todolist.model.TodoMapper">
<resultMap id="todoResultMap" type="com.xuqi.todolist.model.Todo">
<id property="id" column="id"/>
<result property="description" column="description"/>
<result property="deadline" column="deadline"/>
<result property="priority" column="priority"/>
<result property="completed" column="completed"/>
</resultMap>
<select id="getTodoById" parameterType="Integer" resultMap="todoResultMap">
SELECT * FROM todos WHERE id = #{id}
</select>
<select id="getAllTodos" resultMap="todoResultMap">
SELECT * FROM todos
</select>
<insert id="insertTodo" parameterType="com.xuqi.todolist.model.Todo">
INSERT INTO todos (id, description, deadline, priority, completed)
VALUES (#{id}, #{description}, #{deadline}, #{priority}, #{completed})
</insert>
<update id="updateTodo" parameterType="com.xuqi.todolist.model.Todo">
UPDATE todos
SET description = #{description},
deadline = #{deadline},
priority = #{priority},
completed = #{completed}
WHERE id = #{id}
</update>
<delete id="deleteTodo" parameterType="Integer">
DELETE FROM todos WHERE id = #{id}
</delete>
</mapper>
这个xml文件定义了mybatis如何通过sql语句生成对应的Java方法。要注意这里面的namespace="com.xuqi.todolist.model.TodoMapper"要和对应的Java类路径保持相同。parameterType="com.xuqi.todolist.model.Todo"也要和对应的model层的类路径相同。这里xml文件的编写要格外小心。
由于我们将xml文件放在resources目录下,我们需要在项目配置文件中添加如下配置:
yml# mybatis configuration
mybatis.mapper-locations=classpath:mapper/*.xml
显示指定xml文件的路径。(这也说明xml文件的路径是可以自定义的)
根据xml文件中定义的方法名(也就是xml的id项),编写对应的TodoMapper.java文件
javapackage com.xuqi.todolist.model;
import java.util.List;
import org.apache.ibatis.annotations.*;
@Mapper
public interface TodoMapper {
Todo getTodoById(Integer id);
List<Todo> getAllTodos();
void insertTodo(Todo todo);
void updateTodo(Todo todo);
void deleteTodo(Integer id);
}
这里使用了@Mapper注解,表示这个Java接口会被mybatis注入实现。注意这个接口的方法名和参数要与xml文件中的一一对应。
在service文件夹创建TodoService.java文件,文件内容如下:
javapackage com.xuqi.todolist.service;
import com.xuqi.todolist.model.Todo;
import com.xuqi.todolist.model.TodoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TodoService {
@Autowired
private TodoMapper todoMapper; // 这里需要注入TodoMapper
public Todo getTodoById(Integer id) {
return todoMapper.getTodoById(id);
}
public List<Todo> getAllTodos() {
return todoMapper.getAllTodos();
}
public void addTodo(Todo todo) {
todoMapper.insertTodo(todo);
}
public void updateTodo(Todo todo) {
todoMapper.updateTodo(todo);
}
public void deleteTodo(Integer id) {
todoMapper.deleteTodo(id);
}
}
这里使用了@Service注解表示这是一个Service层代码,使用@Autowired注解完成字段注入。
这里的方法是可以自定义的,不必要与xml文件中的相同。controller层将会调用这里的方法。
在controller文件夹下创建TodoController.java文件,完成了API设计中的1-5项,内容如下:
javapackage com.xuqi.todolist.controller;
import com.xuqi.todolist.model.Todo;
import com.xuqi.todolist.service.TodoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import java.util.List;
@RestController
@RequestMapping("/api/todos")
public class TodoController {
@Autowired
private TodoService todoService; // 这里需要注入TodoService
@GetMapping("/{id}")
public ResponseEntity<Todo> getTodoById(@PathVariable Integer id) {
Todo todo = todoService.getTodoById(id);
if (todo != null) {
return ResponseEntity.ok().body(todo);
} else {
return ResponseEntity.notFound().build();
}
}
@GetMapping
public ResponseEntity<List<Todo>> getAllTodos() {
List<Todo> todos = todoService.getAllTodos();
return ResponseEntity.ok().body(todos);
}
@PostMapping
public ResponseEntity<Void> addTodo(@RequestBody Todo todo) {
todoService.addTodo(todo);
return ResponseEntity.ok().build();
}
@PutMapping("/{id}")
public ResponseEntity<Void> updateTodo(@PathVariable Integer id, @RequestBody Todo todo) {
if (todoService.getTodoById(id) != null) {
todo.setId(id);
todoService.updateTodo(todo);
return ResponseEntity.ok().build();
} else {
return ResponseEntity.notFound().build();
}
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTodo(@PathVariable Integer id) {
if (todoService.getTodoById(id) != null) {
todoService.deleteTodo(id);
return ResponseEntity.ok().build();
} else {
return ResponseEntity.notFound().build();
}
}
}
注意这里的返回值用ResponseEntity包起来,请求体参数用@RequestBody注解标明,路径参数用@PathVariable注解标明。HTTP方法用对应的Mapping注解标明,这些注解中要标明端点,例如使用@RequestMapping("/api/todos")表示这个类下所有的HTTP方法都有共同的端点前缀/api/todos,@GetMapping("/{id}")表示这个Get方法具有一个路径端点(也就是上文提及的:id)
到这,整个Java springboot的代码已经编写完成了。
1. 测试创建待办事项 端点: POST /api/todos 描述: 创建一个新的待办事项 请求体: 包含待办事项的描述、截止日期、优先级等信息 响应: 返回新创建的待办事项的信息
这里注意这个POST方法的参数是@RequestBody,所以需要在Body中写入参数,而不是Params。
插入成功后数据库也更新为
2. 测试获取所有待办事项 端点: GET /api/todos 描述: 获取所有待办事项列表 响应: 返回所有待办事项的列表
通过上面的POST方法准备两条数据,再测试获取全部数据。
3. 测试获取单个待办事项 端点: GET /api/todos/:id 描述: 获取特定 ID 的待办事项 响应: 返回特定待办事项的信息
测试获取id为2的那条todo
4. 测试更新待办事项 端点: PUT /api/todos/:id 描述: 更新特定 ID 的待办事项信息 请求体: 包含待办事项的描述、截止日期、优先级等信息 响应: 返回更新后的待办事项信息
更新id为2的todo的描述,注意要在body中填写更新的数据。
数据库也变更为
这里由于在body中没有填写deadline与priority的值,所以这两个字段被更新为NULL
5. 测试删除待办事项 端点: DELETE /api/todos/:id 描述: 删除特定 ID 的待办事项 响应: 返回成功/失败的消息
测试删除第二条todo
数据库也更新为
本文作者:insomnia
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!