החל CQRS על ממשק API REST של Spring

REST למעלה

רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 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 תחתון

רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:

>> בדוק את הקורס

$config[zx-auto] not found$config[zx-overlay] not found