در این بخش خواهیم دید که چگونه یک REST api آماده را با CQRS تکامل بدهیم و از ویژگی های آن استفاده کنیم و لایه Service را از Controller جدا کنیم تا بتوانیم Query ها و Command ها را جداگانه مدیریت کنیم
توجه داشته باشید که مطالب این بخش برای کسانی که روی معماری و فرآیند ها در CQRS آشنایی دارند مفید خواهد بود لذا اگر پیش زمینه ندارید بهتر است قبل از خواندن ادامه مطلب نگاهی به آن داشته باشید
لایه Service :
در این لایه عملیات خواندنی و نوشتنی کلاینت ها را مشخص میکنیم که شامل یک کلاس برای Query ها و یک کلاس برای Command ها میشود
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);
 
}
* همانطور که در معماری CQRS و کد بالا قابل مشاهده است بخش Command ها مقدار برگشتی ندارند و دیتایی خوانده نمیشود
لایه کنترلر Query سرویس :
@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;
    }
}
نکته جالبی که وجود دارد در لایه Controller سرویس query به عنوان یک Bean تزریق شده است و وظیفه رسیدگی به درخواست های کاربر را دارد و از لایه سرویس Command ها ایزوله است و در یک ماژول جداگانه قابل استفاده است
لایه کنترلر Command سرویس :
@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);
    }
}
ویژگی جالبی که استفاده از معماری CQRS به API ما میدهد در هر یک از پیاده سازی ها دستورات متفاوتی استفاده شده و در آینده اگر بخواهیم تغییری با بهبودی بدهیم راحت تر قابل انجام است و در ثانی اگر بخواهیم Event Sourcing داشته باشیم مجموعه فرامین مرتبی داریم که میتوانیم از انها استفاده کنیم
ارائه منابع جداگانه :
در بخش قبل دیدیم که چطور فرامین را توانستیم از هم جدا کنیم حالا نوبت به جدا سازی Resource هایی است که در بخش Command ها و Query ها نیاز به استفاده داریم
کلاس DTO نگهداری اطلاعات کاربر بعد از اجرای Query :
public class UserQueryDto {
    private Long id;
 
    private String username;
 
    private boolean enabled;
 
    private Set<Role> roles;
 
    private long scheduledPostsCount;
}
کلاس DTO مورد نیاز برای Command ثبت نام کاربر جدید :
public class UserRegisterCommandDto {
    private String username;
    private String email;
    private String password;
}
کلاس DTO مورد نیاز برای Command آپدیت کردن password کاربر :
public class UserUpdatePasswordCommandDto {
    private String oldPassword;
    private String password;
}
کلاس DTO مورد نیاز Command ریست password و بصورت token موقتا نگهداری میشود :
public class UserTriggerResetPasswordCommandDto {
    private String email;
}
کلاس DTO برای Command ریست password و نگهداری پسورد جدید ریست شده :
public class UserChangePasswordCommandDto {
    private String password;
}
کلاس DTO نگهداری اطلاعات کاربر مربوط به Command تغییر دادن اطلاعات کاربر :
public class UserUpdateCommandDto {
    private Long id;
 
    private boolean enabled;
 
    private Set<Role> roles;
}