Composing Traits and Declarative Validation

Scala’s traits are a nice fit for JSR-303 validation. Here’s an example. Suppose we have a web service interface that has methods like:

@WebService
trait Notification {
  def deleteTopic(apiKey: String, topicId: Long)
  def getSubscriber(apiKey: String, userId: String): Subscriber
  def unsubscribe(apiKey: String, userId: String, topicId: Long, context: SubscriptionContext)
  //...
}

Notice that the methods share many but not all of the same parameters. For example, all of them take apiKey. Our validation code should be factored to handle common parameters independently.  JSR-303 supports this very well. However, we also want to return validation errors to the caller with names that correspond directly to the web service method parameters. For example, if deleteTopic() is called with an invalid topicId, we want to respond with a fault like:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <soap:Fault>
         <faultcode>soap:Client</faultcode>
         <faultstring>Errors in request</faultstring>
         <errors>
           <error>
             <inputElement>topicId</inputElement>
             <errorMessage>99 is invalid</errorMessage>
          </error>
        </errors>
      </soap:Fault>
   </soap:Body>
</soap:Envelope>

The parameter name/validation error requirement would be a pain to handle in Java because the validation object would need to be composed as a hierarchy of individual objects. The hierarchy would be reflected in the validation errors and would have to be manually flattened before returning the errors to the caller.

In our web service implementation we will validate the parameters by creating a flat object containing the method parameters as properties.  Then we will call throwFaultIfInvalid() which will validate the parameters and throw a Fault exception if there are errors. For example:

  def unsubscribe(apiKeyIn: String, userIdIn: String, topicIdIn: Long, context: SubscriptionContext) {
    val cmd = new UnsubscribeCmd {
      apiKey = apiKeyIn
      userId = userIdIn
      topicId = topicIdIn
      subscriptionContext = context
    }
    throwFaultIfInvalid(cmd)

    // Logic to do unsubscribe...
  }

Above, cmd is a stack of five validation traits on top of a base Object.

@UnsubscribeCmdValid
trait UnsubscribeCmd extends UserIdCmd with TopicIdCmd with SubscriptionContextCmd {
  var subscription: Subscription = _
}

@UserIdValid
trait UserIdCmd extends ApiKeyCmd {
  @NotEmpty var userId: String = _
  lazy val subscriber: Option[Subscriber] = // findSubscriber(userId)
}

@TopicIdValid
trait TopicIdCmd extends ApiKeyCmd {
  @NotZero var topicId: Long = _
  lazy val topic: Option[Topic] = // findTopic(topicId)
}

@SubscriptionContextValid
trait SubscriptionContextCmd extends ApiKeyCmd {
  @NullInvalid var subscriptionContext: SubscriptionContext = _
}

@ApiKeyValid
trait ApiKeyCmd {
  @NotEmpty var apiKey: String = _
  lazy val application: Option[Application] = // findApplication(apiKey)
}

The validator for UnsubscribeCmd looks like:

class UnsubscribeCmdValidator extends Validator[UnsubscribeCmdValid, UnsubscribeCmd] {
  @Autowired var subscriptionDao: SubscriptionDao = _

  def isValid(obj: UnsubscribeCmd, context: ConstraintValidatorContext): Boolean = {

    ifSome(for (subscriber  obj.subscription = subscription
    }
  }

Nice! Traits allow us to compose the validators together in a way that results in very clean validation objects and validation code.

Side note

Sadly, as of Scala 2.9 you can’t create custom JSR-303 annotations (like @UnsubscribeCmdValid above) which require RetentionPolicy.RUNTIME in Scala. Go, right now, and vote for SI-32 so we don’t have to keep writing these in Java.   Thank you for your support.

Speak Your Mind

*