Git Product home page Git Product logo

lifecycle's Introduction

Lifecycle

Build Status

A pattern for safe usage of resources. Supports monadic operations so usage can be composed.

It's similar to:

But also acts as a Factory for instantiating initialized instances of a type.

Lifecycle was born of frustration, aiming to help your codebase tidy up all used resources. It's a nice try / finally wrapper!

A resource is anything that requires:

  • Some intialization operations
  • And/or some tear-down operations

to be performed between use. For example, a file:

  • Must be opened before any read/write operations are performed
  • Any pending writes should be flushed, and the file handle returned to the OS after use

SBT settings

Stable:

"com.itv" %% "lifecycle" % "0.3"

(Contrived) Example

Meet bob, fred, and barry.

They each have a secret, but they'll only reveal it to you if you say hello first. After you're done using them, you should say goodbye - it'd be pretty rude not to.

 case class Person(name: String, private val secret: String) {
     private var readyToTellSecret = false

     def hello(): Unit = {
         readyToTellSecret = true
         println("Said hello to " + name)
     }

     def revealSecret(): String = {
         require(readyToTellSecret, s"it's rude to ask $name a secret before you've said hello")
         secret
     }

     def goodbye(): Unit = {
         readyToTellSecret = false
         println("Said goodbye to " + name)
     }
 }
scala> val bob = Person("Bob", secret = "I hate Barry")
bob: Person = Person(Bob,I hate Barry)

scala> val fred = Person("Fred", secret = "I hate Barry")
fred: Person = Person(Fred,I hate Barry)

