2
\$\begingroup\$

I have been around in Spring Boot eco-system since 2012, I have worked on many little projects. The most important thing in development I experienced id Validation especially JPA Entities. We simply do validation like:

@Entity
public class Input {

  @Id
  @GeneratedValue
  private Long id;

  @Min(1)
  @Max(10)
  private int numberBetweenOneAndTen;

  @Pattern(regexp = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$")
  private String ipAddress;
  
  // ...
  
}

as mentioned here, but there is a note highlighted which says :

We usually don't want to do validation as late as in the persistence layer because it means that the business code above has worked with potentially invalid objects which may lead to unforeseen errors...

What is the safe and suitable way of Validating JPA Entities?

I have tried in one of my projects as:

1) Entity class

@Data
@Entity(name = "users")
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class AppUser extends AuditModel{

    private String userFirstName;

    private String userLastName;

    private String userDateOfBirth;

}

2) Dto class

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public class AppUserDto {

    private Long id;

    @NotNull(message = "First name must not be null")
    @NotBlank(message = "First name must not be blank")
    private String userFirstName;

    @NotNull(message = "Last name must not be null")
    @NotBlank(message = "Last name must not be blank")
    private String userLastName;

    @NotNull(message = "Date of Birth must not be null")
    @NotBlank(message = "Date of Birth must not be blank")
    @Past()
    private String userDateOfBirth;
}

3) Finally Validating Bean to a Spring Service Method

@Service
@Validated
public class AppUserService {

    private final AppUserRepository appUserRepository;

    @Autowired
    public AppUserService(AppUserRepository appUserRepository) {
        this.appUserRepository = appUserRepository;
    }

    public void update(@Valid AppUserDto appUserDto){
        appUserRepository.update(
                appUserDto.getId(),
                appUserDto.getUserFirstName(),
                appUserDto.getUserLastName(),
                appUserDto.getUserDateOfBirth()
        );
    }

}

Is this approach suitable or there is an other better way than that?

Please give your feed back your reviews are precious to me!

\$\endgroup\$
2
\$\begingroup\$

The approach is ok, although it depends on the context. If the application receives AppUserDto in the controller layer, doing the validation in the service layer might be too late. Moreover, the service layer will be dependent on the DTO.

If AppUserDto is received in the controller layer (e.g. from a REST API) a good approach is validating the request in the controller layer, mapping the DTO to the entity, and passing it down to the service layer:

@RestController
public class UserController {
    //...

    @PutMapping(value = "/user")
    public void updateUser(@RequestBody @Valid AppUserDto appUserDto) {
        AppUser appUser = toEntity(appUserDto);
        appUserService.update(appUser);
    }

    //...
}

After the validation with @Valid, if there are no errors, toEntity will map the DTO to the entity. The mapping can be done manually or using ModelMapper. More info on this approach here.

In general, DTO validation should be done as soon as possible to prevent internal components to work with invalid objects (as mentioned in the article you linked). Additionally, mapping the DTO to the entity keeps the service layer independent of the particular request so that it can be better reused and tested.

\$\endgroup\$
1
  • \$\begingroup\$ Isn't the mapping a verbose or redundant, or its okay this is good to go! \$\endgroup\$ – Arshad Ali Dec 18 '20 at 9:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.