Skip to content

@adamgfraser adamgfraser released this Mar 3, 2020 · 145 commits to master since this release

ZIO 1.0.0-RC18 is the last expected release candidate for ZIO 1.0. There are no breaking changes between RC18 and 1.0, but no guarantees. The current plan is to release 1.0 within a few weeks after RC18, to ensure production-worthiness of 1.0.

Note: All deprecated methods will be deleted for ZIO 1.0.

ZIO Environment

Previously, the official recommended way to use ZIO Environment was to assemble pieces using inheritance to create larger environments out of smaller environments.

In the absence of proxies, this led to some pain around construction of the environment, because creating one class from other classes is not done using value-based operators, but inheritance-specific language features that do not permit abstraction.

Moreover, this approach had several drawbacks:

  • Providing just part of an environment was difficult, and was not possible to do generically (the remaining part of the environment had to be known statically).
  • It was not possible to dynamically update parts of the environment inside scoped regions, which led to pain when trying to customize services in parts of the application.

Finally, the principal way to make a first service depend on a second service was to list a reference to the second service inside the first (recalling the Cake Pattern self-type). This led to a pattern whereby business logic would express its dependencies by using ZIO Environment, but all other parts of the application would express their dependencies using fields.

These problems have been solved in RC18 with the introduction of two new data types: Has and ZLayer. Together they have led to a new pattern of encoding environmental dependencies, which has been rolled out in ZIO Core services (Clock, Console, Random, System).

While it is still technically possible to use the older pattern, the official ZIO ecosystem (all projects in the ZIO organization on Github) will be migrated to the new pattern.

Has

The new recommended way to assemble environments involves using a new structure called Has. The Has data type has operators for building bigger environments out of smaller environments, including add, which adds a service to a Has, and concat, which merges two Has into one.

ZLayer

ZLayer is a value that represents a recipe for constructing some services in terms of other services. It is similar to a constructor in a Java or Scala application, which takes the services it depends on, and returns the service it constructs (constructor-based dependency injection). Unlike constructors, however, ZLayers are first-class values that compose type-safely in several ways, and they can construct many services, not just one. Additionally, they can describe the effectful and resourceful construction, such as connecting to a database, creating a database thread pool, and when the service is no longer required, freeing the thread pool, and disconnecting from the database.

ZLayer represents the most power you could ever want in describing the construction of a service. In the end, almost all applications need something with this power (as witnessed by the fact that, generally, users of previous ZIO versions resorted to building the environment using ZManaged).

ZLayers can be constructed using a variety of constructors in the companion object of ZLayer. They can be composed horizontally (when one service depends on another), and vertically (for two independent services). Services that are repeated in the graph are automatically shared, to avoid duplicate construction and release. Further, services that can be constructed in parallel are automatically constructed in parallel, to optimize service construction time.

The general pattern of ZIO Environment is now as follows:

type UserRepo = Has[UserRepo.Service]
object UserRepo {
  trait Service { def getUserById(id: Id): Task[User] }

  def getUserById(id: Id): RIO[UserRepo, User] = ZIO.accessM(_.get.getUserById(id))
}

Then typically, a live value is placed in the companion object of the service, which uses ZLayer to construct a production version of the service in terms of its dependencies:

val live: ZLayer[Database, Nothing, UserRepo] = ???

Services may be provided to effects that need them using the ZIO#provideLayer method, e.g. myEffect.provideLayer(UserRepo.live).

ZIO Runtime System

Weak automatic supervision

Supervision is now backed by weak sets, which means that child fibers may be freely garbage collected when they cannot resume. This should address memory leaks in scenarios where large numbers of non-terminating fibers are created.

Structured concurrency

All child fibers are automatically bound to their parent fiber. When the parent fiber exits, the child fiber will be interrupted or disowned, as determined by the SuperviseMode it is forked with. The default supervision mode for forked fibers is interruption, which means that by default, when a parent fiber exits, its child fibers will be interrupted. This ensures fibers do not leak.

In order to recapture the previous behavior, one may use forkDaemon, or disown, which explicitly disowns a child fiber and moves it to a root set of fibers.

Safer race

Race no longer forces either side to be interruptible. Users may have to perform left.interruptible.race(right.interruptible) to acquire the old behavior (which punched holes in uninterruptible regions); or possibly, left.disconnect.race(right.disconnect) (if they want the effects to be keep running when interrupted, but in the background).

New combinators

Several new combinators are added:
disconnect — disconnects interruption of the effect from its parent. This allows "early interruption" without waiting for finalization. It replaces the need for all xyzFork variants, which are now deprecated. It's quite useful with race, with or without interruptible.
disown — Called by a parent fiber to disown a child fiber. Relocates the child to the root set.

