Coding for the unhappy paths

Microservices architecture

Designing for failure

  • Fail gracefully
  • Easy to diagnose where the issue arose

Recent python script


def processContent():
  url = "http://content.guardianapis.com/technology"
  response = urllib2.urlopen(url)
  data = response.read()
  results = json.loads(data).get('response', {}).get('results', [])
  for r in results:
    body = r.get('fields', {}).get('body', '')
    if body:
      cleantext = BeautifulSoup(body).text
      f = open("technology.txt", 'a')
      f.write(cleantext.encode('utf-8'))
      f.write('\n')
      f.close()		

How could this fail?

  • Http call might fail
  • Structure of the JSON document might not be as expected
  • Writing to disc could fail

Example

Reading an external resource and storing in memory

  • Access file on the web
  • Read contents
  • Parse contents into format
  • Store in memory

In Scala

Design our functions to return a computation context which can be explicitly type checked when used

Options in scala


sealed trait Option[A]

case class Some[A](a: A) extends Option[A]
case class None extends Option[A]

public class HelloWorld { 
   public static void main(String[] args) { 
      User user = findUser(1234)
      if(user != null) {
      	//continue with the program
  	  }
   }
   public User findUser(Int i)
}

object OptionExample {

 def main(args: Array[String]) {

    val user: User = findUser(1234)
    //doesn't compile -> this is an option
  }
  
 def findUser(i: Int): Option[User]


}

object OptionExample {

 def main(args: Array[String]) {

    val user: Option[User] = findUser(1234)
   	user match {
   		case Some(u) => //continue with your program
   		case None => //handle this
   	}
  }
  
 def findUser(i: Int): Option[User]


}

Example

Reading an external resource and storing in memory

  • Access file on the web
  • Read contents
  • Parse contents into format
  • Store in memory

sealed trait Either[+E,+A]

case class Left[+E](get: E) extends Either[E,Nothing]
case class Right[+A](get: A) extends Either[Nothing,A]

def fileFromWeb(key: String, bucketName: String): Either[Error, File] = {
    try {
      Right(getFile(key, bucketName))
    } catch {
      case NonFatal(e) => Left(Error(s"error reading the s3 
      cache ${e}"))
    }
  }

val file = getFileFromWeb()
file match {
  case Right(f) => //continue our execution
  case Left(error) => Log.error(error)
}
				

val file = getFileFromWeb()
file match {
  case Right(f) => readContents(f) match {
  		case Right(contents) => //continue
  		case Left(error) => Log.error(error)
	}
  case Left(error) => Log.error(error)
}
				

For comprehension


def method1(): Either[E, A]
def method2(a: A): Either[E, B]

for {
  a <- method1().right
  b <- method2(a).right
} yield b
					

  def populateCache(): Either[PermissionsReaderError, Date] = {
    for {
      obj <- getFileFromWeb(key, bucket).right
      data <- readContents(obj).right
      permissions <- parseFileConents(data.contents).right
      _ = storePermissionsData(permissions)
    } yield data.lastMod
  }	

  populateCache() match {
      case Right(d) => Logger.info(s"successfully updated permissions cache with last modified time ${d}")
      case Left(error) => Logger.error("error updating permissions 
      cache " + error)
    }
				
  • Compiler to check cases are handled
  • Handle errors in one place