Road to ZIO[n]: Chapter 4, the Persistence layer

Manuel Rodríguez
4 min readApr 7, 2022

--

Clean and pure meditation without a doubt
Don’t mek dem take you like who dem took out

Damian Marley, Road to Zion

In today’s chapter of Road To ZIO[n] we’ll take a deep look at how the Persistence layer is implemented. This layer is in charge of the communication between the Domain (pure functional layer with the business logic) and the dirty world of databases. We will be using an in-memory DB called H2 in here.

Disclaimer. As in the rest of the series this article can be regarded as the continuation of THIS BLOG POST which serves as the inspiration of this project. The full code is stored in THIS GITHUB REPO.

Also it looks apparently simple, this layer has a lot of hidden stuff. So what I’ll be doing here is to slowly analyze the code and try to untangle everything.

Libraries

We will be using DOOBIE and H2. These are imported with:

Data types

In Road To ZIO[n]: chapter 2, Types in the Domain layer we defined a Data Type for the User in the domain layer. However here we want to have our own types to be stored in the DataBase. This way we are not coupling both layers, and depending on the DB employed we can evolve the Type in the future without affecting the domain.

This Type can be seen as redundant -and with this size of application it is- but in my experience having decoupled the Domain and Storage layers is a very good practice that pays off in the long term.

This is done with this class + Companion object to provide basic functionality.

In the repo I’ve included some basic tests for this, but I’ll not be stopping with them as they are trivial.

Persistence

This is mostly taken from the original post for this work. Make sure to check it out if this explanation is not completely clear.

Note that these are fragments of code. The full class is in the GitHub repo.

SQL

First we’ll create the SQL queries that we’ll be needing. Note that with DOOBIE we are able to use the input parameters in the SQL queries and to transform the result of the queries back into Scala case classes.

Persistence class with business logic

What we want to do here is to have a class that implements the Port from the Domain layer and performs the desired DB operations. This is pretty straightforward and looks like this:

The key thing is that this class receives a transactor as a configuration parameter. This transactor will wrap the connection pool provided by our database that we want to choose, which in our example it would be h2. After that we take the output and transform into the target effect type that will take the effectful computation, being IO[AppError, Whatever] here.

Transactor

We need to build a transactor. It will be in charge of reserving the transaction, use it in the application and when the application is closed, cleaning up the resource.

In ZIO there is a data type called zio.Managed that describes a managed resource, the resource will be acquired before it is used and automatically released after using it.

Altogether, and after adding some boilerplate, the result looks like

ZLayer

Now we are creating the ZLayer that allows to use our DoobiePersistenceService in our application. This is basically more wiring and looks like

let’s go line by line:

  • L1: this is going to be our result: a ZLayer that gets a DBTransactor (which encapsulates the DB) and returns a UserPersistence layer.
  • L4: requirements to create the transactor: config for the database and Blocking (blocking execution context used to run the DB stuff)
  • L6–10: wiring

I would not however invest much time in understanding this section nor the previous one, as Managed is going to change in ZIO 2 and this is all supposed to be easier and cleaner.

Testing

Finally we can test all this!

This is pretty similar to what we did in the Domain layer, so I will not stop too much on it. There are however a couple important things that I want to mention.

L1: standard execution

L3: I want my tests to be independent from each other, so the DB has to be recreated on each one. In ScalaTest this is a perfect example of the “beforeEach” function, but as there doesn’t seem to be one in ZIO test I implemented the functionality manually.

L23: I lost a couple hours with this. Tests in ZIO are run in parallel by default, so be careful if you have dependencies between them. In this case we have the DB dependency, so running in parallel meant ugly interactions and flaky tests. Running them sequentially ensures no race conditions/resource sharing.

Future Steps

In a next step I want to set up restrictions on the DB: number of elements, size and so. I am curious to see how this affects the rest of the system, making a domain and application layer that can deal gracefully with errors on the DB.

--

--

Manuel Rodríguez

Developer at New Relic. This is where I keep the cool stuff that I learn in my free time