New ZIO.never

When ZIO.never is forked, it will be garbage collected, so ZIO.never.onInterrupt(putStrLn("Bye")) will never execute the finalizer. This can be inconvenient for testing, so ZIO.infinity is added which is like never, but won't be garbage collected.

Deletion of Daemon Mode

Daemon mode has been deleted because in practice, code never knows whether grandchildren should be daemons, only whether immediate children should be daemons; and generally, daemon mode is a one-off for a specific forked fiber.

In place of daemon mode, effect.forkDaemon can be used, which is the composition of two other operators: ordinary fork, followed by an immediate disown of the child fiber.

Deprecation of *Fork Variations

All xyzFork variants are deprecated or removed. For example, bracketFork. This is thanks to the new disconnect operator which allows for a much more compositional approach to solving this problem. Now if you don't want interruption of something to wait around for completion, just use effect.disconnect.

Performance enhancements for blocking effects

Blocking effects has seen several enhancement regarding performance, with potential change in semantic:

  • Now, calling lock on a fiber first checks to see if the fiber is already on the correct Executor and does nothing if in that case;
  • effectBlocking doesn’t interrupt the underlying thread anymore in case of fiber interruption. If you want to revert to previous behavior, use effectBlockingInterrupt.
  • Blocking executor ergonomics were updated to enhance thread reuse and limit risk to crash the server in case of massive thread leak.

ZIO General API

Laziness

Effect constructors are now lazy. This is done to ensure that if users embed side-effects in unexpected places, then errors are managed by ZIO.

Deprecated traverse/sequence

Rather than have duplicate names for operators, the decision was made to deprecate the "classic names" for traverse/sequence, in favor of friendlier and more descriptive names foreach/collectAll.

ZIO Test

Inheritance Based Specs

Spes are now defined by inheriting from DefaultRunnableSpec instead of using constructor arguments:

object ExampleSpec extends DefaultRunnableSpec {
  
  def spec = suite(“ExampleSpec)(
    test(“addition works”) {
      assert(1 + 1)(equalTo(2))
    }
  )
}

This provides a more natural syntax for writing tests and allows defining data or helper methods in the same object instead of requiring a separate utility object.

Type Safe Equality

To provide increased type safety, the syntax for assertions has changed from assert(1 + 1, equalTo(2)) to assert(1 + 1)(equalTo(2)). This curried syntax allows the test framework to issue a compilation error if an equality assertion is comparing two unrelated types, catching bugs earlier. A ScalaFix migration rule is provided to automatically rewrite assertions to the new curried syntax.

Improved Test Console Behavior

To facilitate debugging, by default the TestConsole will now render output to the standard output in addition to writing it to the output buffer. This feature is configurable on a scoped basis using the debug and silent methods on TestConsole or the corresponding test aspects.

Test Annotations

Test annotations provide flexible information for reporting metadata about tests. ZIO Test ships with a variety of test annotations that will automatically track metrics such as the number of ignored tests, the number of times a test was retried using flaky and the number of times a test was repeated using nonFlaky. Users can apply additional annotations using test aspects, for example timing the execution of tests and showing the slowest tests using timed or labeling tests with tag.

In the future test annotations will be the basis for additional structured test reporting to support rich analysis of test results both across suites and over time.

Spy Mocking Functionality

The mock package contains new functionality for spy mocks in the Spyable trait. Spies have the ability to wrap an existing service and capture all method calls to the service being spied on as well as inputs and outputs, making it easy to verify the behavior of elements of complex systems. See the Scaladoc for additional information on this new feature.

Providing The Environment

The functionality provided by layers discussed above makes it much for users to provide tests with additional environmental requirements. In particular, the provideCustomLayer and provideCustomLayerShared methods make it extremely easy to add an additional dependency to the TestEnvironment, which previously required significant boilerplate.

New Aspects

A variety of new test aspects have been added to provide more flexibility than ever in modifying test execution:

  • debug — Renders TestConsole output to standard output for the scope of a test.
  • diagnose — Runs a test on its own fiber and performs a fiber dump of the test fiber and all of its children if the test does not complete within the specified time. This can be extremely useful for diagnosing concurrency bugs.
  • forked — Runs each test on its own fiber.
  • noDelay — Automatically runs all effects involving the TestClock without it needing to be manually adjusted.
  • nonTermination — Tests that an effect does not terminate within a specified time.
  • nondeterministic — Runs each test with a random seed generated from the system time.
  • setSeed — Sets the random seed for a test. This is useful to deterministically "replay" tests.
  • silent — Prevents TestConsole output from being rendered to standard output for the scope of a test.
  • tag — labels tests with the specified tag. Tests can be filtered based on tags and tags will be displayed along with test output.
  • verify — Verifies that a postcondition holds after each test.

