Anvend CQRS på en Spring REST API

REST Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

1. Oversigt

I denne hurtige artikel vil vi gøre noget nyt. Vi udvikler en eksisterende REST Spring API og får den til at bruge Command Query Responsibility Segregation - CQRS.

Målet er at adskiller tydeligt både service- og controller-lagene til at håndtere læser - forespørgsler og skriv - kommandoer, der kommer ind i systemet separat.

Husk, at dette kun er et tidligt første skridt mod denne form for arkitektur, ikke "et ankomstpunkt". Når det er sagt - jeg er begejstret for denne.

Endelig - eksemplet API vi vil bruge er udgivelse Bruger ressourcer og er en del af vores igangværende case-undersøgelse af Reddit-appen for at eksemplificere, hvordan dette fungerer - men selvfølgelig vil enhver API gøre.

2. Servicelaget

Vi starter simpelt - ved blot at identificere læse- og skriveoperationerne i vores tidligere brugertjeneste - og vi deler det i 2 separate tjenester - UserQueryService og UserCommandService:

offentlig grænseflade IUserQueryService {Liste getUsersList (int side, int størrelse, streng sortDir, streng sortering); Streng checkPasswordResetToken (lang userId, streng token); Streng checkConfirmRegistrationToken (streng token); lang countAllUsers (); }
offentlig grænseflade IUserCommandService {void registerNewUser (String brugernavn, String email, String password, String appUrl); ugyldig opdateringUserPassword (brugerbruger, strengadgangskode, streng oldPassword); ugyldig ændringUserPassword (brugerbruger, strengadgangskode); ugyldig resetPassword (streng e-mail, streng appUrl); ugyldigt createVerificationTokenForUser (brugerbruger, streng-token); ugyldig opdateringUser (brugerbruger); }

Fra at læse denne API kan du tydeligt se, hvordan forespørgselstjenesten gør al læsning og kommandotjenesten læser ikke data - alt ugyldigt returneres.

3. Controller-laget

Næste op - controller-laget.

3.1. Forespørgselscontrolleren

Her er vores UserQueryRestController:

@Controller @RequestMapping (værdi = "/ api / brugere") offentlig klasse UserQueryRestController {@Autowired private IUserQueryService userService; @Autowired privat IScheduledPostQueryService planningPostService; @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 brugere = userService.getUsersList (side, størrelse, sortDir, sorter); returnere brugere.stream (). kort (bruger -> convertUserEntityToDto (bruger)). indsamle (Collectors.toList ()); } privat UserQueryDto convertUserEntityToDto (brugerbruger) {UserQueryDto dto = modelMapper.map (bruger, UserQueryDto.class); dto.setScheduledPostsCount (planningPostService.countScheduledPostsByUser (bruger)); returnere dto; }}

Hvad der er interessant her er, at forespørgselscontrolleren kun injicerer forespørgselstjenester.

Hvad der ville være endnu mere interessant er at afskære adgangen til denne controller til kommandotjenesterne - ved at placere disse i et separat modul.

3.2. Kommandocontrolleren

Her er vores implementering af kommandocontroller:

@Controller @RequestMapping (værdi = "/ api / brugere") offentlig klasse UserCommandRestController {@Autowired privat IUserCommandService userService; @Autowired privat ModelMapper modelMapper; @RequestMapping (værdi = "/ registrering", metode = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) offentligt ugyldigt register (HttpServletRequest anmodning, @RequestBody UserRegisterCommandDto brugerDto) {String appUrl = request.getRequestURL () .Stat. (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) {userService.server.server. , userDto.getPassword (), userDto.getOldPassword ()); } @RequestMapping (værdi = "/ passwordReset", metode = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) offentlig ugyldig createAResetPassword (HttpServletRequest anmodning, @RequestBody UserTriggerResetPasswordCommandDto userDto) {anmodning. String appUr. erstatte (request.getRequestURI (), ""); userService.resetPassword (userDto.getEmail (), appUrl); } @RequestMapping (værdi = "/ adgangskode", metode = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) offentlig ugyldig ændringUserPassword (@RequestBody UserchangePasswordCommandDto userDto) {userService.changeUserPassword (getCurrentUser (), userDto); } @PreAuthorize ("hasRole ('USER_WRITE_PRIVILEGE')") @RequestMapping (value = "/ {id}", method = RequestMethod.PUT) @ResponseStatus (HttpStatus.OK) offentlig ugyldig opdateringsbruger (@RequestBody UserUpdateCommandDto bruger. Bruger. Service). updateUser (convertToEntity (userDto)); } privat bruger convertToEntity (UserUpdateCommandDto userDto) {return modelMapper.map (userDto, User.class); }}

Et par interessante ting sker her. Først - bemærk hvordan hver af disse API-implementeringer bruger en anden kommando. Dette er primært for at give os et godt grundlag for yderligere at forbedre design af API og udvinde forskellige ressourcer, når de kommer frem.

En anden grund er, at når vi tager det næste skridt mod begivenhedssourcing - har vi et rent sæt kommandoer, som vi arbejder med.

3.3. Separate ressourcerepræsentationer

Lad os nu hurtigt gå over de forskellige repræsentationer af vores brugerressource efter denne adskillelse i kommandoer og forespørgsler:

offentlig klasse UserQueryDto {privat Lang id; privat streng brugernavn; privat boolsk aktiveret private sæt roller; privat lang planlagtPostsCount; }

Her er vores Command DTO'er:

  • UserRegisterCommandDto bruges til at repræsentere brugerregistreringsdata:
offentlig klasse UserRegisterCommandDto {privat streng brugernavn; privat streng e-mail; privat strengadgangskode; }
  • UserUpdatePasswordCommandDto bruges til at repræsentere data til opdatering af den aktuelle brugeradgangskode:
offentlig klasse UserUpdatePasswordCommandDto {private String oldPassword; privat strengadgangskode; }
  • UserTriggerResetPasswordCommandDto bruges til at repræsentere brugerens e-mail til at udløse nulstillingsadgangskode ved at sende en e-mail med nulstillet kodeordstoken:
offentlig klasse UserTriggerResetPasswordCommandDto {privat streng-e-mail; }
  • UserChangePasswordCommandDto bruges til at repræsentere ny brugeradgangskode - denne kommando kaldes efter adgangskode til nulstilling af brugeradgangskode.
offentlig klasse UserChangePasswordCommandDto {privat strengadgangskode; }
  • UserUpdateCommandDto bruges til at repræsentere nye brugers data efter ændringer:
offentlig klasse UserUpdateCommandDto {privat Lang id; privat boolsk aktiveret private sæt roller; }

4. Konklusion

I denne vejledning lagde vi grundlaget for en ren CQRS-implementering til en Spring REST API.

Det næste trin vil være at fortsætte med at forbedre API'en ved at identificere nogle separate ansvarsområder (og ressourcer) ud i deres egne tjenester, så vi tættere på en ressourcecentreret arkitektur.

REST bunden

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

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