Bruk CQRS på en Spring REST API

REST Topp

Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:

>> KONTROLLER KURSET

1. Oversikt

I denne raske artikkelen skal vi gjøre noe nytt. Vi skal utvikle en eksisterende REST Spring API og få den til å bruke Command Query Responsibility Segregation - CQRS.

Målet er å skiller tydelig både tjeneste- og kontrollerlagene å håndtere Reads - Queries and Writes - Kommandoer som kommer inn i systemet separat.

Husk at dette bare er et tidlig første skritt mot denne typen arkitektur, ikke "et ankomstpunkt". Når det er sagt - jeg er spent på denne.

Endelig - eksempelet API vi skal bruke er publisering Bruker ressurser og er en del av vår pågående Reddit app case study for å eksemplifisere hvordan dette fungerer - men selvfølgelig vil enhver API gjøre.

2. Servicelaget

Vi begynner enkelt - ved bare å identifisere lese- og skriveoperasjonene i vår forrige brukertjeneste - og vi vil dele det opp i to separate tjenester - UserQueryService og UserCommandService:

offentlig grensesnitt IUserQueryService {List getUsersList (int side, int size, String sortDir, String sort); Streng checkPasswordResetToken (lang userId, streng token); Streng checkConfirmRegistrationToken (streng token); lang countAllUsers (); }
offentlig grensesnitt IUserCommandService {void registerNewUser (streng brukernavn, streng e-post, streng passord, streng appUrl); ugyldig oppdateringUserPassword (brukerbruker, strengpassord, streng gammelt passord); ugyldig endringUserPassword (brukerbruker, strengpassord); ugyldig resetPassword (streng e-post, streng appUrl); ugyldig createVerificationTokenForUser (brukerbruker, strengtegn); ugyldig oppdatering Bruker (brukerbruker); }

Fra å lese denne API-en kan du tydelig se hvordan spørretjenesten gjør all lesing og kommandotjenesten leser ingen data - alt ugyldig returnerer.

3. Kontrollerlaget

Neste opp - kontrollerlaget.

3.1. Spørringskontrolleren

Her er vår UserQueryRestController:

@Controller @RequestMapping (verdi = "/ api / brukere") offentlig klasse UserQueryRestController {@Autowired private IUserQueryService userService; @Autowired private IScheduledPostQueryService scheduledPostService; @Autowired privat ModelMapper modelMapper; @PreAuthorize ("hasRole ('USER_READ_PRIVILEGE')") @RequestMapping (method = RequestMethod.GET) @ResponseBody public List getUsersList (...) {PagingInfo pagingInfo = new PagingInfo (side, størrelse, userService.countAllUsers (); response.addHeader ("PAGING_INFO", pagingInfo.toString ()); Liste brukere = userService.getUsersList (side, størrelse, sortDir, sorter); returnere brukere.stream (). kart (bruker -> convertUserEntityToDto (bruker)). samle (Collectors.toList ()); } privat UserQueryDto convertUserEntityToDto (brukerbruker) {UserQueryDto dto = modelMapper.map (bruker, UserQueryDto.class); dto.setScheduledPostsCount (schedulertPostService.countScheduledPostsByUser (bruker)); returnere dto; }}

Det som er interessant her er at spørrekontrolleren bare injiserer spørringstjenester.

Det som ville være enda mer interessant er å kutte tilgangen til denne kontrolleren til kommandotjenestene - ved å plassere disse i en egen modul.

3.2. Kommandokontrolleren

Her er implementeringen av kommandokontrolleren vår:

@Controller @RequestMapping (verdi = "/ api / brukere") offentlig klasse UserCommandRestController {@Autowired private IUserCommandService userService; @Autowired privat ModelMapper modelMapper; @RequestMapping (value = "/ registration", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) public void register (HttpServletRequest request, @RequestBody UserRegisterCommandDto userDto) {String appUrl = request.getRequestURL (). ToSt. (request.getRequestURI (), ""); userService.registerNewUser (userDto.getUsername (), userDto.getEmail (), userDto.getPassword (), appUrl); } @PreAuthorize ("isAuthenticated ()") @RequestMapping (value = "/ password", method = RequestMethod.PUT) @ResponseStatus (HttpStatus.OK) public void updateUserPassword (@RequestBody UserUpdatePasswordCommandDto userDto) {userServer.server.server.server) , userDto.getPassword (), userDto.getOldPassword ()); } @RequestMapping (value = "/ passwordReset", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) public void createAResetPassword (HttpServletRequest request, @RequestBody UserTriggerResetPasswordCommandDto userDto) {String appUr. erstatt (request.getRequestURI (), ""); userService.resetPassword (userDto.getEmail (), appUrl); } @RequestMapping (verdi = "/ passord", metode = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) offentlig ugyldig endreUserPassword (@RequestBody UserchangePasswordCommandDto userDto) {userService.changeUserPassword (getCurrentUser (), userDto); } @PreAuthorize ("hasRole ('USER_WRITE_PRIVILEGE')") @RequestMapping (verdi = "/ {id}", metode = RequestMethod.PUT) @ResponseStatus (HttpStatus.OK) offentlig ugyldig oppdateringsbruker (@RequestBody UserUpdateCommandDto bruker. Bruker. updateUser (convertToEntity (userDto)); } privat bruker convertToEntity (UserUpdateCommandDto userDto) {return modelMapper.map (userDto, User.class); }}

