背景
最近研究大佬代码,发现了一个贼特么有意思的前端设计模式实现,在这里记录一下
项目结构
- 数据存储:可适配各种存储,redis,hbase,hive,mysql,tidb,mongo等,无所谓
- 数据访问:上述技术栈对应的数据访问
- 服务:常见的Service
- 控制器:常见的controller
如何体现前端设计模式
众所周知,前端设计模式指的是用一个统一的接口收纳请求,再根据不同的条件分发给不同的服务实现。 为什么要采用前端模式?因为如果只是普通的注入服务,就容易出现这么一种情况:
- 重复开发各种VO用来封装返回值
- 重复开发分页相关的功能
- 服务数量大量膨胀导致服务管理性下降
而采用前端设计模式,我们可以通过统一封装VO,统一封装分页,统一服务的基本返回类型,来实现可控扩容。更妙的是,如果我们想要隐藏真实的服务提供类或者实现,并且实现分层校验,这种方式也能很好的满足需要。
案例
假如存在服务 BussinesService 接口,而对于不同部门的不同bussines有不同的实现,我们就可以这么玩:
基准服务接口
import com.xu.util.PageUtil;
import java.util.Map;
public interface BussinesService {
PageUtil generateRes(Map<String, String> params);
String getServiceId();
}
我们用serviceId来唯一标记一个服务,用PageUtil来统一封装返回值,用params来统一封装所有入参。这样所有子服务都实现这个顶层服务。
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PageUtil<T> {
private long total;
private int currentPage;
private int currentSize;
private int limit;
private List<T> list;
private String desc;
private T data;
}
那就有同学要问了,这样服务注入的时候不还是好麻烦么,有啥用啊?
服务统一加载
import com.xu.service.BussinesService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Configuration
public class BussinesServiceConfig {
private Map<String, BussinesService> allService;
@Autowired
public void setAllService(List<BussinesService> bussinesServices) {
allService = new HashMap<>(bussinesServices.size());
bussinesServices.forEach(e -> allService.put(e.getServiceId(), e));
}
@Bean
public Map<String, BussinesService> allService() {
return this.allService;
}
}
我们写一个config类,用于项目启动时一次性把可能用到的服务都加载到map里,实际上就是简化的IOC。当我们需要使用某个服务时,只需要解析调用方的参数,从map拿数据即可。 那又有人要问,为什么要用Map把请求参数也统一起来呢? 一是形式上方便检查,二是可以预留动态性。 预留动态性,是说万一以后接口的参数要做成可编辑可配置的动态参数表,那用Map就方便很多,而且可以通过在数据库里保存接口参数的相关信息实现动态校验,岂不美哉?
参数的基本检验
为了检测的方便,我们预先定义一下必须 / 容易出错的字段:
public class KeyPropertiesConfig {
public static final String SERVICE_ID = "serviceId";
public static final String PAGE = "page";
public static final String SIZE = "size";
public static final String LIMIT = "limit";
}
也不做的太麻烦,这里就是展示一下把参数检验剥离出服务流程的方式:
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ParamErrorEnum {
CORRECT(0, "正常"),
WITHOUT_SERVICE_ID (-1, "缺少服务ID"),
PAGE_SIZE_NUM_ERROR(-2, "页码或每页条数数值有误"),
PAGE_LACK_ERROR(-3, "页码缺失"),
SIZE_LACK_ERROR(-4, "条数缺失"),
LIMIT_NUM_ERROR(-5, "限定数值错误"),
OTHERS(-6, "无效检查");
private final int code;
private final String desc;
public static ParamErrorEnum valueOf(Integer code) {
if (code == null) {
return ParamErrorEnum.OTHERS;
}
for (ParamErrorEnum value : ParamErrorEnum.values()) {
if (code == value.code) {
return value;
}
}
return ParamErrorEnum.OTHERS;
}
}
以及实际工具类:
import com.xu.config.KeyPropertiesConfig;
import com.xu.enums.ParamErrorEnum;
import java.util.Map;
public class ParamValidator {
public static ParamErrorEnum paramCheck(Map<String, String> param) {
if (!param.containsKey(KeyPropertiesConfig.SERVICE_ID)) {
return ParamErrorEnum.WITHOUT_SERVICE_ID;
}
if (param.containsKey(KeyPropertiesConfig.PAGE) && param.containsKey(KeyPropertiesConfig.SIZE)) {
int page = Integer.parseInt(param.get(KeyPropertiesConfig.PAGE));
int size = Integer.parseInt(param.get(KeyPropertiesConfig.SIZE));
if (page < 1 || size < 0) {
return ParamErrorEnum.PAGE_SIZE_NUM_ERROR;
}
}
if (param.containsKey(KeyPropertiesConfig.PAGE) && !param.containsKey(KeyPropertiesConfig.SIZE)) {
return ParamErrorEnum.SIZE_LACK_ERROR;
}
if (param.containsKey(KeyPropertiesConfig.SIZE) && !param.containsKey(KeyPropertiesConfig.PAGE)) {
return ParamErrorEnum.PAGE_LACK_ERROR;
}
if (param.containsKey(KeyPropertiesConfig.LIMIT)) {
int limit = Integer.parseInt(param.get(KeyPropertiesConfig.LIMIT));
if (limit < 1) {
return ParamErrorEnum.LIMIT_NUM_ERROR;
}
}
return ParamErrorEnum.CORRECT;
}
}
包装具体服务
为了能有一些中间处理过程,我们把具体服务的中间过程也封装起来:
import com.xu.enums.ParamErrorEnum;
import com.xu.util.PageUtil;
import com.xu.util.ParamValidator;
import org.springframework.stereotype.Service;
import java.util.Map;
import javax.annotation.Resource;
@Service
public class ResultService {
@Resource
private Map<String, BussinesService> allService;
public PageUtil getRes(Map<String, String> params) {
ParamErrorEnum paramCheck = ParamValidator.paramCheck(params);
if (paramCheck.getCode() < 0) {
return PageUtil.builder().desc(String.format("参数检定错误,原因为:%s", paramCheck.getDesc())).build();
}
return allService.get(params.get(KeyPropertiesConfig.SERVICE_ID)).generateRes(params)
}
}
最终的实际服务类
例如有两个部门A和B
我们分别定义A和B的所有服务
@Getter
@AllArgsConstructor
public enum DepartmentA {
ACCOUNT_SERVICE("accountService", "账户服务"),
LOGIN_SERVICE("loginService", "登陆服务"),
UNKNOWN("unknown", "未知服务")
;
private final String code;
private final String desc;
}
@Getter
@AllArgsConstructor
public enum DepartmentB {
EMAIL_SERVICE("emailService", "邮箱服务"),
PAY_SERVICE("payService", "付款服务"),
UNKNOWN("unknown", "未知服务")
;
private final String code;
private final String desc;
}
然后开始实现
@Service
public class DepartmentAServiceAccount implements BussinesService {
@Override
public PageUtil generateRes(Map<String, String> params) {
return PageUtil.builder().build();
}
@Override
public String getServiceId() {
return DepartmentA.ACCOUNT_SERVICE.getCode();
}
}
public class DepartmentBServiceLogin implements BussinesService {
@Override
public PageUtil generateRes(Map<String, String> params) {
return null;
}
@Override
public String getServiceId() {
return DepartmentA.LOGIN_SERVICE.getCode();
}
}
这样一来,我们搭建起了以顶层接口为模板的统一服务架构
怎么样,是不是有一种很优雅很协调的快感?
控制器
最终我们就会看到,所有服务均统一在一个接口中:
@RestController
@RequestMapping("bussines")
public class BussinesController {
@Resource
private ResultService resultService;
@PostMapping("/realService")
private ApiReturn<PageUtil> useBussinesService(@RequestBody Map<String, String> params) {
return new ApiReturn<>(resultService.getRes(params));
}
}
极致的简单,极致的享受
|