Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 198 additions & 2 deletions docs/datatypes/resource/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,202 @@
---
id: index
title: "Summary"
title: "Introduction"
---

- **[Managed](managed.md)** — A `Managed` is a value that describes a perishable resource that may be consumed only once inside a given scope.
When we are writing a long-lived application, resource management is very important. Proper resource management is vital to any large-scale application. We need to make sure that our application is resource-safe, and it doesn't leak any resource.

Leaking socket connections, database connections or file descriptors is not acceptable in a web application. ZIO provides some good construct to make sure about this concern.

To write a resource-safe application, we need to make sure whenever we are opening a resource, we have a mechanism to close that resource whether we use that resource completely or not, for example, an exception occurred during resource usage.

## Try / Finally
Before we dive into the ZIO solution, it's better to review the `try` / `finally` which is the standard approach in the Scala language to manage resources.

Scala has a `try` / `finally` construct which helps us to make sure we don't leak resources because no matter what happens in the try, the `finally` block will be executed. So we can open files in the try block, and then we can close them in the `finally` block, and that gives us the guarantee that we will not leak resources.

Assume we want to read a file and return the number of its lines:

```scala mdoc:invisible
import java.io._
import zio.{Task, UIO, ZIO, ZManaged}
```

```scala mdoc:silent:nest
def lines(file: String): Task[Long] = Task.effect {
def countLines(br: BufferedReader): Long = br.lines().count()
val bufferedReader = new BufferedReader(
new InputStreamReader(new FileInputStream("file.txt")),
2048
)
val count = countLines(bufferedReader)
bufferedReader.close()
count
}
```

What happens if after opening the file and before closing the file, an exception occurs? So, the `bufferedReader.close()` line, doesn't have a chance to close the resource. This creates a resource leakage. The Scala language has `try...finally` construct, which helps up to prevent these situations.

Let's rewrite the above example with `try..finally`:

```scala mdoc:silent:nest
def lines(file: String): Task[Long] = Task.effect {
def countLines(br: BufferedReader): Long = br.lines().count()
val bufferedReader = new BufferedReader(
new InputStreamReader(new FileInputStream("file.txt")),
2048
)
try countLines(bufferedReader)
finally bufferedReader.close()
}
```

Now, we are sure that if our program is interrupted during the process of a file, the `finally` block will be executed.

The `try` / `finally` solve simple problems, but it has some drawbacks:

1. It's not composable; We can't compose multiple resources together.

2. When we have multiple resources, we end up with messy and ugly code, hard to reason about, and refactoring.
3. We don't have any control over the order of resource clean-up
4. It only helps us to handle resources sequentially. It can't compose multiple resources, concurrently.
5. It doesn't support asynchronous workflows.
6. It's a manual way of resource management, not automatic. To have a resource-safe application we need to manually check that all resources are managed correctly. This way of resource management is error-prone in case of forgetting to manage resources, correctly.

## ZIO Solution

ZIO's resource management features work across synchronous, asynchronous, concurrent, and other effect types, and provide strong guarantees even in the presence of failure, interruption, or defects in the application.

ZIO has two major mechanisms to manage resources.

### bracket

ZIO generalized the pattern of `try` / `finally` and encoded it in `ZIO.bracket` or `ZIO#bracket` operations.

Every bracket requires three actions:
1. **Acquiring Resource**— An effect describing the acquisition of resource. For example, opening a file.
2. **Using Resource**— An effect describing the actual process to produce a result. For example, counting the number of lines in a file.
3. **Releasing Resource**— An effect describing the final step of releasing or cleaning up the resource. For example, closing a file.

```scala mdoc:invisible
trait Resource
```

```scala mdoc:silent
def use(resource: Resource): Task[Any] = Task.effect(???)
def release(resource: Resource): UIO[Unit] = Task.effectTotal(???)
def acquire: Task[Resource] = Task.effect(???)

val result1: Task[Any] = acquire.bracket(release, use)
val result2: Task[Any] = acquire.bracket(release)(use) // More ergonomic API

val result3: Task[Any] = Task.bracket(acquire, release, use)
val result4: Task[Any] = Task.bracket(acquire)(release)(use) // More ergonomic API
```

The bracket guarantees us that the `acquiring` and `releasing` of a resource will not be interrupted. These two guarantees ensure us that the resource will always be released.

