Primijenite CQRS na Spring REST API

OSTALO Vrh

Upravo sam najavio novo Uči proljeće tečaj, usredotočen na osnove Spring 5 i Spring Boot 2:

>> PROVJERITE TEČAJ

1. Pregled

U ovom brzom članku učinit ćemo nešto novo. Razvit ćemo postojeći REST Spring API i koristiti ga za razdvajanje naredbenih upita - CQRS.

Cilj je jasno razdvojite i servisni i upravljački sloj za rješavanje čitanja - upita i pisanja - naredbi koje zasebno dolaze u sustav.

Imajte na umu da je ovo samo prvi rani korak prema takvoj vrsti arhitekture, a ne „točka dolaska“. Kad se tako kažem - uzbuđen sam zbog ovog.

Konačno - primjer API-ja koji ćemo koristiti je objavljivanje Korisnik resursa i dio je naše studije slučaja aplikacije Reddit koja ilustrira kako to funkcionira - ali naravno, svaki API će to učiniti.

2. Razina usluge

Počet ćemo jednostavno - samo identificiranjem operacija čitanja i pisanja u našoj prethodnoj Korisničkoj usluzi - i podijelit ćemo to u 2 zasebne usluge - UserQueryService i UserCommandService:

javno sučelje IUserQueryService {Popis getUsersList (int stranica, int veličina, String sortDir, String sort); String checkPasswordResetToken (dugi userId, žetonski token); String checkConfirmRegistrationToken (token niza); long countAllUsers (); }
javno sučelje IUserCommandService {void registerNewUser (korisničko ime niza, niz e-pošte, lozinka niza, niz appUrl); void updateUserPassword (Korisnički korisnik, Lozinka niza, Niz oldPassword); void changeUserPassword (Korisnički korisnik, Lozinka niza); void resetPassword (String email, String appUrl); void createVerificationTokenForUser (Korisnički korisnik, žetonski token); void updateUser (Korisnički korisnik); }

Iz čitanja ovog API-ja možete jasno vidjeti kako usluga upita vrši sve čitanje i naredbena usluga ne čita nikakve podatke - sve praznine se vraćaju.

3. Sloj kontrolera

Sljedeće - sloj kontrolera.

3.1. Upravitelj upita

Ovdje je naš UserQueryRestController:

@Controller @RequestMapping (value = "/ api / users") javna klasa UserQueryRestController {@Autowired private IUserQueryService userService; @Autowired private IScheduledPostQueryService rasporedPostService; @Autowired privatni ModelMapper modelMapper; @PreAuthorize ("hasRole ('USER_READ_PRIVILEGE')") @RequestMapping (method = RequestMethod.GET) @ResponseBody javni popis getUsersList (...) {PagingInfo pagingInfo = novo PagingInfo (stranica, veličina, userService.countAllUsers ()); response.addHeader ("PAGING_INFO", pagingInfo.toString ()); Popis korisnika = userService.getUsersList (stranica, veličina, sortDir, sortiranje); vratiti users.stream (). map (user -> convertUserEntityToDto (user)). collect (Collectors.toList ()); } private UserQueryDto convertUserEntityToDto (Korisnički korisnik) {UserQueryDto dto = modelMapper.map (korisnik, UserQueryDto.class); dto.setScheduledPostsCount (rasporedPostService.countScheduledPostsByUser (korisnik)); vratiti dto; }}

Ono što je ovdje zanimljivo jest da kontroler upita samo ubacuje usluge upita.

Ono što bi bilo još zanimljivije jest da prekinuti pristup ovog kontrolera zapovjednim uslugama - njihovim smještanjem u zasebni modul.

3.2. Upravljač naredbama

Eto, evo implementacije našeg naredbe kontrolera:

