Použite CQRS na Spring REST API

ODPOČINOK Najlepšie

Práve som oznámil nové Naučte sa jar kurz zameraný na základy jari 5 a Spring Boot 2:

>> SKONTROLUJTE KURZ

1. 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

Práve som oznámil nové Naučte sa jar kurz zameraný na základy jari 5 a Spring Boot 2:

>> SKONTROLUJTE KURZ

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