I'm building a validation package for spring, with...
# spring
d
I'm building a validation package for spring, with Kotlin. I'm pretty much done but I have to decide between doing the validation like this within the function:
Copy code
suspend fun register(
        @RequestBody request: RegisterRequest,
        exchange: ServerWebExchange
    ): ResponseEntity<DataResponse<Map<String, String>>> {
        registerRequestValidator.validate(request, exchange)
or with an annotation
Copy code
@ValidateWith(RegisterPayloadValidator::class)
    suspend fun register(
        @RequestBody request: RegisterRequest,
        exchange: ServerWebExchange
    ): ResponseEntity<DataResponse<Map<String, String>>> {
The annotation is simpler but then the end users will have to add these 2 librairies to their spring app
Copy code
// AspectJ dependencies for ValidationAspect
	implementation("org.springframework:spring-aop")
	implementation("org.aspectj:aspectjweaver:1.9.22")
Anyone know these dependencies ? would it be a turn off ? There are also other options (to make the validation happen before entering the method) but I like the simplicity of the annotation.
@ValidateWith(RegisterPayloadValidator::class)
a
I like the simplicity of the annotation. Adding dependencies is not an obstacle
r
Why don't you use Jakarta Validation instead? I mean, if it's a learning project, it's ok, but there are already production-grade solutions out there
d
Jakarta Validation is the built in package ? It's blocking. It doesn't support suspend functions. With my package I can do:
Copy code
@Component
class UniqueEmailRule(
    private val userService: UserService,
    private val messageSource: MessageSource
) : ValidationRule {
    override suspend fun validate(value: Any?, locale: Locale): String? { 
        if (value == null || value !is String) return null // Single Responsibility: Each rule does ONE thing well
        if (value.isBlank()) return null

        return try {
            val exists = userService.findByEmail(value) != null
            if (exists) {
                messageSource.getMessage("enter_unique_email", null, locale)
            } else null
        } catch (e: Exception) {
            null // If error checking, let it pass (other validations will catch issues)
        }
    }
}
Copy code
// Usage in validator
validator.validate(request, locale) {
    field(RegisterRequest::email) {
        required()
        email()
        custom(uniqueEmailRule) // Check database for uniqueness
    }
}
I had to do a a runBlocking with the Jakarta Validation. And by the way, I ended up not adding the other libraries to keep the package simple.
r
Ok, I didn't know the blocking nature of Jakarta Validation. I thought it was only a library called by a process and that it didn't do blocking calls 🤷‍♂️
By the way, checking if an email is unique in the system is a business validation, and you should not perform it in a controller, which is intended to check syntactic validations. I think the problem is what you want to achieve with “validation”
d
ya good point. That was just an example. The point is I need an async/reactive/non-blocking validator. I wonder though if any validation doing queries to the db would be considered business. In my library all my built in rules are syntactic. But users are free to create custom rules that may have db check. But I had put that uniqueEmailRule as an example (for custom db validations) in the readme. I'm wondering now if I should remove it now. But thanks a lot for the feedback.
r
I wonder though if any validation doing queries to the db would be considered business
yes, they do. I would remove the example from the documentation.
If you want an example of a custom validation, consider something related to constraints on strings or numbers.
d
ya I see the difference
Copy code
There are indeed two types of validation:

  1. Syntactic/Input Validation (Data validation)
    - Format checks (email format, length, regex patterns)
    - Type validation (is this a number? a valid date?)
    - Structure validation (required fields, field relationships)
    - Purpose: Ensure the data is well-formed and safe to process
  2. Business/Domain Validation (Business rules)
    - Uniqueness constraints
    - Business logic rules (e.g., "can't delete an order that's already shipped")
    - Domain-specific constraints (e.g., "age must be 18+ for this product")
    - Purpose: Ensure the operation is valid within your business context

  Where Each Should Live

  Traditional separation:
  - Controller/Presentation Layer: Syntactic validation only
  - Service/Domain Layer: Business validation

  So checking if an email is unique would typically belong in your service layer, not in a validation framework at the controller boundary.

  However, There's Nuance Here

  Arguments FOR including uniqueness in this validation DSL:

  1. User Experience: You want to tell the user immediately "this email is taken" rather than fail later in the service layer
  2. Fail Fast: Why process the entire request if you know it will fail on a business rule?
  3. Consistent Error Format: Users get the same error response structure for all validation failures
  4. Pragmatism: In many CRUD applications, the line between "input validation" and "business validation" is blurry

  Arguments AGAINST:

  1. Architectural Purity: Mixing concerns - validation framework now has database dependencies
  2. Reusability: Service methods should validate their own business rules regardless of how they're called (API, CLI, internal service)
  3. Testing Complexity: Harder to unit test validators that need database access
  4. Performance: Syntactic validation is fast; business validation might be slow
just for the DRY principle, I see the point. I need to check for email uniqueness in the service. I could not just rely on the validation, in case someone calls that service.
👍 1
🚀 1
k
I am curious, what makes it spring specific?
d
good point @kqr. I'd like it to be framework agnostic. I could upgrade it and remove the spring's message system (and remove the single @component and replace multiPartFile from the FileValidation Rules) and then make an adapter for spring. But people would have to create their own adapters for other frameworks. I don't know enough about other frameworks to do it myself. It would keep the core library simpler. And in the spring adapter I could add different framework specific validation methods. **update: I'm working on it 🙂