We’re all using resources on a daily basis. You turn on the water tap, wash your hands for at least 20–30 seconds, and turn it off. You switch the light on if it’s dark, and off when you no longer need it. Resources have a . You open them, use them, and close them afterward. Not only light and water taps fall in that category, but also all kinds of database connections, HTTP servers, clients, files, or streams. lifecycle What’s interesting about resources is that they often depend on each other, so we care about the of their acquisition and release. We want to see how we wash our hands, so we switch the light on before turning the water tap. If a database is used for processing HTTP requests, we want to connect to it first and after that, we start the HTTP server. However, during the application shutdown, we want to stop the HTTP server, and then close the database connection. In other words, we often want to release the resources in ordering reverse acquisition order. Finally, we’d like to resource management. We shouldn't go through the code all over again to manually verify if we properly release them. Ideally, we should come up with a construct that does that automatically. It’d be awesome if both light and water tap would turn on/off itself as you’re approaching! automate There are multiple ways to do resource management in Scala. We’ll start with the most basic ones and iteratively improve to achieve a fully compositional and safe way of dealing with them. In the end, we’ll see how to manage resources in purely functional programs with . Cats Effect 2.x This article is dedicated to beginner and intermediate level Scala programmers. The source code shown in this blog post can be found . With all of that in mind, let’s start! in this repository It all starts with… try-finally try-finally is probably the most basic way of managing resources. We to use them, and , whatever happens, we try to close them: try finally { () = ??? } sqsConsumer = {} { } { sqsConsumer.close() } trait SqsConsumer def close val new SqsConsumer try // using sqsConsumer finally The code above wouldn’t differ much from its Java equivalent. try-finally is a construct that comes from and is meant to be used in the imperative world. This approach has the following disadvantages: It’s not composable. It’s manual. You have to always look at the code, and check if all the resources you acquire are closed.It’s easy to forget about proper ordering when closing. If we’re closing more resources in the finally block, and something throws an exception there, the rest of the resources would remain open. How can we improve? The Loaner Pattern Next, we can use the so-called to better manage resources: Loaner pattern [ ](resource: )(handle: => ): = { { handle(resource) } { resource.close() } } withSqsConsumer( {}) { consumer: => consumer.poll } def withSqsConsumer T SqsConsumer SqsConsumer T T try finally new SqsConsumer SqsConsumer The idea is to encapsulate resource management inside a function and delegate its usage to a closure ( ). In other words, to the resource to the handler. handle loan It doesn’t differ much from the raw try-finally approach. However, what’s better is that the caller of withSqsConsumer doesn’t have to care about closing the resource. If we had more resources, we could try to compose them by nesting: withDB( ) { db => withHttpServer( ) { httpServer => println( ) } } new DB new Server "==> Using DB and server" We have a guaranteed ordering when closing resources, which is a reverse order of acquisition. It means that, in this case, we could observe the following order of acquisition/release: Opening DB Opening Http server ==> Using DB and server Closing Http server Closing DB The main drawback is that this approach does not compose well. Imagine how it would look with 10 or more resources. It’s definitely not scalable in terms of code structure, and would probably resemble something like a . callback hell The Loaner pattern addresses some of the flaws we observed with try-catch. But what’s interesting here is that it looks as something we could generalize even more, and if done properly, we could drastically improve composability. Introducing Resource Let’s try to generalize the Loaner pattern to work for all kinds of resources: [ , ](resource: => )(handle: => )(close: => ): = { handle(resource) } { close(resource) } def withResource R T R R T R Unit T try finally What we did here is we introduced the type parameter R, which represents the resource and we extracted the close function. Notice that and nicely describe the whole lifecycle of the value R. resource handle close But… we still didn’t solve the problem of composition! It turns out we can take the above approach, and turn it into a nice abstraction. Meet the Resource: { [ ](f: => ): } { [ ](acquire: => )(close: => ) = [ ] { [ ](f: => ): = { resource = acquire { f(resource) } { close(resource) } } } } [ ] trait Resource R def use U R U U object Resource def make R R R Unit new Resource R override def use U R U U val try finally We can use it in the following way: server = .make( {})(_.close()) server.use { httpServer => } val Resource new HttpServer // ... The resource is meant to be used only inside the use block. Before/after that, it’s closed. If you leak it outside of use, that might throw an exception. Now let’s think about how we could approach the problem of composition. As mentioned in the introduction, we often care about the order of acquisition/release, as resources can have dependencies between each other. That means we’d like to compose our new data type sequentially, and is the essence of… a Monad! sequential composition Creating a Monad[Resource] is not that hard, and we can follow the types here: [ , ](r: [ ])(mapping: => [ ]): [ ] = [ ] { [ ](f: => ): = r.use(res1 => mapping(res1).use(res2 => f(res2))) } [ ](r: => ): [ ] = .make(r)(_ => ()) def flatMap A B Resource A A Resource B Resource B new Resource B override def use U B U U def pure R R Resource R Resource Each flatMap invocation creates a new Resource[B]. To open Resource[B], we have to first open Resource[A] with r.use. As you noticed, this is the essence of the sequential composition. We open A, and only after that we open B. Then, we define what happens after r is opened. We apply the mapping function and follow the types till the end. That’s it! Now we can compose resources sequentially! httpServer = .make( {})(_.close()) mq = .make( {})(_.close()) httpServer.flatMap { server => mq.flatMap { mq => .pure(businessLogic(server, mq)) } } val Resource new HttpServer val Resource new MQ Resource Side note on Monads The Curse of Monad says, that once you get the epiphany, once you understand, you lose the ability to explain it to anybody. That’s probably why so many beginner functional programmers struggle with this concept… while they don’t really have to know anything about Monads in order to write functional programs. To simply put, a Monad is all about running computations in sequence. In Scala, this sequencing is expressed using flatMap operation, or its syntactic sugar: for-comprehension. Option, Either, List, IO, Resource, and many more data types are in fact Monads. def flatMap(fa: F[A])(f: A => F[B]): F[B] says: To get F[B] , first it needs to be returned by f: A => F[B] . But to return it, we need to supply A . And the only place where A comes can be F[A] . Monads come with laws. They’re just to make your life easier and avoid unnecessary surprises. Laws guarantee, that flatMap will behave in the same consistent way across all monads, e.g. Option, Resource, IO, … We can also easily implement Functor[Resource] instance as well: [ ](r: [ ])(mapping: => ): [ ] = [ ] { [ ](f: => ): = r.use(a => f(mapping(a))) } def map B Resource A A B Resource B new Resource B override def use U B U U Functor, like Monad, is also a recurring pattern in functional programming. Functors are all data structures, that you can map. In fact, all Monads, are also Functors, but not the other way around. Voilà, now we have flatMap and map operations defined on the Resource, so we can use for-comprehensions: resources: [( , )] = { db <- dbResource httpServer <- httpServerResource } (httpServer, db) resources.use { (httpServer, db) => println( ) } val Resource HttpServer DB for yield case "Running program, that uses http server and db..." And it works exactly as we wanted: Opening DB Opening http server Running program, that uses http server and db... Closing HTTP Server Closing DB We can use the property, that every Monad is an Applicative, and turn the above example into a nice applicative composition (if resources don’t depend on each other). We’ll see such an example later in this article when we’ll talk about resource management with cats-effect. What we achieved here is: sequential composition an ordered resource release (in reverse order of acquisition) automated resource management closing all resources if our program fails with an exception handling edge situations when closing one of the resources throws an exception. In this case, all other resources are closed, which is good. It turns out, that a similar concept of resource already exists in Haskell and in Scala libraries for some time. Of course, the Resource[R] we designed is very simplistic, just to give you an idea. It’d be hard to use when asynchrony comes into play, hence we can’t really use it with modern effect types like ZIO, Monix Task, or Cats IO. Luckily, all the above effect types have mechanisms to deal with resource management. The good news is as soon as we see them, and we’ll immediately notice some resemblance. Resource management with Cats Effect cats-effect is a library that comes with building blocks for modeling effectful computations. In other words, it allows you to write purely functional programs, that do side-effects like: Communicating with different services over the network.Keeping and modifying its internal state.… cats-effect consists of two parts: — a fully composable data type, that allows you to hide ( ) side-effects inside. Think of it as an alternative to Monix Task, or ZIO. Think of it as Future, but rather in a wide approximation. cats IO suspend , which describe the shape of an arbitrary effect type. What it means is that you can write programs or libraries , and later supply other effect types (e.g. Cats IO, Monix Task, ZIO, …). type classes in terms of them compatible¹ [1] cats-effect the ones for which type class instances are defined. defines it in the following way: Daniel Spiewak Slide from the recent talk Cats Effect 3: What, When, Why presented during Scala Love conference cats-effect comes with two handy mechanisms of dealing with resources: and . bracket resource But for now, let’s see how we’d manage resources without them, and what could be the potential problems. By default, we can just treat resources any other kind of value: { println( ) : = println( ) : = println( ) } : [ ] = { client <- ( ) _ <- businessLogic(client) _ <- (client.close) } () (client: ): [ ] = { _ <- (client.use) } () class SomeAwsSdkJavaClient "Opening connections" def use Unit "Using" def close Unit "Closing connections" // we should probably use Blocker here, but let's forget about that detail for now def program IO Unit for IO new SomeAwsSdkJavaClient IO yield def businessLogic SomeAwsSdkJavaClient IO Unit for IO yield and this could work fine: Opening connections Using Closing connections Now let’s modify the business logic to raise an unexpected error: (client: ): [ ] = { _ <- .raiseError( ( )) _ <- (client.use) } () def businessLogic SomeAwsSdkJavaClient IO Unit for IO new RuntimeException "boom" IO yield As a result, we are not closing the resource: Opening connections java.lang.RuntimeException: boom at com.example.catsexamples$.businessLogic(catsexamples.scala: ) ... 22 Ok, you might say we can just attempt , and then rethrow again after we close the resource: : [ ] = { client <- ( ) e <- businessLogic(client).attempt _ <- (client.close) _ <- .fromEither(e) } () def program IO Unit for IO new SomeAwsSdkJavaClient IO IO yield And there we go, the program explodes and the resource is closed: Opening connections Closing connections java.lang.RuntimeException: boom But... there is one detail we missed. IO is cancellable and that means we can stop it in the middle of execution. In this case, we’d also want to, no matter what, release all the resources involved. We can demonstrate it in the following way: (args: [ ]): [ ] = program.timeout( seconds).map(_ => . ) : [ ] = { client <- ( ) _ <- .sleep( seconds) e <- businessLogic(client).attempt _ <- (client.close) _ <- .fromEither(e) } () override def run List String IO ExitCode 2. ExitCode Success def program IO Unit for IO new SomeAwsSdkJavaClient IO 5. IO IO yield and what we get is: Opening connections java.util.concurrent.TimeoutException: seconds at cats.effect.IO.timeout(IO.scala: ) 2 452 As you can see, we do not release any resources if IO gets canceled. For that, we’d probably need something more advanced. We could obviously experiment with using timeoutTo, and falling back another IO that does the cleanup, but this is not where we want to go. Bracket Meet the . Bracket , and is a type class in cats-effect dedicated to safe and automatic resource management: bracket comes from Haskell { [ , ](acquire: [ ])(use: => [ ]) (release: => [ ]): [ ] } [ [_], ] [ , ] trait Bracket F E extends MonadError F E def bracket A B F A A F B A F Unit F B is: What the docs say is an extension of exposing the operation, a generalized abstracted pattern of safe resource acquisition and release in the face of errors or interruption. Bracket MonadError bracket That sounds like a solution to the problem with errors and cancellations mentioned above. Let’s see it in action: : [ ] = ( ) .bracket { client => businessLogic(client) }(client => (client.close)) def program IO Unit IO new SomeAwsSdkJavaClient IO Nice! We just squashed a bunch of lines into just a single construct, gaining full safety along the way! When business logic raises an error, then we’re covered: ( {}) .bracket { _ => .raiseError( ( )) *> .unit }(x => (x.close())) IO new HttpServer IO new RuntimeException "boom" IO IO // Output: // Opening Http server // Closing Http server // java.lang.RuntimeException: boom // at com.example.Main3$.$anonfun$run$10(Main.scala:211) The same applies to cancellations: ( {}) .bracket(_ => .sleep( seconds))(x => (x.close())) .map(_ => . ) .timeout( second) IO new HttpServer IO 5. IO ExitCode Success 1. // Output: // Opening Http server // Closing Http server // java.util.concurrent.TimeoutException: 1 second // at cats.effect.IO.timeout(IO.scala:452) Now let’s add more resources into play, and let’s see how the code would scale: : [ ] = ( ) .bracket { dynamoClient => ( ) .bracket { sqsClient => businessLogic(sqsClient, dynamoClient) }(sqsClient => (sqsClient.close)) }(dynamoClient => (dynamoClient.close)) def program IO Unit IO new Dynamo IO new Sqs IO IO It turns out that with Bracket we stumble upon the same problem as with the Loaner pattern. Our code becomes unmanageable with more resources due to the lack of composition. Although we have fully automated and safe resource management, we can’t really compose it nicely. And this is where another abstraction comes into play, the Resource. Note: I’m not saying that using Bracket is wrong. It provides safe resource management when working with IO and is fairly simple. It might work nicely when working a single resource, but with more of them, it might be just cumbersome to use. Resource Meet the . This is a data structure we already discovered before. You already know that Resource forms a Monad, hence it can be composed sequentially. And with this property, we can nicely structure our code no matter how it grows! Resource The definition of Resource in cats-effect is just slightly different than ours. In cats-effect world, everything revolves around IO, and corresponding type classes. The same applies for Resource: { [ ](f: => [ ])( : [ , ]): [ ] } { [ [_], ](open: [ ])(close: => [ ]): [ , ] } abstract [ [_], ] class Resource F A def use B A F B implicit F Bracket F Throwable F B object Resource def make F A F A A F Unit Resource F A The structure resembles the Resource we derived in the former part of the article, but this one requires F[_], which denotes the effect type. That’s because Resource in cats-effect is meant to be used with different effect types (Cats IO, ZIO etc.). Let’s see how to initialise multiple resources: (dynamo: , sqs: ): [ ] = { _ <- (dynamo.use) _ <- (sqs.use) } () : [ , ] = { dynamo <- .make( ( ))(r => (r.close())) sqs <- .make( ( ))(r => (r.close())) _ <- .liftF(businessLogic(dynamo, sqs)) } () def businessLogic Dynamo Sqs IO Unit for IO IO yield def program Resource IO Unit for Resource IO new Dynamo IO Resource IO new Sqs IO Resource yield and it works fine. What’s interesting is that now the whole program is in the Resource context, and not IO. That’s why we used liftF to our business logic into Resource[F[_], A]. This shouldn’t be a surprise for a cats user. lift Now, if the business logic raises an exception: (dynamo: , sqs: ): [ ] = { _ <- .raiseError( ( )) _ <- (dynamo.use) _ <- (sqs.use) } () def businessLogic Dynamo Sqs IO Unit for IO new RuntimeException "boom" IO IO yield Then all resources are closed: Opening dynamo connections Opening sqs connections Closing sqs connections Closing dynamo connections java.lang.RuntimeException: boom at com.example.catsexamples$.businessLogic(catsexamples.scala: ) 47 On the other hand, if one of the release operations raises an exception: = { s3 <- .make( ( ))(r => (r.close())) dynamo <- .make( ( ))(r => .raiseError( ( ))) sqs <- .make( ( ))(r => (r.close())) _ <- .liftF(businessLogic(dynamo, sqs, s3)) } () def program for Resource IO new S3 IO Resource IO new Dynamo IO new RuntimeException "boom" Resource IO new Sqs IO Resource yield Then the rest would be closed normally: Opening S3 connections Opening dynamo connections Opening sqs connections Using dynamo Using sqs Using S3 Closing sqs connections Closing S3 connections java.lang.RuntimeException: boom at com.example.catsexamples$.$anonfun$program4$ (catsexamples.scala: ) 6 55 One last thing. What if you have a bunch of resources you’d like to compose? Again, it’s not a problem for Resource. You can just use applicative composition: independent (s3Res, dynamoRes).tupled.use { (s3, dynamo) => } case // ... That’s it! Resource gives us extra safety, composes nicely, and does all of that automatically. I encourage you to look into the . For example, you can find many useful constructors, like fromAutoCloseable , or fromAutoCloseableBlocking that might help when dealing with Java APIs. Resource source code Bonus: bootstrapping a microservices using Resource Below you can find a bigger example of the Resource being used while bootstrapping a microservice application, that uses cats-effect, http4s, and Doobie. The full runnable version . can be found here { [ [_]: : : ]: [ , [ ]] = { config <- .liftF(parser.decodePathF[ , ]( )) blocker <- .apply[ ] kinesis <- .fromAutoCloseable( .builder().build().pure[ ]) xa <- .pure[ , [ ]]( .fromDriverManager[ ]( , , blocker) ) orderRepo = [ ](xa) userRepo = [ ](xa) orderService = [ ](orderRepo) userService = [ ](userRepo, kinesis) httpApp = ( -> .endpoints[ ](userService), -> .endpoints[ ](orderService) ).orNotFound server <- [ ] .bindHttp(config.server.port, config.server.host) .withHttpApp(httpApp) .resource } server (args: [ ]): [ ] = createMicroservice.use(_ => .never).as( . ) } object Microservice extends IOApp def createMicroservice F ContextShift ConcurrentEffect Timer Resource F Server F for Resource F AppConfig "app" Blocker F Resource KinesisClient F Resource F Transactor F Transactor F "" "" OrderRepository F UserRepository F OrderService F UserService F Router "/users" UserEndpoints F "/orders" OrderEndpoints F BlazeServerBuilder F yield def run List String IO ExitCode IO ExitCode Success Summary We started with the simplest approach, which is try-finally, and iteratively improved until discovering the Resource. We learned that it’s a data structure that guarantees safety, proper release order, and also composes very well. In the end, we saw how to deal with resources using Bracket and Resource in purely functional programs using cats-effect. We noticed that those mechanisms are very reliable and they met our initial expectations: Sequential composition. Automatic release in reverse order of acquisition. Closing all resources automatically no matter whether the program completes with success, raises with an exception, or is canceled. Handling edge situations when closing one of the resources out of many raises an exception. Thanks for reading and happy coding! Further reading I encourage you to look into the . You can find there other useful operations like guarantee, or onCancel. Bracket sources The same applies to ! You can find many useful constructors, like fromAutoCloseable , or fromAutoCloseableBlocking that might help when dealing with blocking Java APIs. Resource Github repo with runnable code snippets from this post. on resource management. cats-effect documentation The upcoming Cats Effect 3 will introduce changes in resource management. Recordings from Scala Love and Daniel’s talk about Cats Effect 3 . will pop up here someday Special thanks @sinisalouc for proofreading and making this article better. Previously published at https://medium.com/@bszwej/composable-resource-management-in-scala-ce902bda48b2