美文网首页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使用

    单体应用优化-模块拆分以及RestTemplate使用 一、前言 ​ 在传统的单体应用中,我们所有的代码都写在...

  • Seata理念+部署+实践_超详细整理

    分布式事务 单体应用被拆分成微服务应用,原来的三个模块被拆分成多个独立应用,分别使用多个独立的数据源,此时每个服务...

  • 微服务架构之「 容错隔离 」

    我们知道,在单体应用的架构下一旦程序发生了故障,那么整个应用可能就没法使用了,所以我们要把单体应用拆分成具有多个服...

  • 微服务架构之「 容错隔离 」

    我们知道,在单体应用的架构下一旦程序发生了故障,那么整个应用可能就没法使用了,所以我们要把单体应用拆分成具有多个服...

  • 微服务从放弃到精通

    从0开始学微服务 概念 单体应用 不足:业务扩张导致系统膨胀 模块间调用方式:本地调用 微服务 特性拆分粒度一定程...

  • 服务治理之dubbo核心概念

    1. 为什么要做系统拆分?如何进行系统拆分?拆分后不用dubbo可以吗? 为什么要做系统拆分? 单体应用的弊端 部...

  • 日志优化实践经验分享

    背景 最近公司对原有单体应用进行业务拆分,将每个有自己特定功能的模块作为一个微服务,每个微服务单独部署,开发过程中...

  • 谈兄弟应用读取DB的问题

    软件系统从单体应用到多系统应用(水平拆分、垂直拆分)的架构演进方式,相信已经深入人心。服务化,高内聚低耦合。但是,...

  • 完美解决方案-雪花算法ID到前端之后精度丢失问题

    最近公司的一个项目组要把以前的单体应用进行为服务拆分,表的ID主键使用Mybatis plus默认 的雪花算法来生...

  • AWS Lambda 借助 Serverless Framewo

    前言 微服务架构有别于传统的单体式应用方案,我们可将单体应用拆分成多个核心功能。每个功能都被称为一项服务,可以单独...

网友评论

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

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