Skip to main content
Version: 2.x

Decrel

Decrel is a Scala library for declarative programming using relations between our data. It fetches data with automatic batching, parallelization, and caching while keeping the business logic clean and readable. It runs on ZIO through ZIO Query, and on Cats Effect through Fetch.

Introductionโ€‹

Fetching data from datasources is usually done by calling repository methods one after another. This works, but it hides a few issues: independent fetches run sequentially, fetching many items leads to the N+1 query problem, and caching needs extra code for every operation.

With Decrel, we:

  • Declare Relations โ€” Express the relationships between our data models as first-class values, for example "a Book has one Author" or "a User may have a PremiumSubscription".
  • Implement Datasources โ€” Decide how each relation is fulfilled, including batching and caching.
  • Compose Relations โ€” Combine simple relations sequentially (<>:) or in parallel (&) to express complex access patterns.
  • Execute Efficiently โ€” The composed relations run with batching and parallelism by default, through ZIO Query.
  • Generate Test Data โ€” The same relations can generate random data for ZIO Test and ScalaCheck.

Installationโ€‹

In order to use this library with ZIO, we need to add the following lines to our build.sbt file:

libraryDependencies += "com.yoohaemin" %% "decrel-zquery" % "0.2.0"
libraryDependencies += "com.yoohaemin" %% "decrel-ziotest" % "0.2.0" % Test

The decrel-fetch module is for Cats Effect users, and decrel-scalacheck generates test data for ScalaCheck.

Exampleโ€‹

First, we declare the relations between the data models:

object Book {
object author extends Relation.Single[Book, Author]
}

object User {
object subscription extends Relation.Optional[User, PremiumSubscription]
}

Then we implement the datasource of each relation. The implementation receives all the inputs at once, so it can batch them:

implementSingleDatasource(Book.author) { books =>
ZIO.succeed(books.map(book => book -> authorMap(book.authorId)))
}

Finally, we compose the relations and run them. The author and the price are fetched in parallel:

val bookId: Book.Id = ???

for {
(book: Book, author: Author, price: Price) <-
(Book.Self & Book.author & Book.price).toZIO(bookId)
} yield ()

To fetch the details of many books, we use toZIOMany. The underlying calls are batched, so the number of calls to each datasource doesn't grow with the number of books:

val bookIds: List[Book.Id] = ???

for {
bookDetails: List[(Book, Author, Price)] <-
(Book.Self & Book.author & Book.price).toZIOMany(bookIds)
} yield ()

For the full documentation, see the Decrel documentation.