Let's try a real example. We are going to write a function which count line number of given file. As we are working with file resource, we should separate our logic into three part. At the first part, we create a `BufferedReader`. At the second, we count the file lines with given `BufferedReader` resource, and at the end we close that resource:

```scala:mdoc:silent
def lines(file: String): Task[Long] = {
def countLines(reader: BufferedReader): Task[Long] = Task.effect(reader.lines().count())
def releaseReader(reader: BufferedReader): UIO[Unit] = Task.effectTotal(reader.close())
def acquireReader(file: String): Task[BufferedReader] = Task.effect(new BufferedReader(new FileReader(file), 2048))

Task.bracket(acquireReader(file), releaseReader, countLines)
}
```

Let's write another function which copy a file from source to destination file. We can do that by nesting two brackets one for the `FileInputStream` and the other for `FileOutputStream`:

```scala mdoc:silent
def is(file: String): Task[FileInputStream] = Task.effect(???)
def os(file: String): Task[FileOutputStream] = Task.effect(???)

def close(resource: Closeable): UIO[Unit] = Task.effectTotal(???)
def copy(from: FileInputStream, to: FileOutputStream): Task[Unit] = ???

def transfer(src: String, dst: String): ZIO[Any, Throwable, Unit] = {
Task.bracket(is(src))(close) { in =>
Task.bracket(os(dst))(close) { out =>
copy(in, out)
}
}
}
```

As there isn't any dependency between our two resources (`is` and `os`), it doesn't make sense to use nested brackets, so let's `zip` these two acquisition into one effect, and the use one bracket on them:

```scala mdoc:silent:nest
def transfer(src: String, dst: String): ZIO[Any, Throwable, Unit] = {
is(src)
.zipPar(os(dst))
.bracket { case (in, out) =>
Task
.effectTotal(in.close())
.zipPar(Task.effectTotal(out.close()))
} { case (in, out) =>
copy(in, out)
}
}
```

While using bracket is a nice and simple way of managing resources, but there are some cases where a bracket is not the best choice:

1. Bracket is not composable— When we have multiple resources, composing them with a bracket is not straightforward.

2. Messy nested brackets— In the case of multiple resources, nested brackets remind us of callback hell awkwardness. The bracket is designed with nested resource acquisition. In the case of multiple resources, we encounter inefficient nested bracket calls, and it causes refactoring a complicated process.

Using brackets is simple and straightforward, but in the case of multiple resources, it isn't a good player. This is where we need another abstraction to cover these issues.

### ZManaged

`ZManage` is a composable data type for resource management, which wraps the acquisition and release action of a resource. We can think of `ZManage` as a handle with build-in acquisition and release logic.

To create a managed resource, we need to provide `acquire` and `release` action of that resource to the `make` constructor:

```scala mdoc:silent
val managed = ZManaged.make(acquire)(release)
```

We can use managed resources by calling `use` on that. A managed resource is meant to be used only inside of the `use` block. So that resource is not available outside of the `use` block.

The `ZManaged` is a separate world like `ZIO`; In this world, we have a lot of combinators to combine `ZManaged` and create another `ZManaged`. At the end of the day, when our composed `ZManaged` prepared, we can run any effect on this resource and convert that into a `ZIO` world.

Let's try to rewrite a `transfer` example with `ZManaged`:

```scala mdoc:silent:nest
def transfer(from: String, to: String): ZIO[Any, Throwable, Unit] = {
val resource = for {
from <- ZManaged.make(is(from))(close)
to <- ZManaged.make(os(to))(close)
} yield (from, to)

resource.use { case (in, out) =>
copy(in, out)
}
}
```

Also, we can get rid of this ceremony and treat the `Managed` like a `ZIO` effect:

```scala mdoc:silent:nest
def transfer(from: String, to: String): ZIO[Any, Throwable, Unit] = {
val resource: ZManaged[Any, Throwable, Unit] = for {
from <- ZManaged.make(is(from))(close)
to <- ZManaged.make(os(to))(close)
_ <- copy(from, to).toManaged_
} yield ()
resource.useNow
}
```

This is where the `ZManaged` provides us a composable and flexible way of allocating resources. They can be composed with any `ZIO` effect by converting them using the `ZIO#toManaged_` operator.

`ZManaged` has several type aliases, each of which is useful for a specific workflow:

