0
点赞
收藏
分享

微信扫一扫

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

单体应用优化-模块拆分以及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接口

三、服务间调用-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。

  • 文件夹结构

  • service 源码

    • UserService.java

      /**
      * @author Martin
      * @version 1.0
      * @date 2020/4/26 4:41 下午
      */
      public interface UserService {
      
      /**
      * 根据id查询用户
      * @param userId
      * @return
      */
      User findById(Integer userId);
      
      /**
      * 添加用户
      * @param user
      * @return
      */
      int createUser(User user);
      }
      
    • UserServiceImpl.java

      /**
       * @author Martin
       * @version 1.0
       * @date 2020/4/26 4:42 下午
       */
      @Service
      public class UserServiceImpl implements UserService {
      
          @Autowired
          UserMapper userMapper;
      
          @Override
          public User findById(Integer userId) {
              Optional<User> user = userMapper.selectOne(c -> c.where(UserDynamicSqlSupport.id, isEqualTo(userId)));
              return user.orElse(null);
          }
      
          @Override
          public int createUser(User user) {
              return userMapper.insertSelective(user);
          }
      }
      

4.4 api层

  • 项目文件夹

  • UserVO

    /**
     * @author Martin
     * @version 1.0
     * @date 2020/4/24 2:01 下午
     */
    @Data
    public class UserVO {
        private Integer id;
        private String userName;
        private Integer age;
    
        public static UserVO of(User user){
            UserVO userVO = new UserVO();
            BeanUtils.copyProperties(user,userVO);
            return userVO;
        }
    }
    
  • CreateUserRequest

    /**
     * @author Martin
     * @version 1.0
     * @date 2020/4/26 4:55 下午
     */
    @Data
    public class CreateUserRequest {
    
        private String userName;
    
        private Integer age;
    
        public User toEntity() {
            User user = new User();
            BeanUtils.copyProperties(this,user);
            return user;
        }
    }
    
  • IUserAdminService

    /**
     * @author Martin
     * @version 1.0
     * @date 2020/4/23 3:42 下午
     */
    public interface IUserAdminService {
    
        /**
         * 通过id获取用户
         * @param userId
         * @return
         */
        UserVO getUserById(Integer userId);
    
        /**
         * 添加用户
         * @param request
         * @return
         */
        int createUser(CreateUserRequest request);
    }
    

4.5 common层

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

  • 文件夹结构

  • OperationFaillIngException.java

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

    public OperationFailingException(String message) {
        super(message);
    }
}
  • StatusCode.java
/**
 * @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;
    }
}
  • BaseResponse.java

    /**
     * @author Martin
     * @version 1.0
     * @date 2020/3/24 11:29 上午
     */
    public class BaseResponse<T> implements Serializable {
    
        private Integer code;
        private String msg;
        private T data;
    
        public BaseResponse() {
        } 
        
        public BaseResponse(Integer code, String msg, T data) {
            this.code = code;
            this.msg = msg;
            this.data = data;
        } 
        public BaseResponse(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }
     
        public BaseResponse(StatusCode statusCode) {
            this.code = statusCode.getCode();
            this.msg = statusCode.getMsg();
        }
    
        public static <T> BaseResponse<T> success(){
            BaseResponse<T> response = new BaseResponse<T>(StatusCode.Success);
            return response;
        }
    
        public static <T> BaseResponse<T> success(T data){
            BaseResponse<T> response = new BaseResponse<T>(StatusCode.Success);
            response.setData(data);
            return response;
        }
    
        public static <T> BaseResponse<T> fail(){
            return new BaseResponse<T>(StatusCode.Fail);
        }
    
        public static <T> BaseResponse<T> fail(String msg){
            BaseResponse<T> response = new BaseResponse<T>(StatusCode.Fail);
            response.setMsg(msg);
            return response;
        } 
        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;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    

4.6 服务提供层

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

  • 文件夹结构

  • ExceptionAdvice
/**
 * @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("数据库错误");
    } 
}
  • UserAdminService.java
/**
 * @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;
    }
}
  • UserAdminController.java
/**
 * @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,然后直接请求。

  • 文件夹结构

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

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

}
  • UserAdminController.java
/**
 * @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() ;
    }
}

五、总结

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

举报

相关推荐

0 条评论