Domain Model Validation In Kotlin: Part 4

Writing an (almost) functional app in an (almost) functional way

Dealing with exceptions

Writing a JVM application that doesn’t have to deal with exceptions is a chimaera, but the next best thing is isolating the code that deals with exceptional behaviour.

  • (1) we define this function as an extension function over any non-nullable type to have access to this (in the context of this::class) and get a classpath resource
  • (5) use is an extension function over AutoClosable that executes a block of code and then closes the resource
  • (6) apply is a scope function that invokes a function with the invoking instance as the receiver, equivalent to val properties = Properties(); properties.load(inputStream)
  • (8, 11) asList is an extension function we created for convenience to read a comma-separated string as a list of strings (we have to love extension functions):
  • (13) we return the Pair of valid properties from the use block
  • (14) if everything goes well (no exception thrown), we map the pair to Right
  • (16) if there’s an exception, we return a Left;we also need to extend a bit our error hierarchy:

Composing Validated and Either

Let’s recall from the previous article that we need to implement these two interfaces required to validate our model:

  • (1) our function is suspended to be able to use either blocks; there is the alternative of using either.eager if we’re not in a suspended context
  • (3) we start an either block, this enables us to use the bind function on Either or Validated
  • (4 —5) readProperties has an ApplicationError on the left, but to be able to compose easily with Validated<ApplicationErrors, T> (accumulated errors) we will map the single error to a list of errors using mapLeft
  • (5) bind short-circuits our either computation block; if readProperties returns:
    Right then the pair of expected values is destructured into allowedSenderProperties and receiveEmailConsentProperties
    Leftthen the computation ends (hence the short circuit), and the block evaluates to Left<ApplicationErrors>
    – the same applies to Validated, mapping Valid to Right and Invalid to Left
  • (7— 12) we use our validate function to get valid allowedSenders and consents (9) then map it to a Pair, then destructure into validatedAllowedSender and validatedConsents variables
  • (12) we use bind again, but this time on a Validated
  • (14–15) we can provide simple implementations to AllowedSenders and ReceiveEmailConsents, backed by the lists we just read from our property file
  • (5) we replaced mapLeft { it.nel() } with leftNel()
  • (7–10) our new validate overload maps our valid values to a pair, and we can destructure it; this is what we meant by in-place parallel validation
  • (2) the return type of readInput looks intimidating, but we can see in our main function below that we can deal with it nicely
  • (4) we wrap all the input strings into a Tuple4: we take some shortcuts in our proof of concept; a new data class would make the code more readable
  • (12) the potential exception-throwing code is isolated in the readStringfunction

Putting everything together

Our main wraps everything together: reads the configuration, sets the context for validations, reads the input and maps it to an EmailRoute. We are already familiar with all these techniques from this and the previous articles.

  • (1) we are not able to use type inference for the generic types of either because main returns Unit, thus we specify them explicitly
  • (4–5) set the validation context — we use the context receivers solution from the previous article
  • (6) destructure Tuple4 into the corresponding variables
  • (7) create a validated EmailRoute and then bind the validated value to theemailRoute variable
  • (9) we confirm that emailRoute is valid by printing it
  • (12) our either block ends here, and we apply the getOrHandle function to provide a way of dealing with errors: we print them
  • In the first part, we used inline classes to validate our simple data types in a type-safe way
  • We continued in the second part by validating multiple properties and lists of values and accumulating the errors
  • In the third part, we explored multiple ways of dealing with a validation context without using by relying solely on the compiler (without dependency injection frameworks)
  • And in this final part, we integrated our model into a demo CLI application that deals with exceptions and validation failures in a unitary and elegant way.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store