Použite CQRS na Spring REST API
Práve som oznámil nové Naučte sa jar kurz zameraný na základy jari 5 a Spring Boot 2:
>> SKONTROLUJTE KURZ1. Prehľad
V tomto rýchlom článku sa chystáme urobiť niečo nové. Chystáme sa vyvinúť existujúce API REST Spring a sprístupniť ho pomocou segregácie zodpovednosti za zodpovedanie príkazov - CQRS.
Cieľom je zreteľne oddeľte servisnú aj kontrolnú vrstvu zaoberať sa čítaním - dotazmi a zápismi - príkazmi prichádzajúcimi do systému osobitne.
Pamätajte, že je to iba prvý krok k tomuto druhu architektúry, nie k „cieľovému bodu“. To je povedané - z tejto som nadšený.
Nakoniec - príklad rozhrania API, ktoré budeme používať, je publikovanie Používateľ zdrojov a je súčasťou našej prebiehajúcej prípadovej štúdie aplikácie Reddit, aby sme ilustrovali, ako to funguje - ale samozrejme to bude stačiť každé API.
2. Servisná vrstva
Začneme jednoducho - iba identifikáciou operácií čítania a zápisu v našej predchádzajúcej službe pre používateľov - a rozdelíme to na 2 samostatné služby - UserQueryService a UserCommandService:
verejné rozhranie IUserQueryService {List getUsersList (int stránka, int veľkosť, String sortDir, String sort); String checkPasswordResetToken (long userId, String token); String checkConfirmRegistrationToken (reťazcový token); long countAllUsers (); }
verejné rozhranie IUserCommandService {void registerNewUser (meno používateľa reťazca, reťazec e-mail, heslo reťazca, reťazec appUrl); void updateUserPassword (užívateľský používateľ, heslo reťazca, reťazec oldPassword); void changeUserPassword (užívateľský užívateľ, heslo k reťazcu); void resetPassword (String email, String appUrl); void createVerificationTokenForUser (užívateľský užívateľ, reťazcový token); void updateUser (užívateľ užívateľa); }
Z čítania tohto API môžete jasne vidieť, ako dopytová služba robí všetky čítania a príkazová služba nečíta žiadne údaje - vráti sa všetko neplatné.
3. Vrstva radiča
Ďalej - vrstva radiča.
3.1. Kontrolór dopytov
Tu je náš UserQueryRestController:
@Controller @RequestMapping (value = "/ api / users") verejná trieda UserQueryRestController {@Autowired private IUserQueryService userService; @Autowired private IScheduledPostQueryService scheduledPostService; @Autowired private ModelMapper modelMapper; @PreAuthorize ("hasRole ('USER_READ_PRIVILEGE')") @RequestMapping (method = RequestMethod.GET) @ResponseBody verejný zoznam getUsersList (...) {PagingInfo pagingInfo = nový PagingInfo (stránka, veľkosť, userService.countAllUsers (); response.addHeader ("PAGING_INFO", pagingInfo.toString ()); Zoznam používateľov = userService.getUsersList (stránka, veľkosť, sortDir, triedenie); vrátiť users.stream (). map (user -> convertUserEntityToDto (user)). collect (Collectors.toList ()); } private UserQueryDto convertUserEntityToDto (užívateľský užívateľ) {UserQueryDto dto = modelMapper.map (užívateľ, UserQueryDto.class); dto.setScheduledPostsCount (scheduledPostService.countScheduledPostsByUser (užívateľ)); návrat dto; }}
Zaujímavé tu je, že radič dotazov vkladá iba dopytovacie služby.
Čo by bolo ešte zaujímavejšie, je prerušiť prístup tohto radiča k príkazovým službám - ich umiestnením do samostatného modulu.
3.2. Riadiaci veliteľ
Teraz je tu implementácia nášho príkazového radiča:
@Controller @RequestMapping (value = "/ api / users") verejná trieda UserCommandRestController {@Autowired private IUserCommandService userService; @Autowired private ModelMapper modelMapper; @RequestMapping (hodnota = "/ registrácia", metóda = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) verejný neplatný register (požiadavka HttpServletRequest, @RequestBody UserRegisterCommandDto userDto) {String appUrl = request.getRequestURL (). ToString (). (request.getRequestURI (), ""); userService.registerNewUser (userDto.getUsername (), userDto.getEmail (), userDto.getPassword (), appUrl); } @PreAuthorize ("isAuthenticated ()") @RequestMapping (hodnota = "/ heslo", metóda = RequestMethod.PUT) @ResponseStatus (HttpStatus.OK) public void updateUserPassword (@RequestBody UserUpdatePasswordCommandDto userDto) (userService.server) , userDto.getPassword (), userDto.getOldPassword ()); } @RequestMapping (value = "/ passwordReset", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) public void createAResetPassword (požiadavka HttpServletRequest, @RequestBody UserTriggerResetPasswordCommandDto userDto) = String appUrget.txt) replace (request.getRequestURI (), ""); userService.resetPassword (userDto.getEmail (), appUrl); } @RequestMapping (hodnota = "/ heslo", metóda = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) verejné void changeUserPassword (@RequestBody UserchangePasswordCommandDto userDto) {userService.changeUserPassword (getCurrentUser (), userDto.Postword) } @PreAuthorize ("hasRole ('USER_WRITE_PRIVILEGE')") @RequestMapping (value = "/ {id}", metóda = RequestMethod.PUT) @ResponseStatus (HttpStatus.OK) public void updateUser (@RequestBody UserUpdateCommandDto user) updateUser (convertToEntity (userDto)); } súkromný používateľ convertToEntity (UserUpdateCommandDto userDto) {návrat modelMapper.map (userDto, User.class); }}
Deje sa tu niekoľko zaujímavých vecí. Najprv - všimnite si, ako každá z týchto implementácií API používa iný príkaz. To nám dáva hlavne dobrý základ pre ďalšie zlepšovanie dizajnu API a získavanie rôznych zdrojov, keď sa objavia.
Ďalším dôvodom je, že keď urobíme ďalší krok, smerom k Event Sourcingu - máme čistú sadu príkazov, s ktorými pracujeme.
3.3. Samostatné zastúpenie zdrojov
Po tomto rozdelení na príkazy a dotazy si teraz poďme rýchlo prejsť rôzne reprezentácie nášho prostriedku používateľa:
public class UserQueryDto {private Long id; súkromné reťazcové používateľské meno; povolené súkromné boolean; roly súkromnej množiny; private long scheduledPostsCount; }
Tu sú naše príkazové DTO:
- UserRegisterCommandDto slúži na predstavenie registračných údajov používateľa:
verejná trieda UserRegisterCommandDto {súkromné reťazcové používateľské meno; súkromný reťazcový e-mail; súkromné reťazcové heslo; }
- UserUpdatePasswordCommandDto slúži na reprezentáciu údajov na aktualizáciu aktuálneho hesla používateľa:
verejná trieda UserUpdatePasswordCommandDto {private String oldPassword; súkromné reťazcové heslo; }
- UserTriggerResetPasswordCommandDto slúži na predstavenie e-mailu používateľa na spustenie resetovacieho hesla zaslaním e-mailu s tokenom resetovacieho hesla:
verejná trieda UserTriggerResetPasswordCommandDto {súkromný e-mail s reťazcom; }
- UserChangePasswordCommandDto slúži na predstavenie nového hesla používateľa - tento príkaz sa volá po tom, ako používateľ použije token na obnovenie hesla.
verejná trieda UserChangePasswordCommandDto {súkromné reťazcové heslo; }
- UserUpdateCommandDto slúži na reprezentáciu údajov nového používateľa po úpravách:
public class UserUpdateCommandDto {private Long id; povolené súkromné boolean; roly súkromnej množiny; }
4. Záver
V tomto tutoriáli sme položili základy čistej implementácie CQRS pre Spring REST API.
Ďalším krokom bude neustále zdokonaľovanie API tým, že sa určia niektoré samostatné zodpovednosti (a zdroje) v ich vlastných službách, aby sme sa užšie zosúladili s architektúrou zameranou na zdroje.
REST spodok