Composing functionality in actors

If you remember, we had very little functionality in our Akka Patterns application. The only thing it allowed us to do was to manipulate the customers—all be it with nice REST API, which talked to the actors, which then saved the Customer instances in MongoDB. However, it still only shifted one kind of thing back and forth. Let’s now update it to push two things around by adding the User.

Registering a customer

The motivation for the example is that we want to be able to register a customer. (You know, like a proper e-commerce website, where if you want to buy a widget for $10, you have to create an account, give your date of birth, provide a scan of a letter from the hospital in which your mother was born… and breathe!) We begin building the abomination by adding the User case class, which is simply case class User(id: Identity, username: String, password: String). Then, we will require POSTs to the /customer endpoint to give document that can be unmarshalled into

/**
 * Registers a customer and a user. After registering, we have a user account 
 * for the given customer.
 *
 * @param customer the customer
 * @param user the user
 */
case class RegisterCustomer(customer: Customer, user: User)

We will also specify that the response to the POST to /customers can be either a successful confirmation of registration or an error message. Let’s create a structure that allows us to handle just that, by first creating a type that represents an error.

/**
 * Base type for failures
 */
trait Failure {
  /**
   * The error code for the failure
   * @return the error code
   */
  def code: String
}

We extend this trait (defined in the domain) in the appropriate actors that respond with specific failures; and these failures might include additional information. Turning to our customer registration, the responses are either the description of failure or the confirmation. In Scala-speak, we have

/**
 * Reply to successful customer registration
 * @param customer the newly registered customer
 * @param user the newly registered user
 */
case class RegisteredCustomer(customer: Customer, user: User)

/**
 * Reply to unsuccessful customer registration
 * @param code the error code for the failure reason
 */
case class NotRegisteredCustomer(code: String) extends Failure

So, the CustomerActor reacts to RegisterCustomer message by replying with Either[NotRegisteredCustomer, RegisteredCustomer], which the CustomerService gladly uses to complete the requests.

Where’s the composition?

Let’s play spot the composition: to register a customer, we must first successfully register a user. To register a user, we must check that the username is unique, … . By golly! That’s it! The CustomerActor needs to combine functionality of registering a customer and registering a user. This is a job for appropriate traits. Let’s have a go, starting with the scaffolding arund user registration

/**
 * Registers a user. Checks the password complexity and that the username 
 * is not duplicated
 *
 * @param user the user to be registered
 */
case class Register(user: User)

/**
 * Successfully registered a user
 *
 * @param user the user that's just been registered
 */
case class RegisteredUser(user: User)

/**
 * Unsuccessful registration with the error code
 * @param code the error code
 */
case class NotRegisteredUser(code: String) extends Failure

Now that we have the messages (that the UserActor will react to at some point), we can try creating the functionality that the actors will use.

trait UserOperations {
	
  def register(user: User): Either[Failure, RegisteredUser] = { ... }
}

trait CustomerOperations {
	
  def register(customer: Customer)(ru: RegisteredUser): 
    Either[Failure, RegisteredCustomer] = { ... }

}

Now, let’s mix in the right functionality into our CustomerActor. When we receive the RegisterCustomer message, we first register a user; when that works, we register the customer. We reply with either the failures or with the final result.

class CustomerActor extends Actor with Configured 
  with CustomerOperations with UserOperations {

  protected def receive = {
    ...

    case RegisterCustomer(customer, user) =>
      import scalaz._
      import Scalaz._

      sender ! (registerUser(user) >>= registerCustomer(customer))
  }
}

Excellent. The structure is ours. Notice that we mix in the appropriate *Operations trait into our actor: in this particular case, the CustomerActor needs the functionalty of users and customers; hence we mix in the CustomerOperations and UserOperations.

The details

You may notice that I’ve skipped the implementation of the registerUser and registerCustomer functions. They are actually quite easy, but we need to fill in some blanks before we can jump into the implementation. The customers and users live in our MongoDB database; in their own collections. So, the UserOperations and CustomerOperations each need to know which collection to save the objects into. Nothing is easier: we’ll simply wish these collections into existence and leave the responsibility of giving their concrete form to someone else.