Noen interessante ting skjer her. Først - legg merke til hvordan hver av disse API-implementeringene bruker en annen kommando. Dette er hovedsakelig for å gi oss et godt grunnlag for å ytterligere forbedre utformingen av API og trekke ut forskjellige ressurser når de dukker opp.

En annen grunn er at når vi tar neste skritt mot Event Sourcing, har vi et rent sett med kommandoer som vi jobber med.

3.3. Separate ressursrepresentasjoner

La oss nå raskt gå gjennom de forskjellige representasjonene av brukerressursen vår, etter denne separasjonen i kommandoer og spørsmål:

offentlig klasse UserQueryDto {private Lang id; privat streng brukernavn; privat boolsk aktivert; private Sett roller; privat lang planlagtPostsCount; }

Her er våre Command DTO-er:

  • UserRegisterCommandDto brukes til å representere brukerregistreringsdata:
offentlig klasse UserRegisterCommandDto {private String brukernavn; privat streng e-post; privat strengpassord; }
  • UserUpdatePasswordCommandDto brukes til å representere data for å oppdatere nåværende brukerpassord:
offentlig klasse UserUpdatePasswordCommandDto {private String oldPassword; privat strengpassord; }
  • UserTriggerResetPasswordCommandDto brukes til å representere brukerens e-post for å utløse tilbakestillingspassord ved å sende en e-post med tilbakestill passord token:
offentlig klasse UserTriggerResetPasswordCommandDto {privat streng e-post; }
  • UserChangePasswordCommandDto brukes til å representere nytt brukerpassord - denne kommandoen kalles etter at brukerbrukerens passord-reset-token er blitt brukt.
offentlig klasse UserChangePasswordCommandDto {private String password; }
  • UserUpdateCommandDto brukes til å representere nye brukeres data etter endringer:
offentlig klasse UserUpdateCommandDto {private Lang id; privat boolsk aktivert; private Sett roller; }

4. Konklusjon

I denne veiledningen la vi grunnlaget for en ren CQRS-implementering for en Spring REST API.

Det neste trinnet vil være å fortsette å forbedre API-et ved å identifisere noen separate ansvarsområder (og ressurser) i sine egne tjenester, slik at vi kommer nærmere inn på en ressurssentrert arkitektur.

HVILLE bunnen

Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:

>> KONTROLLER KURSET

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