Additional Generator Combinators

A variety of additional combinators have been provided for composing generators. In particular, the zip variants allow composing multiple generators in parallel when the generators do not depend on each other. This allows for more efficient shrinking and can be particularly good for large case classes or similar data structure. Additional generators are also provided for types such as java.util.UUID.

JUnit integration*

A custom JUnit runner is provided for running ZIO Test specs under other build tools (like Maven, Gradle, Bazel, etc.) and under IDEs.

To get the runner, add the equivalent of following dependency definition under your build tool:

libraryDependencies ++= Seq(
"dev.zio" %% "zio-test-junit"   % zioVersion % "test"
)

To make your spec appear as a JUnit test to build tools and IDEs, convert it to a class and annotate it with @RunWith(classOf[zio.test.junit.ZTestJUnitRunner]) or simply extend zio.test.junit.JUnitRunnableSpec.

Automatic Derivation of Generators Powered By Magnolia

Automatic derivation of generators for case classes and sealed traits is now available powered by Magnolia. To get it, add the following to your dependencies:

libraryDependencies ++= Seq(
  “dev.zio” %% “zio-test-magnolia” %% zioVersion % “test
)

You can then automatically derive generators for your own data types:

final case class Person(name: String, age: Int)

val genPerson: Gen[Random with Sized, Person] = DeriveGen[Person]

sealed trait Color
case object Red   extends Color
case object Green extends Color
case object Blue  extends Color

val genColor: Gen[Random with Sized, Color] = DeriveGen[Color]

Experimental Polymorphic Generators

ZIO Test now includes experimental support for polymorphic generators. These generators do not require the user to specify a particular type but instead generate random data from a variety of types that satisfy specified constraints, such as having an Ordering. This can be useful for testing highly polymorphic code in a generic way. See the examples in the zio.test.poly package and please provide feedback on this feature.

ZIO Streams

ZIO Streams includes a number of new combinators, which can be explored through the Scaladoc, but no significant breaking changes were introduced.

ZIO STM

ZIO STM has undergone a large amount of development work, adding more power, more safety, and more structures.

More Methods

STM now features many more methods, which mirror those on ZIO structures; and STM structures like TArray now have many more useful methods on them.

Stack Safety

STM is now stack safe and can handle transactions of any size without stack overflows. This is achieved not with trampolining but by using exceptions for control-flow. There is a performance hit but it is minimal for the added stack safety.

Environment

STM itself has been generalized to ZSTM, permitting use of ZIO Environment. This is useful to embed STM structures directly into the environment, which allows transactions to operate against application-wide data structures, such as caches, counters, and the like.

New Structures

  • TMap
  • TQueue
  • TReentrantLock

Contributors

There were a total of 67 contributors to RC18.

Thank you to Adam Fraser, Ajay Chandran, Alex Savin, Alexander van Olst, Bojan Babić, Bojan Blagojević, Boris V.Kuznetsov, Chris Andre, dariusrobson, Dejan Mijić, Dmitry Karlinsky, Dragutin Marjanović, Evgeny Veretennikov, Fabio Serragnoli, felher, Ferdinand Svehla, Greg Holland, Ievgen Garkusha, Igal Tabachnik, ioleo, Isaias Bartelborth, Itamar Ravid, Jan Toebes, Jens Hoffmann, John A. De Goes, Jonathan Winandy, Kai, Koleman Nix, Laurynas Lubys, Luis Miguel Mejía Suárez, Marek Kadek, Mateusz Sokół‚, Matthias Langer, Maxim Davydov, Maxim Schuwalow, montrivo, Nadav Samet, Oleksandra Holubitska, Oliver Wickham, Pascal Mengelt, Pavel Shirshov, Pavels Sisojevs, Paweł Kiersznowski, peterlopen, Philippe Derome, Pierangelo Cecchetto, Pierre Ricadat, Rafael Saraiva Figueiredo, Regis Kuckaertz, reibitto, Richard Whaling, Roberto Leibman, Salar Rahmanian, Sergey Rublev, simpadjo, sken, svroonland, TapanVaishnav, Tibor Erdesz, Unclebob, Vasil Vasilev, Vasiliy Levykin, Vitalii, Wiem Zine El Abidine, wongelz, and Zachary Albia for your contributions to ZIO.

It is our incredible community of users and contributors that make ZIO possible.

Assets 2
You can’t perform that action at this time.