جاوا و تکنولوژی های آن

java programming language

در این وبلاگ به بررسی نکات موجود در جاوا و تکنولوژی های آن می پردازیم

طبقه بندی موضوعی


در این بخش خواهیم دید که چگونه یک 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;
}







نظرات  (۰)

هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی