Java webSpring Cloud

单体应用优化-模块拆分以及RestTemplate使用

2020-04-26  本文已影响0人  Martain

单体应用优化-模块拆分以及RestTemplate使用

一、前言

​ 在传统的单体应用中,我们所有的代码都写在一个project里面,这个project里面也没有划分模块,只是简单地用不同的包(文件夹)将一些业务分开了,随着业务的添加以及复杂化,我们的项目会变的越来越难以维护,而且这样的项目结构耦合性比较高,中间的调用关系后期会变的越来越模糊,后面我在网上看到了多模块开发以及在学习Dubbo时看到了如何将自己的项目进行模块拆分,所以在开始学习springcloud之前,我认为有必要梳理下自己从传统的javaweb项目的开发转向多模块的开发的记录,这也是这篇文章的书写初衷。

二、架构图设计

一个基础的基于springboot的javaweb项目基本包含Dao层Controller层Service层Model层
等,Dao层实现了对数据库操作的接口,Model层实现了对EntityVORequestEntity的定义,Service层实现了对实际业务的封装,Controller层实现了对接口的对外暴露。
基于此,我把单体项目拆分成了多个模块:controller模块service模块api模块model模块common模块,调用关系如下图:

模块拆分图

​ 其中,各模块的职责定义:

模块名称 模块简称 职责
common 公共层 这里提供一些公共的工具类等,可以将比较独立的util等放这里
model 模型层 这里主要维护mybatis,维护mybatis生成的EntityDao。如果需要的话,这里也可以包含一层DelegateService,这一层封装了对数据库的基本操作,其实也就是对Dao做了定制,这样就可以不让Dao层直接给service使用,如果项目小的话,也可以将VO层和RequestEntity放在这里
api 接口层 这里只定义了相关的接口intefaceVO层和RequestEntity也可以放在这里。这样做的好处是如果后续使用Dubbo或者springcloud的话,可以直接打包该模块,然后让provideconsumer依赖使用,也可以基于此提供接口文档等。
service 业务逻辑层 这一层依赖api层,对api层定义的接口做了实现
controller 控制层 这里暴露REST接口

这个只是我自己对springboot业务的拆分理解,欢迎大佬指正

三、服务间调用-RestTemplate

如果我们没有使用微服务框架,如果两个服务之间需要互相调用的话,我们只能通过调用服务他暴露出来的http接口来实现服务的调用,这个时候主要能发出http请求即可满足需求,所以我们可以使用HttpClient或者OKHttp等网络请求框架,当然,spring也向我们提供了非常优雅的用于访问Rest服务的客户端-RestTemplate

RestTemplate可以非常简便的让我们对Rest风格的接口进行请求,它实现了所有的Http请求的方法,并很优雅的接受请求的结果,使用RestTemplate的话减少了创建一个请求繁琐的工作以及接收处理请求结果的业务,而且使得代码使用非常优雅。

RestTemplate针对http的各种请求提供了如下的方法:

Http方法 RestTemplate方法
GET getForEntity getForObject
POST postForLocationpostForObjectpostForEntity
PUT put
DELETE delete
OPTIONS optionsForAllow
HEAD headForHeaders

因为使用方式都是比较简单的,所以这里就不赘述了,具体的使用可以参考教程网站官方网站

四、实例

4.1 项目描述

​ 这个项目也是为后面学springcloud的demo基础,也是按照了上述的架构方式拆分成了多模块应用。

4.2 项目文件夹截图

项目文件夹截图

4.3 model层

​ 这一层里面维护了mybatis自动生成的entitydao,我这里使用的是mybatis 1.4.0。

其中dao文件夹和entity文件夹是mybatis自动生成的

4.4 api层

api层文件夹结构

4.5 common层

​ 因为业务比较简单,common层这里就简单的放了定义的Response结构体和定义了异常

/**
 * @author Martin
 * @version 1.0
 * @date 2020/3/29 9:28 上午
 */
public class OperationFailingException extends RuntimeException {

    public OperationFailingException(String message) {
        super(message);
    }
}
/**
 * @author Martin
 * @version 1.0
 * @date 2020/3/24 11:16 上午
 */
public enum  StatusCode {

