refactor: reduce PetController complexity by extracting validation logic

Signed-off-by: Mickael-7 <mickael.albuquerque@upe.br>
This commit is contained in:
Mickael-7 2025-12-07 18:00:21 -03:00
parent 3e1ce239f4
commit 9b820120ce
3 changed files with 133 additions and 26 deletions

View file

@ -15,15 +15,12 @@
*/
package org.springframework.samples.petclinic.owner;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
@ -53,9 +50,12 @@ class PetController {
private final PetTypeRepository types;
public PetController(OwnerRepository owners, PetTypeRepository types) {
private final PetValidationService petValidationService;
public PetController(OwnerRepository owners, PetTypeRepository types, PetValidationService petValidationService) {
this.owners = owners;
this.types = types;
this.petValidationService = petValidationService;
}
@ModelAttribute("types")
@ -106,13 +106,7 @@ class PetController {
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result,
RedirectAttributes redirectAttributes) {
if (StringUtils.hasText(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null)
result.rejectValue("name", "duplicate", "already exists");
LocalDate currentDate = LocalDate.now();
if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) {
result.rejectValue("birthDate", "typeMismatch.birthDate");
}
this.petValidationService.validateForCreation(pet, owner, result);
if (result.hasErrors()) {
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
@ -133,20 +127,8 @@ class PetController {
public String processUpdateForm(Owner owner, @Valid Pet pet, BindingResult result,
RedirectAttributes redirectAttributes) {
String petName = pet.getName();
// checking if the pet name already exists for the owner
if (StringUtils.hasText(petName)) {
Pet existingPet = owner.getPet(petName, false);
if (existingPet != null && !Objects.equals(existingPet.getId(), pet.getId())) {
result.rejectValue("name", "duplicate", "already exists");
}
}
LocalDate currentDate = LocalDate.now();
if (pet.getBirthDate() != null && pet.getBirthDate().isAfter(currentDate)) {
result.rejectValue("birthDate", "typeMismatch.birthDate");
}
// Delegated validation to service - CCN reduced from 7 to 3
this.petValidationService.validateForUpdate(pet, owner, result);
if (result.hasErrors()) {
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;

View file

@ -0,0 +1,124 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.owner;
import java.time.LocalDate;
import java.util.Objects;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
/**
* Service responsible for Pet validation logic. Extracts complex validation rules from
* the controller layer. Follows Single Responsibility Principle (SRP) from SOLID.
*
* @author Nathan Dalbert
* @author Paulo Henrique
* @author Mickael de Albuquerque
* @author Igor Rego
*/
@Service
public class PetValidationService {
/**
* Validates pet name for duplicates during creation.
* @param pet the pet being created
* @param owner the owner of the pet
* @param errors validation errors container
* @return true if validation passes, false otherwise
*/
public boolean validatePetNameForCreation(Pet pet, Owner owner, Errors errors) {
if (!StringUtils.hasText(pet.getName())) {
return true; // Let @Valid handle blank names
}
if (pet.isNew() && owner.getPet(pet.getName(), true) != null) {
errors.rejectValue("name", "duplicate", "already exists");
return false;
}
return true;
}
/**
* Validates pet name for duplicates during update.
* @param pet the pet being updated
* @param owner the owner of the pet
* @param errors validation errors container
* @return true if validation passes, false otherwise
*/
public boolean validatePetNameForUpdate(Pet pet, Owner owner, Errors errors) {
if (!StringUtils.hasText(pet.getName())) {
return true; // Let @Valid handle blank names
}
Pet existingPet = owner.getPet(pet.getName(), false);
if (existingPet != null && !Objects.equals(existingPet.getId(), pet.getId())) {
errors.rejectValue("name", "duplicate", "already exists");
return false;
}
return true;
}
/**
* Validates that birth date is not in the future.
* @param pet the pet being validated
* @param errors validation errors container
* @return true if validation passes, false otherwise
*/
public boolean validateBirthDate(Pet pet, Errors errors) {
if (pet.getBirthDate() == null) {
return true;
}
LocalDate currentDate = LocalDate.now();
if (pet.getBirthDate().isAfter(currentDate)) {
errors.rejectValue("birthDate", "typeMismatch.birthDate");
return false;
}
return true;
}
/**
* Performs all validations for pet creation.
* @param pet the pet being created
* @param owner the owner of the pet
* @param errors validation errors container
* @return true if all validations pass, false otherwise
*/
public boolean validateForCreation(Pet pet, Owner owner, Errors errors) {
boolean isNameValid = validatePetNameForCreation(pet, owner, errors);
boolean isBirthDateValid = validateBirthDate(pet, errors);
return isNameValid && isBirthDateValid;
}
/**
* Performs all validations for pet update.
* @param pet the pet being updated
* @param owner the owner of the pet
* @param errors validation errors container
* @return true if all validations pass, false otherwise
*/
public boolean validateForUpdate(Pet pet, Owner owner, Errors errors) {
boolean isNameValid = validatePetNameForUpdate(pet, owner, errors);
boolean isBirthDateValid = validateBirthDate(pet, errors);
return isNameValid && isBirthDateValid;
}
}

View file

@ -46,7 +46,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Wick Dynex
*/
@WebMvcTest(value = PetController.class,
includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE))
includeFilters = { @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE),
@ComponentScan.Filter(value = PetValidationService.class, type = FilterType.ASSIGNABLE_TYPE) })
@DisabledInNativeImage
@DisabledInAotMode
class PetControllerTests {