单体应用优化-模块拆分以及RestTemplate使用
一、前言
在传统的单体应用中,我们所有的代码都写在一个project里面,这个project里面也没有划分模块,只是简单地用不同的包(文件夹)将一些业务分开了,随着业务的添加以及复杂化,我们的项目会变的越来越难以维护,而且这样的项目结构耦合性比较高,中间的调用关系后期会变的越来越模糊,后面我在网上看到了多模块开发以及在学习Dubbo时看到了如何将自己的项目进行模块拆分,所以在开始学习springcloud之前,我认为有必要梳理下自己从传统的javaweb项目的开发转向多模块的开发的记录,这也是这篇文章的书写初衷。
二、架构图设计
一个基础的基于springboot的javaweb项目基本包含Dao层
、Controller层
、Service层
、Model层
等,Dao层
实现了对数据库操作的接口,Model层
实现了对Entity
、VO
、RequestEntity
的定义,Service层
实现了对实际业务的封装,Controller层
实现了对接口的对外暴露。
基于此,我把单体项目拆分成了多个模块:controller模块
、service模块
、api模块
、model模块
、common模块
,调用关系如下图:
其中,各模块的职责定义:
模块名称 | 模块简称 | 职责 |
---|---|---|
common | 公共层 | 这里提供一些公共的工具类等,可以将比较独立的util 等放这里 |
model | 模型层 | 这里主要维护mybatis,维护mybatis生成的Entity 、Dao 。如果需要的话,这里也可以包含一层DelegateService ,这一层封装了对数据库的基本操作,其实也就是对Dao 做了定制,这样就可以不让Dao 层直接给service 使用,如果项目小的话,也可以将VO 层和RequestEntity 放在这里 |
api | 接口层 | 这里只定义了相关的接口inteface ,VO 层和RequestEntity 也可以放在这里。这样做的好处是如果后续使用Dubbo 或者springcloud 的话,可以直接打包该模块,然后让provide 和consumer 依赖使用,也可以基于此提供接口文档等。 |
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 |
postForLocation 、postForObject 、postForEntity
|
PUT | put |
DELETE | delete |
OPTIONS | optionsForAllow |
HEAD | headForHeaders |
因为使用方式都是比较简单的,所以这里就不赘述了,具体的使用可以参考教程网站和官方网站
四、实例
4.1 项目描述
这个项目也是为后面学springcloud
的demo基础,也是按照了上述的架构方式拆分成了多模块应用。
4.2 项目文件夹截图
项目文件夹截图4.3 model层
这一层里面维护了mybatis自动生成的entity
和dao
,我这里使用的是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层
- 项目文件夹
-
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里面定义的接口
- 文件夹结构
- 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() ;
}
}
五、总结
通过模块划分,可以很清楚的将不同的责任分给不同的模块,当然,我这个模块的划分只是参考了些网上的一些例子,实用性也还有待考究。但是可以看出其实单体服务与服务之间的通讯还是比较诟病的(在我没有去了解微服务的时候觉得这样已经很好了),如果使用微服务的框架的话,服务间的调用会非常方便,而且出错的可能性也会小很多很多。
网友评论