החל CQRS על ממשק API REST של Spring
רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:
>> בדוק את הקורס1. סקירה כללית
במאמר מהיר זה, אנו נעשה משהו חדש. אנו הולכים לפתח ממשק API קיים של REST Spring ולגרום לו להשתמש בהפרדת אחריות של שאילתת פיקוד - CQRS.
המטרה היא להפריד בבירור בין שכבות השירות והן של שכבת הבקר להתמודד עם קריאות - שאילתות וכתיבה - פקודות הנכנסות למערכת בנפרד.
יש לזכור כי זהו רק צעד ראשון מוקדם לעבר אדריכלות מסוג זה, ולא "נקודת הגעה". שנאמר - אני מתרגש מכך.
לבסוף - ה- API לדוגמא שאנו הולכים להשתמש בו הוא פרסום מִשׁתַמֵשׁ משאבים והוא חלק ממחקר המקרה המתמשך של אפליקציית Reddit כדי להדגים כיצד זה עובד - אך כמובן, כל ממשק API יעשה.
2. שכבת השירות
נתחיל בפשטות - פשוט על ידי זיהוי פעולות הקריאה והכתיבה בשירות המשתמשים הקודם שלנו - ונחלק את זה לשני שירותים נפרדים - UserQueryService ו UserCommandService:
ממשק ציבורי IUserQueryService {רשימה getUsersList (דף int, גודל int, sort מחרוזת, מחרוזת מיון); מחרוזת checkPasswordResetToken (userId ארוך, אסימון מחרוזת); מחרוזת checkConfirmRegistrationToken (אסימון מחרוזת); countAllUsers ארוך (); }
ממשק ציבורי IUserCommandService {void registerNewUser (שם משתמש מחרוזת, דוא"ל מחרוזת, סיסמת מחרוזת, appUrl מחרוזת); upid updateUserPassword (משתמש משתמש, סיסמת מחרוזת, מחרוזת oldPassword); בטל שינוי UserPassword (משתמש משתמש, סיסמת מחרוזת); בטל resetPassword (מייל מחרוזת, מחרוזת appUrl); בטל createVerificationTokenForUser (משתמש משתמש, אסימון מחרוזת); void updateUser (משתמש משתמש); }
מקריאת ה- API הזה תוכלו לראות בבירור כיצד שירות השאילתות מבצע את כל הקריאה וה- שירות הפקודה אינו קורא נתונים - כל הריק מחזיר.
3. שכבת הבקר
הבא - שכבת הבקר.
3.1. בקר השאילתות
הנה שלנו UserQueryRestController:
@Controller @RequestMapping (value = "/ api / users") מחלקה ציבורית UserQueryRestController {@Autowired פרטי IUserQueryService userService; @ ISueduledPostQueryService פרטי פרטית אוטומטית @ planningPostService; @Autowired פרטי ModelMapper modelMapper; @PreAuthorize ("hasRole ('USER_READ_PRIVILEGE')") @RequestMapping (method = RequestMethod.GET) @ResponseBody Public List getUsersList (...) {PagingInfo pagingInfo = PagingInfo חדש (דף, גודל, userService.countAllUsers (); response.addHeader ("PAGING_INFO", pagingInfo.toString ()); רשימת משתמשים = userService.getUsersList (עמוד, גודל, sortDir, מיון); להחזיר את המשתמשים.זרם () .מפה (משתמש -> convertUserEntityToDto (משתמש)). אסוף (Collectors.toList ()); } UserQueryDto פרטי להמיר UserUntityToDto (משתמש משתמש) {UserQueryDto dto = modelMapper.map (משתמש, UserQueryDto.class); dto.setScheduledPostsCount (schedulPostService.countScheduledPostsByUser (משתמש)); להחזיר dto; }}
מה שמעניין כאן הוא שבקר השאילתות מזריק רק שירותי שאילתות.
מה שיהיה מעניין עוד יותר הוא לעשות זאת לנתק את הגישה של בקר זה לשירותי הפיקוד - על ידי הצבתם במודול נפרד.
3.2. בקר הפיקוד
עכשיו, הנה הטמעת בקר הפקודה שלנו:
@Controller @RequestMapping (value = "/ api / משתמשים") מחלקה ציבורית UserCommandRestController {@Autowired פרטי IUserCommandService userService; @Autowired פרטי ModelMapper modelMapper; @RequestMapping (value = "/ registration", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) רישום חלל ציבורי (HttpServletRequest בקשה, @RequestBody UserRegisterCommandDto userDto) {מחרוזת appUrl = request.getRequestURL (). (request.getRequestURI (), ""); userService.registerNewUser (userDto.getUsername (), userDto.getEmail (), userDto.getPassword (), appUrl); } @PreAuthorize ("isAuthenticated ()") @RequestMapping (value = "/ password", method = RequestMethod.PUT) @ResponseStatus (HttpStatus.OK) update public voidUserPassword (@RequestBody UserUpdatePasswordCommandDto userDto) {userServerPass , userDto.getPassword (), userDto.getOldPassword ()); } @RequestMapping (value = "/ passwordReset", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) בטל ציבורי createAResetPassword (HttpServletRequest בקשה, @RequestBody UserTriggerResetPasswordCommandDto userDto) {בקשה .String.). החלף (request.getRequestURI (), ""); userService.resetPassword (userDto.getEmail (), appUrl); } @RequestMapping (value = "/ password", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) שינוי ריק ציבורי UserPassword (@RequestBody UserchangePasswordCommandDto userDto) {userService.changeUserPassword (getCurrentUser (), userDto); } @PreAuthorize ("hasRole ('USER_WRITE_PRIVILEGE')") @RequestMapping (value = "/ {id}", method = RequestMethod.PUT) @ResponseStatus (HttpStatus.OK) עדכון משתמש ציבורי ריק (משתמש @ @ RequestBody UserUpdateCommandDto משתמש. updateUser (convertToEntity (userDto)); } משתמש פרטי convertToEntity (UserUpdateCommandDto userDto) {return modelMapper.map (userDto, User.class); }}
כמה דברים מעניינים קורים כאן. ראשית - שימו לב כיצד כל אחת מיישומי ה- API הללו משתמשת בפקודה אחרת. זה בעיקר כדי לתת לנו בסיס טוב לשיפור נוסף בתכנון ה- API ולמיצוי משאבים שונים ככל שיופיעו.
סיבה נוספת היא שכאשר אנו מבצעים את הצעד הבא, לעבר מקורות אירועים - יש לנו סט פקודות נקי שאיתו אנו עובדים.
3.3. נציגויות משאבים נפרדות
בואו נעבור במהירות על הייצוגים השונים של משאב המשתמש שלנו, לאחר הפרדה זו לפקודות ולשאילתות:
מחלקה ציבורית UserQueryDto {פרטי מזהה ארוך; שם משתמש פרטי מחרוזת; מופעל בוליאני פרטי; תפקידי סט פרטיים; פרטית מתוזמנת פרטית ארוכה; }
להלן הוראות ה- DTO שלנו:
- UserRegisterCommandDto משמש לייצוג נתוני רישום משתמשים:
מחלקה ציבורית UserRegisterCommandDto {שם משתמש פרטי מחרוזת; דוא"ל מחרוזת פרטי; סיסמת מחרוזת פרטית; }
- UserUpdatePasswordCommandDto משמש לייצוג נתונים לעדכון סיסמת המשתמש הנוכחית:
מחלקה ציבורית UserUpdatePasswordCommandDto {private String oldPassword; סיסמת מחרוזת פרטית; }
- UserTriggerResetPasswordCommandDto משמש לייצוג דוא"ל המשתמש להפעלת סיסמת איפוס על ידי שליחת דוא"ל עם אסימון סיסמה לאפס:
מחלקה ציבורית UserTriggerResetPasswordCommandDto {דוא"ל מחרוזת פרטי; }
- UserChangePasswordCommandDto משמש לייצוג סיסמת משתמש חדשה - פקודה זו נקראת לאחר אסימון איפוס הסיסמה של המשתמש.
מחלקה ציבורית UserChangePasswordCommandDto {סיסמת מחרוזת פרטית; }
- UserUpdateCommandDto משמש לייצוג נתוני המשתמש החדש לאחר שינויים:
מחלקה ציבורית UserUpdateCommandDto {מזהה פרטי ארוך; מופעל בוליאני פרטי; תפקידי סט פרטיים; }
4. מסקנה
במדריך זה, הנחנו את היסודות לקראת יישום CQRS נקי עבור ממשק REST API של Spring.
השלב הבא יהיה להמשיך ולשפר את ה- API על ידי זיהוי של כמה תחומי אחריות (ומשאבים) נפרדים לשירותים שלהם, כך שניישר קו יותר עם ארכיטקטורה ממוקדת משאבים.
REST תחתון