- **[Managed](managed.md)**— `Managed[E, A]` is a type alias for `Managed[Any, E, A]`.
- **[TaskManaged](task-managed.md)**— `TaskManaged[A]` is a type alias for `ZManaged[Any, Throwable, A]`.
- **[RManaged](rmanaged.md)**— `RManaged[R, A]` is a type alias for `ZManaged[R, Throwable, A]`.
- **[UManaged](umanaged.md)**— `UManaged[A]` is a type alias for `ZManaged[Any, Nothing, A]`.
- **[URManaged](urmanaged.md)**— `URManaged[R, A]` is a type alias for `ZManaged[R, Nothing, A]`.
16 changes: 13 additions & 3 deletions docs/datatypes/resource/managed.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ id: managed
title: "Managed"
---

`Managed` is a data structure that encapsulates the acquisition and the release of a resource.
`Managed[E, A]` is a type alias for `Managed[Any, E, A]`, which represents a managed resource that has no requirements, and may fail with an `E`, or succeed with an `A`.

A `Managed[E, A]` is a managed resource of type `A`, which may be used by invoking the `use` method of the resource. The resource will be automatically acquired before the resource is used, and automatically released after the resource is used.
```scala mdoc:invisible
import zio.ZManaged
```

The `Managed` type alias is defined as follows:

```scala mdoc:silent:nest
type Managed[+E, +A] = ZManaged[Any, E, A]
```

`Managed` is a data structure that encapsulates the acquisition and the release of a resource, which may be used by invoking the `use` method of the resource. The resource will be automatically acquired before the resource is used, and automatically released after the resource is used.

Resources do not survive the scope of `use`, meaning that if you attempt to capture the resource, leak it from `use`, and then use it after the resource has been consumed, the resource will not be valid anymore and may fail with some checked error, as per the type of the functions provided by the resource.

Expand Down Expand Up @@ -57,7 +67,7 @@ It is possible to combine multiple `Managed` using `flatMap` to obtain a single
import zio._
```

```scala mdoc:invisible
```scala mdoc:invisible:nest
import java.io.{ File, IOException }

def openFile(s: String): IO[IOException, File] = IO.effect(???).refineToOrDie[IOException]
Expand Down
16 changes: 16 additions & 0 deletions docs/datatypes/resource/rmanaged.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
id: rmanaged
title: "RManaged"
---

`RManaged[R, A]` is a type alias for `ZManaged[R, Throwable, A]`, which represents a managed resource that requires an `R`, and may fail with a `Throwable` value, or succeed with an `A`.

```scala mdoc:invisible
import zio.ZManaged
```

The `RManaged` type alias is defined as follows:

```scala mdoc:silent:nest
type RManaged[-R, +A] = ZManaged[R, Throwable, A]
```
16 changes: 16 additions & 0 deletions docs/datatypes/resource/task-managed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
id: task-managed
title: "TaskManaged"
---

`TaskManaged[A]` is a type alias for `ZManaged[Any, Throwable, A]`, which represents a managed resource that has no requirements, and may fail with a `Throwable` value, or succeed with an `A`.

```scala mdoc:invisible
import zio.ZManaged
```

The `TaskManaged` type alias is defined as follows:

```scala mdoc:silent:nest
type TaskManaged[+A] = ZManaged[Any, Throwable, A]
```
16 changes: 16 additions & 0 deletions docs/datatypes/resource/umanaged.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
id: umanaged
title: "UManaged"
---

`UManaged[A]` is a type alias for `ZManaged[Any, Nothing, A]`, which represents an **unexceptional** managed resource that doesn't require any specific environment, and cannot fail, but can succeed with an `A`.

```scala mdoc:invisible
import zio.ZManaged
```

The `UMManaged` type alias is defined as follows:

```scala mdoc:silent:nest
type UManaged[+A] = ZManaged[Any, Nothing, A]
```
16 changes: 16 additions & 0 deletions docs/datatypes/resource/urmanaged.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
id: urmanaged
title: "URManaged"
---

`URManaged[R, A]` is a type alias for `ZManaged[R, Nothing, A]`, which represents a managed resource that requires an `R`, and cannot fail, but can succeed with an `A`.

```scala mdoc:invisible
import zio.ZManaged
```

The `URManaged` type alias is defined as follows:

```scala mdoc:silent:nest
type URManaged[-R, +A] = ZManaged[R, Nothing, A]
```
Loading