    Success(0,"success"),
    Fail(-1,"fail"),
    InvalidParams(200,"无效的参数"),
    ItemNotExist(201,"商品不存在!");

    /**
     * 状态码
     */
    private Integer code;

    /**
     * 描述信息
     */
    private String msg; 
    StatusCode(Integer code,String msg){
        this.code = code;
        this.msg = msg;
    } 
    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

4.6 服务提供层

​ 服务提供层这里实现了api里面定义的接口

provide文件夹结构
/**
 * @author Martin
 * @version 1.0
 * @date 2020/3/29 9:29 上午
 */
@RestControllerAdvice
@Order(-80)
public class ExceptionAdvice {

    @ExceptionHandler(OperationFailingException.class)
    public BaseResponse<Void> operationFailHandle(OperationFailingException e){
        return BaseResponse.fail(e.getLocalizedMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public BaseResponse<Void> methodArgumentValidHandle(MethodArgumentNotValidException e){

        return BaseResponse.fail(e.getBindingResult().getFieldError().getDefaultMessage());
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public BaseResponse<Void> httpMessageNotReadableExceptionHandle(HttpMessageNotReadableException e){
        return BaseResponse.fail("参数解析错误,请检查参数格式");
    }


    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public BaseResponse<Void> SQLIntegrityConstraintViolationExceptionHandle(SQLIntegrityConstraintViolationException e){
        return BaseResponse.fail("数据库错误");
    } 
}
/**
 * @author Martin
 * @version 1.0
 * @date 2020/4/24 2:17 下午
 */
@Service
public class UserAdminService implements IUserAdminService {

    @Autowired
    UserService userService;

    /**
     * 通过id获取用户
     *
     * @param userId
     * @return
     */
    @Override
    public UserVO getUserById(Integer userId) {
        User user = userService.findById(userId);
        if (user==null){
            throw new OperationFailingException("用户不存在");
        }
        UserVO userVO = UserVO.of(user);
        return userVO;
    } 
    /**
     * 添加用户
     *
     * @param request
     * @return
     */
    @Override
    public int createUser(CreateUserRequest request) {
        User user = request.toEntity();
        int effectRows  = userService.createUser(user);
        if (effectRows == 0){
            throw new OperationFailingException("添加用户失败");
        }
        return effectRows;
    }
}
/**
 * @author Martin
 * @version 1.0
 * @date 2020/4/24 2:17 下午
 */
@RestController
@RequestMapping("/user")
public class UserAdminController {

    @Autowired
    IUserAdminService userAdminService;

    @GetMapping("/getUserById/{userId}")
    public BaseResponse<UserVO> getUserById(@PathVariable Integer userId){
        UserVO userVO = userAdminService.getUserById(userId);
        return BaseResponse.success(userVO);
    }

    @PostMapping("/createUser")
    public BaseResponse<Void> createUser(CreateUserRequest request){
        userAdminService.createUser(request);
        return BaseResponse.success();
    }
}

4.7 服务消费层

​ 消费层这里就比较简单的一个请求,先是将RestTemplate注入成Bean,然后直接请求。

consumer
/**
 * @author Martin
 * @version 1.0
 * @date 2020/4/24 4:48 下午
 */
@Configuration
public class ApplicationContextConfig {

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

}
/**
 * @author Martin
 * @version 1.0
 * @date 2020/4/24 4:44 下午
 */
@RestController
@RequestMapping("/user")
public class UserAdminController {
    @Resource
    RestTemplate restTemplate;
    @GetMapping("/getUser/{userId}")
    public BaseResponse getUser(@PathVariable String userId){
        ResponseEntity<BaseResponse> forEntity = restTemplate.getForEntity("http://localhost:8081/user/getUserById/" + userId, BaseResponse.class);
        return forEntity.getBody() ;
    }
}

五、总结

​ 通过模块划分,可以很清楚的将不同的责任分给不同的模块,当然,我这个模块的划分只是参考了些网上的一些例子,实用性也还有待考究。但是可以看出其实单体服务与服务之间的通讯还是比较诟病的(在我没有去了解微服务的时候觉得这样已经很好了),如果使用微服务的框架的话,服务间的调用会非常方便,而且出错的可能性也会小很多很多。

上一篇 下一篇

猜你喜欢

热点阅读