@Controller @RequestMapping (value = "/ api / users") javna klasa UserCommandRestController {@Autowired private IUserCommandService userService; @Autowired privatni ModelMapper modelMapper; @RequestMapping (value = "/ registration", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) javni void registar (HttpServletRequest zahtjev, @RequestBody UserRegisterCommandDto userDto) {String appUrl = request.getRequestURL (). Replace. (request.getRequestURI (), ""); userService.registerNewUser (userDto.getUsername (), userDto.getEmail (), userDto.getPassword (), appUrl); } @PreAuthorize ("isAuthenticated ()") @RequestMapping (value = "/ password", method = RequestMethod.PUT) @ResponseStatus (HttpStatus.OK) javna void updateUserPassword (@RequestBody UserUpdatePasswordCommandDtoserDurserPress (User) , userDto.getPassword (), userDto.getOldPassword ()); } @RequestMapping (value = "/ passwordReset", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) javna praznina createAResetPassword (HttpServletRequest zahtjev, @RequestBody UserTriggerResetPasswordCommandDto userDto) {String appgetrrt. zamijeniti (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 (), userDassword.get); } @PreAuthorize ("hasRole ('USER_WRITE_PRIVILEGE')") @RequestMapping (value = "/ {id}", method = RequestMethod.PUT) @ResponseStatus (HttpStatus.OK) javna praznina updateUser (@RequestBody UserUpdateComtondDtoService UserSupportDoserDomateDtoService. updateUser (convertToEntity (userDto)); } privatni korisnik convertToEntity (UserUpdateCommandDto userDto) {return modelMapper.map (userDto, User.class); }}

Ovdje se događa nekoliko zanimljivih stvari. Prvo - primijetite kako svaka od ovih implementacija API-ja koristi drugu naredbu. To nam je uglavnom radi dobre osnove za daljnje poboljšanje dizajna API-ja i izdvajanje različitih resursa čim se pojave.

Drugi je razlog taj što kada poduzmemo sljedeći korak, prema Izvoru događaja - imamo čisti skup naredbi s kojima radimo.

3.3. Zasebna predstavljanja resursa

Krenimo sada brzo pregledati različite prikaze našeg korisničkog resursa, nakon ovog razdvajanja na naredbe i upite:

javna klasa UserQueryDto {private Long id; privatno korisničko ime niza; omogućeno privatno logičko polje; privatni skup uloga; privatno dugo zakazanoPostsCount; }

Evo naših naredbenih DTO-a:

  • UserRegisterCommandDto koristi se za predstavljanje podataka o registraciji korisnika:
javna klasa UserRegisterCommandDto {korisničko ime privatnog niza; privatni String e-mail; privatna lozinka za niz; }
  • UserUpdatePasswordCommandDto koristi se za predstavljanje podataka za ažuriranje trenutne korisničke lozinke:
javna klasa UserUpdatePasswordCommandDto {privatni niz oldPassword; privatna lozinka za niz; }
  • UserTriggerResetPasswordCommandDto koristi se za predstavljanje korisničke e-pošte za pokretanje resetiranja lozinke slanjem e-pošte s tokenom resetiranja lozinke:
javna klasa UserTriggerResetPasswordCommandDto {privatni niz e-pošte; }
  • UserChangePasswordCommandDto koristi se za predstavljanje nove korisničke lozinke - ova se naredba poziva nakon tokena za ponovno postavljanje lozinke korisnika.
javna klasa UserChangePasswordCommandDto {lozinka privatnog niza; }
  • UserUpdateCommandDto koristi se za predstavljanje podataka novog korisnika nakon izmjena:
javna klasa UserUpdateCommandDto {private Long id; omogućeno privatno logičko polje; privatni skup uloga; }

4. Zaključak

U ovom uputstvu postavili smo temelje za čistu implementaciju CQRS-a za Spring REST API.

Sljedeći će korak biti stalno unapređivanje API-ja utvrđivanjem nekih zasebnih odgovornosti (i resursa) u njihovim vlastitim uslugama kako bismo se bliže uskladili s arhitekturom usmjerenom na resurse.

OSTALO dno

Upravo sam najavio novo Uči proljeće tečaj, usredotočen na osnove Spring 5 i Spring Boot 2:

>> PROVJERITE TEČAJ