{"id":24407824,"url":"https://github.com/itv/lifecycle","last_synced_at":"2025-04-12T01:16:54.898Z","repository":{"id":45078373,"uuid":"64400497","full_name":"ITV/lifecycle","owner":"ITV","description":"A pattern for safe usage of resources. Acts as a Factory and a disposer of instances of a type.","archived":false,"fork":false,"pushed_at":"2022-01-28T14:13:38.000Z","size":77,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":49,"default_branch":"master","last_synced_at":"2025-04-12T01:16:50.340Z","etag":null,"topics":["cdt","cst"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ITV.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-07-28T14:06:09.000Z","updated_at":"2024-03-13T16:45:50.000Z","dependencies_parsed_at":"2022-09-07T22:11:33.949Z","dependency_job_id":null,"html_url":"https://github.com/ITV/lifecycle","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ITV%2Flifecycle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ITV%2Flifecycle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ITV%2Flifecycle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ITV%2Flifecycle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ITV","download_url":"https://codeload.github.com/ITV/lifecycle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248501859,"owners_count":21114684,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cdt","cst"],"created_at":"2025-01-20T05:18:08.859Z","updated_at":"2025-04-12T01:16:54.874Z","avatar_url":"https://github.com/ITV.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"Lifecycle\n=====\n[![Build Status](https://travis-ci.org/ITV/lifecycle.svg?branch=master)](https://travis-ci.org/ITV/lifecycle)\n\nA pattern for safe usage of resources.\nSupports monadic operations so usage can be composed.\n\nIt's similar to:\n\n* the [using statement](https://msdn.microsoft.com/en-GB/library/yh598w02.aspx) in C#\n* the [try-with-resources](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html) statement introduced in Java JDK 7\n\nBut also acts as a [Factory](https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)) for instantiating initialized instances of a type.\n\nLifecycle was born of frustration, aiming to help your codebase tidy up all used resources. It's a nice `try` / `finally` wrapper!\n\nA resource is anything that requires:\n\n* Some intialization operations\n* And/or some tear-down operations\n\nto be performed between use. For example, a file:\n\n* Must be opened before any read/write operations are performed\n* Any pending writes should be flushed, and the file handle returned to the OS after use\n\nSBT settings\n=====\nStable:\n```\n\"com.itv\" %% \"lifecycle\" % \"0.3\"\n```\n\n(Contrived) Example\n=====\n\nMeet `bob`, `fred`, and `barry`.\n\nThey each have a secret, but they'll only reveal it to you if you say hello first.\nAfter you're done using them, you should say goodbye - it'd be pretty rude not to.\n\n```scala\n case class Person(name: String, private val secret: String) {\n     private var readyToTellSecret = false\n\n     def hello(): Unit = {\n         readyToTellSecret = true\n         println(\"Said hello to \" + name)\n     }\n\n     def revealSecret(): String = {\n         require(readyToTellSecret, s\"it's rude to ask $name a secret before you've said hello\")\n         secret\n     }\n\n     def goodbye(): Unit = {\n         readyToTellSecret = false\n         println(\"Said goodbye to \" + name)\n     }\n }\n```\n\n```scala\nscala\u003e val bob = Person(\"Bob\", secret = \"I hate Barry\")\nbob: Person = Person(Bob,I hate Barry)\n\nscala\u003e val fred = Person(\"Fred\", secret = \"I hate Barry\")\nfred: Person = Person(Fred,I hate Barry)\n\nscala\u003e val barry = Person(\"Barry\", secret = \"I'm lonely\")\nbarry: Person = Person(Barry,I'm lonely)\n```\n\n`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.\n\nYou're gonna have a bad time if you ask `bob` his secret before saying hello:\n\n```scala\nscala\u003e bob.revealSecret()\njava.lang.IllegalArgumentException: requirement failed: it's rude to ask Bob a secret before you've said hello\n  at scala.Predef$.require(Predef.scala:224)\n  at Person.revealSecret(\u003cconsole\u003e:21)\n  ... 382 elided\n```\n\nWe always need to guarantee we interact with a `Person` in this manner:\n```scala\nscala\u003e bob.hello()\nSaid hello to Bob\n\nscala\u003e bob.revealSecret()\nres2: String = I hate Barry\n\nscala\u003e bob.goodbye()\nSaid goodbye to Bob\n```\n\nDefining a `Lifecycle`\n----\n\nHere's a `Lifecycle` that performs all the pleaseantries before/after using a `Person`.\n\n```scala\nscala\u003e import com.itv.lifecycle._\nimport com.itv.lifecycle._\n\nscala\u003e def personInteraction(person: Person): Lifecycle[Person] =\n     |     new VanillaLifecycle[Person] {\n     |         override def start(): Person = {\n     |             person.hello()\n     |             person\n     |         }\n     |\n     |         override def shutdown(instance: Person): Unit =\n     |             person.goodbye()\n     |     }\npersonInteraction: (person: Person)com.itv.lifecycle.Lifecycle[Person]\n```\n\n`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.\n\nNote: 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`.\n\nInteracting with a single person\n----\n\nWe want to grab the secret of an individual `Person`, and print it to stdout.\n\n\n```scala\nscala\u003e def announceSecret(person: Person) = {\n     |     val secret: String = Lifecycle.using(personInteraction(person)) { greetedPerson =\u003e\n     |         println(\"Asking secret\")\n     |         greetedPerson.revealSecret()\n     |     }\n     |\n     |     println(s\"${person.name}'s secret is '$secret'\")\n     | }\nannounceSecret: (person: Person)Unit\n\nscala\u003e announceSecret(bob)\nSaid hello to Bob\nAsking secret\nSaid goodbye to Bob\nBob's secret is 'I hate Barry'\n\nscala\u003e announceSecret(fred)\nSaid hello to Fred\nAsking secret\nSaid goodbye to Fred\nFred's secret is 'I hate Barry'\n\nscala\u003e announceSecret(barry)\nSaid hello to Barry\nAsking secret\nSaid goodbye to Barry\nBarry's secret is 'I'm lonely'\n```\n\nWe have used `Lifecycle.using` to interact safely with a given `Lifecycle[Person]`:\n\n```scala\ndef using[T, S](lifecycle: Lifecycle[T])(block: T =\u003e S): S\n```\n\nThe `using` method:\n* Gets an instance of `T` by using the `start` method of the given `Lifecycle`\n* Uses a block of code you provide to produce an `S`\n* 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\n* Returns the `S` your block of code produced\n\nThe code block we called `Lifecycle.using` with is pretty tame: it's unlikely to throw an exception.\n\nWhat 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.\n\nLet's write a method called `judgeThenAnnounce`. This method will also interact with an individual `Person`.\nIt will judge their secret: and throw an exception if they're being unkind. Otherwise it will return the secret without exception.\n\n```scala\nscala\u003e def judgeThenAnnounce(person: Person) = {\n     |     val secret: String = Lifecycle.using(personInteraction(person)) { greetedPerson =\u003e\n     |         println(\"Asking secret\")\n     |         val revealedSecret: String = greetedPerson.revealSecret()\n     |\n     |         if (revealedSecret contains \"hate\")\n     |             throw new IllegalStateException(s\"I'm not going to repeat what ${greetedPerson.name} just said to me.\")\n     |         else\n     |             revealedSecret\n     |     }\n     |\n     |     println(s\"${person.name}'s secret is '$secret'\")\n     | }\njudgeThenAnnounce: (person: Person)Unit\n```\n```scala\nscala\u003e judgeThenAnnounce(bob)\nSaid hello to Bob\nAsking secret\nSaid goodbye to Bob\njava.lang.IllegalStateException: I'm not going to repeat what Bob just said to me.\n  at $anonfun$1.apply(\u003cconsole\u003e:24)\n  at $anonfun$1.apply(\u003cconsole\u003e:19)\n  at com.itv.lifecycle.Lifecycle$.using(Lifecycle.scala:51)\n  at .judgeThenAnnounce(\u003cconsole\u003e:19)\n  ... 746 elided\n```\n```scala\nscala\u003e judgeThenAnnounce(fred)\nSaid hello to Fred\nAsking secret\nSaid goodbye to Fred\njava.lang.IllegalStateException: I'm not going to repeat what Fred just said to me.\n  at $anonfun$1.apply(\u003cconsole\u003e:24)\n  at $anonfun$1.apply(\u003cconsole\u003e:19)\n  at com.itv.lifecycle.Lifecycle$.using(Lifecycle.scala:51)\n  at .judgeThenAnnounce(\u003cconsole\u003e:19)\n  ... 758 elided\n```\n```scala\nscala\u003e judgeThenAnnounce(barry)\nSaid hello to Barry\nAsking secret\nSaid goodbye to Barry\nBarry's secret is 'I'm lonely'\n```\n\nLooks 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.\n\nInteracting with multiple people\n-----\n\nLet's extend this example further, and interact with multiple greeted `People` at the same time.\n\nWe will say a `Person` is friends with another `Person` if they both share the same secret.\n\n```scala\nscala\u003e def areFriends(personA: Person, personB: Person): Boolean = {\n     |     val interrogation = for {\n     |         a \u003c- personInteraction(personA)\n     |         b \u003c- personInteraction(personB)\n     |     }\n     |         yield (a.revealSecret(), b.revealSecret())\n     |\n     |     Lifecycle.using(interrogation) {\n     |         case (secretA, secretB) =\u003e\n     |             secretA == secretB\n     |     }\n     | }\nareFriends: (personA: Person, personB: Person)Boolean\n\nscala\u003e areFriends(bob, fred)\nSaid hello to Bob\nSaid hello to Fred\nSaid goodbye to Fred\nSaid goodbye to Bob\nres10: Boolean = true\n\nscala\u003e areFriends(bob, barry)\nSaid hello to Bob\nSaid hello to Barry\nSaid goodbye to Barry\nSaid goodbye to Bob\nres11: Boolean = false\n\nscala\u003e areFriends(fred, barry)\nSaid hello to Fred\nSaid hello to Barry\nSaid goodbye to Barry\nSaid goodbye to Fred\nres12: Boolean = false\n\nscala\u003e areFriends(barry, barry)\nSaid hello to Barry\nSaid hello to Barry\nSaid goodbye to Barry\nSaid goodbye to Barry\nres13: Boolean = true\n```\n\nWe can `map` and  `flatMap` a `Lifeycle` just like any other container. We get the same resource safety guarantees:\n\n```scala\nscala\u003e personInteraction(bob).map(_.revealSecret().toUpperCase).foreach(println)\nSaid hello to Bob\nI HATE BARRY\nSaid goodbye to Bob\n```\n\nNote: 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.\n\n\nLong running Lifecycles\n----\n\nOur typical usage is for our entire program to be defined within a single `Lifeycle`.\n\n```scala\nscala\u003e trait HttpServer {\n     |     def stop(): Unit = println(\"stopped\")\n     | }\ndefined trait HttpServer\n\nscala\u003e val httpServerLifecycle: Lifecycle[HttpServer] =\n     |     new VanillaLifecycle[HttpServer] {\n     |         override def start: HttpServer = {\n     |             val server: HttpServer = new HttpServer {}\n     |             /**\n     |             Construct some HTTP server, bind it to a port and establish request routes\n     |             **/\n     |             println(\"started\")\n     |             server\n     |         }\n     |         \n     |         override def shutdown(instance: HttpServer) =\n     |             instance.stop()\n     |     }\nhttpServerLifecycle: com.itv.lifecycle.Lifecycle[HttpServer] = $anon$1@6cd81f3f\n```\n\n`Lifecycle` has a method that will help with this: `runUntilJvmShutdown`.\n\n```scala\nhttpServerLifecycle.runUntilJvmShutdown\n```\n\nThis will start an instance using the `Lifecycle`, and register the `shutdown` method to be run on the instance when the JVM exits.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitv%2Flifecycle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fitv%2Flifecycle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fitv%2Flifecycle/lists"}