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