Spring 与 CQRS
Spring 中实现 CQRS
目的是将服务层和控制器层明确分开,以分别处理进入系统的读取–查询和写入–命令。
Service 层
首先,我们将原本的 UserService 拆分为 UserQueryService 与 UserCommandService:
public interface IUserQueryService {
List<User> getUsersList(int page, int size, String sortDir, String sort);
String checkPasswordResetToken(long userId, String token);
String checkConfirmRegistrationToken(String token);
long countAllUsers();
}
public interface IUserCommandService {
void registerNewUser(
String username,
String email,
String password,
String appUrl
);
void updateUserPassword(User user, String password, String oldPassword);
void changeUserPassword(User user, String password);
void resetPassword(String email, String appUrl);
void createVerificationTokenForUser(User user, String token);
void updateUser(User user);
}
Controller 层
Query Controller
@Controller
@RequestMapping(value = "/api/users")
public class UserQueryRestController {
@Autowired
private IUserQueryService userService;
@Autowired
private IScheduledPostQueryService scheduledPostService;
@Autowired
private ModelMapper modelMapper;
@PreAuthorize("hasRole('USER_READ_PRIVILEGE')")
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public List<UserQueryDto> getUsersList(...) {
PagingInfo pagingInfo = new PagingInfo(page, size, userService.countAllUsers());
response.addHeader("PAGING_INFO", pagingInfo.toString());
List<User> users = userService.getUsersList(page, size, sortDir, sort);
return users.stream().map(
user -> convertUserEntityToDto(user)).collect(Collectors.toList());
}
private UserQueryDto convertUserEntityToDto(User user) {
UserQueryDto dto = modelMapper.map(user, UserQueryDto.class);
dto.setScheduledPostsCount(scheduledPostService.countScheduledPostsByUser(user));
return dto;
}
}
这里有趣的是查询控制器仅注入查询服务,更加有趣的是,通过将这些控制器放在单独的模块中,可以切断该控制器对命令服务的访问。
Command Controller
@Controller
@RequestMapping(value = "/api/users")
public class UserCommandRestController {
@Autowired
private IUserCommandService userService;
@Autowired
private ModelMapper modelMapper;
@RequestMapping(value = "/registration", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void register(
HttpServletRequest request,
@RequestBody UserRegisterCommandDto userDto
) {
String appUrl = request
.getRequestURL()
.toString()
.replace(request.getRequestURI(), "");
userService.registerNewUser(
userDto.getUsername(),
userDto.getEmail(),
userDto.getPassword(),
appUrl
);
}
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/password", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
public void updateUserPassword(
@RequestBody UserUpdatePasswordCommandDto userDto
) {
userService.updateUserPassword(
getCurrentUser(),
userDto.getPassword(),
userDto.getOldPassword()
);
}
@RequestMapping(value = "/passwordReset", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void createAResetPassword(
HttpServletRequest request,
@RequestBody UserTriggerResetPasswordCommandDto userDto
) {
String appUrl = request
.getRequestURL()
.toString()
.replace(request.getRequestURI(), "");
userService.resetPassword(userDto.getEmail(), appUrl);
}
@RequestMapping(value = "/password", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void changeUserPassword(
@RequestBody UserchangePasswordCommandDto userDto
) {
userService.changeUserPassword(getCurrentUser(), userDto.getPassword());
}
@PreAuthorize("hasRole('USER_WRITE_PRIVILEGE')")
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
public void updateUser(@RequestBody UserUpdateCommandDto userDto) {
userService.updateUser(convertToEntity(userDto));
}
private User convertToEntity(UserUpdateCommandDto userDto) {
return modelMapper.map(userDto, User.class);
}
}
对象
将命令和查询分离之后,现在让我们快速浏览一下 User 资源的不同表示形式:
public class UserQueryDto {
private Long id;
private String username;
private boolean enabled;
private Set<Role> roles;
private long scheduledPostsCount;
}
public class UserRegisterCommandDto {
private String username;
private String email;
private String password;
}
public class UserUpdatePasswordCommandDto {
private String oldPassword;
private String password;
}
public class UserTriggerResetPasswordCommandDto {
private String email;
}
public class UserChangePasswordCommandDto {
private String password;
}
public class UserUpdateCommandDto {
private Long id;
private boolean enabled;
private Set<Role> roles;
}