本系列教程将会以企业中API基础功能封装为目标,用最新的Spring Boot 3.x
版本来逐步搭建和完善Rest API项目基础架构,并结合实际电商项目中API的实现需求来进行最佳实践。如果觉得对你有帮助,记得点赞收藏,关注小卷,后续更精彩!
API设计原则
企业最佳实践的API设计会从Controller
组件中将API接口剥离出来,以方便对API定义进行单独维护,比如增加各种注解(请求、参数映射注解、swagger在线文档注解、校验注解等等),这样Controller
组件将会变的很干净,只需要关注方法实现即可,这就是面向接口编程。
一定要定义API接口吗?我可以直接写
Controller
吗?当然可以直接写
Controller
组件,因为不同于service
层,控制器是web
框架调用的,抽取出接口貌似显得没有必要。但是如果是大中型的企业项目,即便做了微服务拆分,每个微服务的API
或许还是有几十个,接口更方便维护,而且可以借助swagger
的API生成工具帮助我们生成API接口
。后续我们将演示其用法。
DTO
一般在API接口接收的参数列表长度超出3时,我们就考虑用POJO
对象来接收,在表示层携带数据进行传输的对象,我们用DTO
。DTO的设计原则:
-
基本类型用包装类型
没有逻辑上的默认值,则默认值都为
null
-
DTO不要公用
尽量为每个API的出入参定义自己的
DTO
,即便里面大多数字段相同,这样在维护时不会相互影响 -
DTO的字段和json字段建立别名的映射
在DTO进行序列化或反序列化时,与之对应的json字段,一般是下划线的写法;而如果序列化的json需要被存储时尽量把别名起的短些,以减少存储空间
比如,添加购物车CartItemDTO
,用作添加购物车接口的入参和出参,作为出参时返回一个列表,代表当前用户的购物车中持久化的所有数据,这些数据会序列化为json字符串同步写入浏览器cookie。
package com.juan.demo.dto;
import ...
@NoArgsConstructor
@AllArgsConstructor
@Data
public class CartItemDTO {
@JsonProperty("p")
private Long productId;
@JsonProperty("q")
private Integer quantity;
}
这里对添加的商品id和添加的数量字段使用了@JsonProperty
注解的简化别名,这样序列化的json字符串可以保存cookie时有更多的空间。
这里对字段的getter
、setter
、toString()
以及无参构造、全参构造都用了Lombok
提供的注解来简化开发,后续DTO的定义中也会采用这种形式。
再来看商品相关的信息这里建了3个DTO类:
-
ProductDetailDTO
用于展示商品详情
-
ProductResultItemDTO
用于展示查询列表项
-
ProductSaveDTO
用于接收编辑的商品信息,以进行后续的保存操作
注意
这里为啥不建立一个
ProductDTO
,而要为每个API接口单独建一个DTO。可能有些小伙伴平时开发一个模块时,会为一个模块的保存和查询API接口建一个公用的DTO,这是不好的习惯,至少要分开建读写操作的DTO,因为在写入时可以对DTO字段增加校验注解,而读取操作返回的DTO不需要,可能会加一些格式化json数据的注解。
API接口
我们要始终遵循面向接口开发的思想,即便只有一种实现。比如这里的Controller
实现,如果我们希望扩展出另一个mock数据的Controller
实现,你就明白抽取接口的重要性了。
后台商品管理API接口
package com.juan.demo.api;
import ...
@RequestMapping("admin/products")
public interface ProductAdminAPI {
@PostMapping
void addProduct(ProductSaveDTO saveDTO);
@PutMapping
void updateProduct(ProductSaveDTO saveDTO);
@GetMapping("{id}")
ProductDetailDTO getProduct(@PathVariable("id") long id);
@DeleteMapping("{id}")
void deleteProduct(@PathVariable("id") long id);
@GetMapping
List<ProductResultItemDTO> listProducts();
}
说明
原先
Controller
中声明的各种注解,除了RestController
,其他的都放到接口中来声明。这里对商品资源的各种操作API的定义,遵循了Rest API的定义规范,对资源的不同操作,对应了不同的请求方法,这样url得以简化。
还要注意api的命名习惯,最前面是模块,然后是资源,这样方便我们后面拦截模块来做认证和授权。
关于返回值,这里我们会直接返回后台服务操作的实际结果类型,而不是通用的类型,因为通用结构我们后续会通过实现
ResponseBodyAdvice
接口来实现。
添加购物车API接口,可以简化为下面形式:
package com.juan.demo.api;
import ...
@RequestMapping("personal/cart")
public interface CartAPI {
@PostMapping
void addCartItem(@RequestBody CartItemDTO cartItemDTO);
}
再添加一个直接返回String
类型的hello API接口,注意,当直接返回字符串作为json内容的数据域时,spring boot框架默认会响应text/plain
格式的数据,这个通过实现ResponseBodyAdvice
接口来进行统一json格式响应时,会有坑存在,后续讲到时再关注。
package com.juan.demo.api;
import ...
public interface HelloAPI {
@GetMapping("hello")
String hello();
}
下一小节,我们将对以上设计的API
做一个简单的实现,并在此基础上进行Spring Boot
对API
支持特性的进一步实践。大家加油!