Stackable Trait Patterns – Part I

Error Reporting Design with Stackable Traits

This post is the first of a two-part series of articles on Stackable Traits

“Traits let you modify the methods of a class, and they do so in a way that allows you to stack those modifications with each other”

(Programming in Scala by Martin Odersky)

Introduction

One of the reasons that Scala still feels to me like ‘a better Java’, is the power of its traits. A trait can be used for multiple inheritances, a Java interface, a rich interface with fields and a state and a mix into a class.

An interesting behavior of traits, as opposed to classes, is the call for a super method. In classes this is statically bound, meaning that the exact implementation to be invoked is known upfront. In traits however, this is dynamically bound. The term usually refers to an invocation of a method in an object, where the implementation is decided at runtime, i.e. a polymorphic call. The super call is not defined when a trait is defined, but only when it is mixed into a concrete class. Such behavior allows us to ‘stack’ the traits, and use the super call as a ‘pipe’, redirecting output – similar to the ‘pipe’ in Linux. This is the basis for the use-cases we’ll review.

Part I: Error Reporting Design

Consider the following:

An Ad-Server gets a request for an advertisement from a mobile phone.

We’ll discuss a solution for executing the actions to be taken when the Ad-Server encounters errors. There are two types of error which should be handled appropriately as follows:

On FatalError:

  1. The Monitor increments the counter Metric.
  2. The Logger prints to the Log
  3. Kafka-Producer sends a Json Event with a timestamp to Kafka.
  4. when step 3 failed, S3-Client uploads the Event as CSV with a timestamp to to S3 (backup)

On InvalidRequestError

  1. The Logger prints to the Log
  2. The Monitor increments the counter Metric.
  3. Kafka-Producer sends a Json Event to Kafka.

An Error has a code, a description, and unique properties:

And these are mocks for the Scala Clients:

Step-by-step implementation, using stackable traits

Create traits that represent the subscribers and mix them.

We’ll create the following traits, each represents a subscriber for an error event:

  • trait Log
  • trait Metric
  • trait S3_Backup
  • trait Kafka

Mix them with the ‘Error’ classes.

Here, we use a Scala-type system to describe the ‘Error’ classes and the actions that need to be taken:

Turn the traits into services that activate the Clients

Now it is clear which subscribers should receive a notification on error.

We can enrich the traits, so they can activate the clients as well:

So now each trait has a “send” method that handles the event using the appropriate client. However, we still need to trigger it on the occurrence of an error, and call them in the order described above.

‘Stack’ the services traits with each other

In other words, a pipe of calls for the ‘send’ method in the services. As discussed, we’ll need to use super method calls for that. Note that in this step we won’t be stacking modifications, but the side-effects that triggered an error.

Now we need to stack the traits according to the order and logic defined in the narrative described above. The order of the invocation of the traits will be right to left.

Hence the order for FatalError:

 

S3_Backup ← Kafka ← Monitor ← Log Where S3_Backup should be invoked only when the Kafka-Producer failed to send the event to Kafka.

 

And for InvalidRequestError:

Kafka ← Monitor ← Log

 

Let’s re-arrange the mix of the ‘Error’ classes:

And stack the traits:

You might notice that:

  1. The call ‘super.send(event)’ was added to ‘send’ method.
  2. The ‘override’ modifier changed to ‘abstract override’, because the send method now overrides the behavior, but also calls for an abstract method with super.send. The modifier indicates that this trait must be mixed with a concrete class(later…).
    By the way, if we omit the ‘abstract’ from the modifier, we’ll get the following error: “method send in class Sender is accessed from super. It may not be abstract unless it is overridden by a member declared ‘abstract’ and ‘override’ super.send(event)”
  3. Kafka would call super, i.e. S3-Backup would be invoked only when KafkaProducer fails to deliver.

Create and ‘stack’ modification traits

The event content needs modification to be in the right format (json, csv) before being sent as an input to KafkaProducer and S3. A fatal error needs to be sent with a timestamp.

Let’s write the modification method and the stackable modification traits:

And mix the modification traits to the ‘Error’ classes:

Create and mix ‘ServingErrorSender’ :

Last, to trigger error reports once an error object is created, we’ll mix them with a sending trait.

Try it out

We have completed the task!
Now, when invoking a ‘send’’ for an error, as in the following:

Results are with the following and are printed to the log:

Next

In Part II we’ll use stackable-actor traits for gathering actor’s metrics.

References:
Programming in Scala (chapter 12.5) / Martin Odersky and Co.

Read these next