scala> val barry = Person("Barry", secret = "I'm lonely")
barry: Person = Person(Barry,I'm lonely)

bob, fred and barry aren't going to win any prizes for lack of side-effects, but you've got to expect the unexpected when dealing with people.

You're gonna have a bad time if you ask bob his secret before saying hello:

scala> bob.revealSecret()
java.lang.IllegalArgumentException: requirement failed: it's rude to ask Bob a secret before you've said hello
  at scala.Predef$.require(Predef.scala:224)
  at Person.revealSecret(<console>:21)
  ... 382 elided

We always need to guarantee we interact with a Person in this manner:

scala> bob.hello()
Said hello to Bob

scala> bob.revealSecret()
res2: String = I hate Barry

scala> bob.goodbye()
Said goodbye to Bob

Defining a Lifecycle

Here's a Lifecycle that performs all the pleaseantries before/after using a Person.

scala> import com.itv.lifecycle._
import com.itv.lifecycle._

scala> def personInteraction(person: Person): Lifecycle[Person] =
     |     new VanillaLifecycle[Person] {
     |         override def start(): Person = {
     |             person.hello()
     |             person
     |         }
     |
     |         override def shutdown(instance: Person): Unit =
     |             person.goodbye()
     |     }
personInteraction: (person: Person)com.itv.lifecycle.Lifecycle[Person]

start produces an initialized instance of a Person. shutdown should always be performed after using a Person: even if the interaction caused an exception to be thrown.

Note: in this example we perform the start operations on a pre-instantiated Person: but it's perfectly valid to instantiate the resource within the start method of a Lifecycle.

Interacting with a single person

We want to grab the secret of an individual Person, and print it to stdout.

scala> def announceSecret(person: Person) = {
     |     val secret: String = Lifecycle.using(personInteraction(person)) { greetedPerson =>
     |         println("Asking secret")
     |         greetedPerson.revealSecret()
     |     }
     |
     |     println(s"${person.name}'s secret is '$secret'")
     | }
announceSecret: (person: Person)Unit

scala> announceSecret(bob)
Said hello to Bob
Asking secret
Said goodbye to Bob
Bob's secret is 'I hate Barry'

scala> announceSecret(fred)
Said hello to Fred
Asking secret
Said goodbye to Fred
Fred's secret is 'I hate Barry'

scala> announceSecret(barry)
Said hello to Barry
Asking secret
Said goodbye to Barry
Barry's secret is 'I'm lonely'

We have used Lifecycle.using to interact safely with a given Lifecycle[Person]:

def using[T, S](lifecycle: Lifecycle[T])(block: T => S): S

The using method:

  • Gets an instance of T by using the start method of the given Lifecycle
  • Uses a block of code you provide to produce an S
  • Guarantees the shutdown method of the given Lifecycle is called with the T instance: even if the block of code you provided threw an exception
  • Returns the S your block of code produced

The code block we called Lifecycle.using with is pretty tame: it's unlikely to throw an exception.

What if there was a strong chance our code block will fail in an unexpected manner? We should still be courteous and ensure we say goodbye to the Person we're interacting with.

Let's write a method called judgeThenAnnounce. This method will also interact with an individual Person. It will judge their secret: and throw an exception if they're being unkind. Otherwise it will return the secret without exception.

scala> def judgeThenAnnounce(person: Person) = {
     |     val secret: String = Lifecycle.using(personInteraction(person)) { greetedPerson =>
     |         println("Asking secret")
     |         val revealedSecret: String = greetedPerson.revealSecret()
     |
     |         if (revealedSecret contains "hate")
     |             throw new IllegalStateException(s"I'm not going to repeat what ${greetedPerson.name} just said to me.")
     |         else
     |             revealedSecret
     |     }
     |
     |     println(s"${person.name}'s secret is '$secret'")
     | }
judgeThenAnnounce: (person: Person)Unit
scala> judgeThenAnnounce(bob)
Said hello to Bob
Asking secret
Said goodbye to Bob
java.lang.IllegalStateException: I'm not going to repeat what Bob just said to me.
  at $anonfun$1.apply(<console>:24)
  at $anonfun$1.apply(<console>:19)
  at com.itv.lifecycle.Lifecycle$.using(Lifecycle.scala:51)
  at .judgeThenAnnounce(<console>:19)
  ... 746 elided
scala> judgeThenAnnounce(fred)
Said hello to Fred
Asking secret
Said goodbye to Fred
java.lang.IllegalStateException: I'm not going to repeat what Fred just said to me.
  at $anonfun$1.apply(<console>:24)
  at $anonfun$1.apply(<console>:19)
  at com.itv.lifecycle.Lifecycle$.using(Lifecycle.scala:51)
  at .judgeThenAnnounce(<console>:19)
  ... 758 elided
scala> judgeThenAnnounce(barry)
Said hello to Barry
Asking secret
Said goodbye to Barry
Barry's secret is 'I'm lonely'

Looks good eh? Even though our code block blew up a couple of times due to the extreme views of bob and fred, we were courteous to each Person: always saying hello and goodbye.

Interacting with multiple people

Let's extend this example further, and interact with multiple greeted People at the same time.

We will say a Person is friends with another Person if they both share the same secret.

scala> def areFriends(personA: Person, personB: Person): Boolean = {
     |     val interrogation = for {
     |         a <- personInteraction(personA)
     |         b <- personInteraction(personB)
     |     }
     |         yield (a.revealSecret(), b.revealSecret())
     |
     |     Lifecycle.using(interrogation) {
     |         case (secretA, secretB) =>
     |             secretA == secretB
     |     }
     | }
areFriends: (personA: Person, personB: Person)Boolean

scala> areFriends(bob, fred)
Said hello to Bob
Said hello to Fred
Said goodbye to Fred
Said goodbye to Bob
res10: Boolean = true

scala> areFriends(bob, barry)
Said hello to Bob
Said hello to Barry
Said goodbye to Barry
Said goodbye to Bob
res11: Boolean = false

scala> areFriends(fred, barry)
Said hello to Fred
Said hello to Barry
Said goodbye to Barry
Said goodbye to Fred
res12: Boolean = false

scala> areFriends(barry, barry)
Said hello to Barry
Said hello to Barry
Said goodbye to Barry
Said goodbye to Barry
res13: Boolean = true

We can map and flatMap a Lifeycle just like any other container. We get the same resource safety guarantees:

scala> personInteraction(bob).map(_.revealSecret().toUpperCase).foreach(println)
Said hello to Bob
I HATE BARRY
Said goodbye to Bob

Note: in this example we're only interacting with Person instances, this is not a limitation, you can combine Lifecycle's of different intance types in the exact same manner.

Long running Lifecycles

Our typical usage is for our entire program to be defined within a single Lifeycle.

scala> trait HttpServer {
     |     def stop(): Unit = println("stopped")
     | }
defined trait HttpServer

scala> val httpServerLifecycle: Lifecycle[HttpServer] =
     |     new VanillaLifecycle[HttpServer] {
     |         override def start: HttpServer = {
     |             val server: HttpServer = new HttpServer {}
     |             /**
     |             Construct some HTTP server, bind it to a port and establish request routes
     |             **/
     |             println("started")
     |             server
     |         }
     |         
     |         override def shutdown(instance: HttpServer) =
     |             instance.stop()
     |     }
httpServerLifecycle: com.itv.lifecycle.Lifecycle[HttpServer] = $anon$1@6cd81f3f

Lifecycle has a method that will help with this: runUntilJvmShutdown.

httpServerLifecycle.runUntilJvmShutdown

This will start an instance using the Lifecycle, and register the shutdown method to be run on the instance when the JVM exits.

lifecycle's People

Contributors

andrewgee avatar applethepeanut avatar catiaferreira6 avatar mcarolan avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

mcarolan

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.