trait CustomerOperations extends TypedCasbah with SearchExpressions {
  def customers: MongoCollection

  ...
}

trait UserOperations extends TypedCasbah with SearchExpressions {
  def users: MongoCollection
  
  ...
}

This allows us to use the appropriate MongoCollections in our functions, but leave out the responsibility to prepare the collections to some overall configuration. With this in mind, let’s write some real Scala & MongoDB code:

trait UserOperations extends TypedCasbah with SearchExpressions {
  def users: MongoCollection
  def sha1 = MessageDigest.getInstance("SHA1")

  def getUser(id: domain.Identity) = 
    users.findOne(entityId(id)).map(mapper[User])

  def getUserByUsername(username: String) = 
    users.findOne(MongoDBObject("username" -> username)).map(mapper[User])

  def registerUser(user: User): Either[Failure, RegisteredUser] = {
    getUserByUsername(user.username) match {
      case None =>
        val hashedPassword = 
          java.util.Arrays.toString(sha1.digest(user.password.getBytes))
        val userToRegister = user.copy(password = hashedPassword)
        users += serialize(userToRegister)
        Right(RegisteredUser(userToRegister))
      case Some(_existingUser) =>
        Left(NotRegisteredUser("User.duplicateUsername"))
    }
  }

}

trait CustomerOperations extends TypedCasbah with SearchExpressions {
  def customers: MongoCollection

  def getCustomer(id: domain.Identity) = 
    customers.findOne(entityId(id)).map(mapper[Customer])

  def findAllCustomers() = 
    customers.find().map(mapper[Customer]).toList

  def insertCustomer(customer: Customer) = {
    customers += serialize(customer)
    customer
  }

  def registerCustomer(customer: Customer)(ru: RegisteredUser): 
    Either[Failure, RegisteredCustomer] = {

    customers += serialize(customer)
    Right(RegisteredCustomer(customer, ru.user))
  }

}

Notice that I’ve kept the operations traits independent and decoupled from the source of the MongoCollections. However, I don’t want to repeat def users = .... every time I mix in the UserOperations trait, especially since the collection will be the same one. The good news is that there’s nothing stopping me from creating a MongoCollections trait, which defines these collections and that I can mix in to my actors:

trait MongoCollections extends Configured {
  def customers = configured[MongoDB].apply("customers")
  def users = configured[MongoDB].apply("users")
}

Wiring up

Let’s put all the pieces together. We have the MongoCollections, UserOperations, CustomerOperations, UserActor, CustomerActor and CustomerService. The CustomerActor mixes in the CustomerOperations, UserOperations and MongoCollections; the UserActor> only mixes in the UserOperations and MongoCollections. Finally, the CustomerService looks up the CustomerActor and sends it the appropriate messages to register a customer.

We verify that we got everything right with a small service-level spec:

class CustomerServiceSpec extends DefaultApiSpecification {
  implicit val service = rootService

  "Registering a customer" in {
    val rc = RegisterCustomer(
      joeBloggs,
      User(UUID.randomUUID(), "janm", "Like I'll tell you!"))
    val registered = perform[RegisterCustomer, RegisteredCustomer](
                       POST, "/customers", rc)

    (registered.customer must_== joeBloggs) and
    (registered.user.username must_== "janm") and
    (registered.user.password must_!= "Like I'll tell you")
  }

}

Summary

In this post, I’ve described the latest changes to the 2.9.2 branch of https://github.com/janm399/akka-patterns. There’s a lot more code to explore, so head over to my GitHub page, checkout the code and try it out for yourselves. As always, if you have any comments or requests, ping me at @honzam399.

This entry was posted in Jan's Blog and tagged , , , . Bookmark the permalink.

One Response to Composing functionality in actors

  1. Pingback: This week in #Scala (14/09/2012) | Cake Solutions Team Blog

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>