Code Mozzer

and if you have five seconds to spare, then I'll tell you the story of my life

DDD Domain Validation, actually Action Validation

• domain,validation,action,composable,messages

Introduction

When I talk about DDD domain validation I do not think about problems that we have when dealing with simple requirements like ‘Name is not null’ or ‘Password minimal length is 5 characters’, what I’m thinking about is validating whole actions that happens in our ecosystem. e.g. User Creation, AD Campaign launch, Complex excel file import, etc.

Dealing with complex validation we need to be able to collect all validation states at processing time not just first and return single validation present validation state if there are multiple ones.

Example:

3 errors are present at action execution time

we do not want to get just first error [Invalid email], we want to be able to get all errors that can be evaluated on action execution. To achieve that kind of validation, we need to have something like validation state builder and for that purpose we introduced Messages, please rebember it as I would use it quite often.

Messages are concept that I heard from my mentor 5 years ago when he introduced it to support validation and also for various other things that can we do with it, as messages are not just for validation.

In next chapter I will continue to introduce you to Messages concept, which I will use to support Deep Action Validation.

Notice: I will use SCALA for my coding examples.

Messages - DDD domain validation core

messages source code.

For our purpose we can look at Messages object as validation state builder.

Each Messages object has inner collecation of single Message objects and also can have reference to parentMessages object (which usage will be explained later).

Messages are consist, as I mentioned, of smaller parts called Message. Message object is object that can have it’s type, messageText, key(wihch is optional and we will later use it to support validation of e.g. specific inputs which are identified by id) and last message part is childMessages what provides us a great tool for building composable message trees.

Available message types:

  1. INFORMATION
  2. WARNING
  3. ERROR

Messages structure like this allow us to build messages iteratively and also allow us to make decision about next action upon prior messages state.

Example:

Example is about validating action: UserCreate

...
private val MAX_ALLOWED_LENGTH = 80
private val MAX_ALLOWED_CHARACTER_ERROR = s"must be less than or equal to $MAX_ALLOWED_LENGTH character"

private def validateEmail(item: UserCreateEntity, validationMessages: Messages) {
    val localMessages = Messages.of(validationMessages)

    val fieldValue = item.email

    ValidateUtils.validateEmail(
      fieldValue,
      UserCreateEntity.EMAIL_FORM_ID,
      localMessages
    )

    ValidateUtils.validateLengthIsLessThanOrEqual(
      fieldValue,
      MAX_ALLOWED_LENGTH,
      localMessages,
      UserCreateEntity.EMAIL_FORM_ID.value,
      MAX_ALLOWED_CHARACTER_ERROR
    )

    if(!localMessages.hasErrors()) {
      val doesExistWithEmail = this.entityDomainService.doesExistByByEmail(fieldValue)
      ValidateUtils.isFalse(
        doesExistWithEmail,
        localMessages,
        UserCreateEntity.EMAIL_FORM_ID.value,
        "User already exists with this email"
      )
    }
  }

When looking into this code you can see usage of ValidateUtils which are extracted helpers that are used for populating Messages object for predefined situations. ValidateUtils source code.

During email validation we first check if email is validly structured calling ValidateUtils.validateEmail(… and we also check if email has valid length by calling ValidateUtils.validateLengthIsLessThanOrEqual(… .

After that two validations are done we will continue to check if email is already assigned to some User only if prior email validations are valid and we do that by calling if(!localMessages.hasErrors()) { … .

That allow us to not to go on expensive database call if we know that that kind of user cannot exists. This is only part of e.g. UserCreateValidator. Completly source code can be found here.

You can also notice that one of validation parameters is UserCreateEntity.EMAIL_FORM_ID that will connect validation state to specific inputId.

In previous example we have been deciding next action move upon fact if messages hasErrors, but we could easily also check if there are any WARNING messages that will make us decide to retry e.g. some sync action.

One thing that you can notice is usage of ‘localMessages’ . Local messages are messages that have been created same as any message but with parentMessages. With that we want to achieve to have messages that referece only current validation [in our example emailValidation] , so we can call localMessages.hasErrors where we check if only emailValidation hasErrors, not all previous validations. Also when we add message to localMessages, message is also added to parentMessages and we have all validation messages on one place.

We have seen Messages in action and in next chapter will start to talk about ItemValidator.

ItemValidator - reusable validation

ItemValidator is simple trait(interface) that force us to implement vlaidate method, which needs to return ValidationResult.

ItemValidator:

trait ItemValidator[T] {
  def validate(item:T) : ValidationResult[T]
}

ValidationResult:

case class ValidationResult[T: Writes](
  validatedItem : T,
  messages      : Messages
) {
  Asserts.argumentIsNotNull(validatedItem)
  Asserts.argumentIsNotNull(messages)

  def isValid :Boolean = {
    !messages.hasErrors
  }

  def errorsRestResponse = {
    Asserts.argumentIsTrue(!this.isValid)

    ResponseTools.of(
      data      = Some(this.validatedItem),
      messages  = Some(messages)
    )
  }
}

When ItemValidators e.g. UserCreateValidator are implemented to be dependecy injection components, then ItemValidator objects can be autowired and reused in any object that needs UserCreate action validation.

After validation is executed, we will check if validation was successfult and if it is then we can proceed to save user in database, but if not, we will return API response containing validation errors.

In next section we will talk about how to present validation errors in restful API response and also how to communicate with API user about execution action states.

Unified API Response - simple user interaction

After we have successfuly validated user action [in our case user creation] we need to display validation action results to API user.

I have created unified response that will be returned on each request.

Response is structured to have GLOBAL and LOCAL messages. LOCAL messages are messages that are coupled to specific input[field] e.g. = “username is too log. Allowed length is 80 chars”. GLOBAL messages are messages that are reflecting state of whole data on page, i.e. “User will not be active untill is approwed”. Local and Global messages are having three levels: ERROR, WARNING and INFORMATION. Data response part is data specific to some request.

With this kind structured response we can easily create client handler that will be responsible for displaying errors, warnings and information messages. Global messages will be displayed at the top of the page, becouse that messages are related to lobal API action state, and LOCAL messages can be displayed near specifed input(field) id, as are directly coupled to that field value. Also ERROR messages can be presented in red colour, WARNING messages in yellow and INFORMATION in blue clolor.

Example:

{
    "messages" : {
        "global" : {
            "info": [],
            "warnings": [],
            "errors": []
        },
        "local" : [
           {
                "inputId" : "email",
                "errors" : ["User with this email already exists."],
                "warnings" : [],
                "info" : []
            }
        ]
    },
	"data":{
	    "firstName": "John",
	    "lastName": "Doe",
	    "username": "johndoer",
	    "email": "john.doe@gmail.com"
    }
}
{
    "messages" : {
        "global" : {
            "info": ["User successfuly created."],
            "warnings": ["User will not be available for login until is activated"],
            "errors": []
        },
        "local" : []
    },
	"data":{
	    "id": 2,
	    "firstName": "John",
	    "lastName": "Doe",
	    "username": "johndoer",
	    "email": "john.doe@gmail.com"
    }
}

To support transition from validation Messages to this kind of Unified API Response, I have created helper classes and tools here.

Completly source code seeds

Completly restfull API, scala, Play framework seeds can be found here in two versions:

Conclusion

This was my suggestion how to support deep, composable action validation which can be easily presented to user. Please share your thougts.

comments powered by Disqus