美文网首页Java webSpring Cloud
单体应用优化-模块拆分以及RestTemplate使用

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

作者: Martain | 来源:发表于2020-04-26 21:40 被阅读0次

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

    • 文件夹结构

      model文件夹结构

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

    • 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层

    • 项目文件夹
    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结构体和定义了异常

    • 文件夹结构

      common层结构
    • 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里面定义的接口

    • 文件夹结构
    provide文件夹结构
    • 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,然后直接请求。

    • 文件夹结构
    consumer
    • 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() ;
        }
    }
    

    五、总结

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

    相关文章

      网友评论

        本文标题:单体应用优化-模块拆分以及RestTemplate使用

        本文链接:https://www.haomeiwen.com/subject/ohlswhtx.html