Skip to main content
Version: 2.0.x

Migration from v3.x.x.x

There are some major changes compared to the ZIO 1 version (v3.x.x.x and v4.x.x.x). This section contains detailed information about what changed and how to modify existing code.

New package names

Previously the zio-aws packages had the root package io.github.vigoo.zioaws. By moving to the ZIO organisation this changed to be simply zio.aws.

So imports for something using the EC2 and ElasticBeanstalk APIs would change from:

import io.github.vigoo.zioaws.core._
import io.github.vigoo.zioaws.ec2.Ec2
import io.github.vigoo.zioaws.ec2.model._
import io.github.vigoo.zioaws.elasticbeanstalk.ElasticBeanstalk
import io.github.vigoo.zioaws.elasticbeanstalk.model._

to

import zio.aws.core._
import zio.aws.ec2.Ec2
import zio.aws.ec2.model._
import zio.aws.ec2.model.primitives._
import zio.aws.elasticbeanstalk.ElasticBeanstalk
import zio.aws.elasticbeanstalk.model._
import zio.aws.elasticbeanstalk.model.primitives._

Some other changes to the imports may be necessary because of switching to the new service pattern of ZIO, described below.

New getter names

In previous version of zio-aws the generated models provided to ways to access fields:

  • a ZIO effect that failed if the value is None with the name of the field (for example .instanceId)
  • a simple value accessor returning an Option value with the Value suffix (for example .instanceIdValue)

This has been changed to match the convention used in zio-k8s to the following:

  • the ZIO effect for requiring that a field has value now has the get prefix (for example .getInstanceId)
  • the value with the field name is the simple accessor returning the optional value: (for example .instanceId)

So for example the following code that prints information about EC2 instances:

for {
id <- instance.instanceId
typ <- instance.instanceType
launchTime <- instance.launchTime
_ <- console.putStrLn(s" instance $id:").ignore
_ <- console.putStrLn(s" type: $typ").ignore
_ <- console.putStrLn(s" launched at: $launchTime").ignore
} yield ()

would have to be changed to:

for {
id <- instance.getInstanceId
typ <- instance.getInstanceType
launchTime <- instance.getLaunchTime
_ <- Console.printLine(s" instance $id:").ignore
_ <- Console.printLine(s" type: $typ").ignore
_ <- Console.printLine(s" launched at: $launchTime").ignore
} yield ()

New service pattern

Previous versions were following the ZIO module pattern 1.0, so all the generated AWS services consisted of a type alias using Has, a trait called Service and a package object with the service's name. With ZIO 2 we no longer have Has and the recommended way to structure services was changed to the simple new service pattern, where each service is just a trait and one or more implementation classes, constructed by layers.

In practice this means that:

  • instead of the package object with lower-case name, the accessor functions are now in the service companion object (ElasticBeanstalk.describeApplications instead of elasticbeanstalk.describeApplications)
  • same for the layers, for example Ec2.live instead of ec2.live
  • the service trait has the name of the service (for example Ec2) instead of Service

The new service pattern is also applied to the core services like AwsConfig and the http implementations.

Optional parameters

A very large part of the fields of AWS models are optional. These previously had the type Option, and although for extracting data from them the library already had the generated getters, constructing these data types still required wrapping most of the parameters in Some(...). To reduce this boilerplate zio-aws now uses the Optional type instead of Option, which was first used in zio-k8s. Now the two libraries share the same type which was moved to zio-prelude.

The following example:

kinesis.describeStreamConsumer(
DescribeStreamConsumerRequest(
consumerName = Some(consumerName),
streamARN = Some(streamDescription.streamDescriptionValue.streamARNValue)
)
)

becomes

Kinesis.describeStreamConsumer(
DescribeStreamConsumerRequest(
consumerName = consumerName,
streamARN = streamDescription.streamDescription.streamARN
)
)

Newtypes

Previously zio-aws generated simple Scala type aliases for primitive types in the AWS SDKs. For example the TableName type in zio-aws-dynamodb was just a type alias for String:

package io.github.vigoo.zioaws.dynamodb.model

package object primitives {
type TableName = String
}

The new version uses zio-prelude's newtype wrappers to provide better type safety:

package zio.aws.dynamodb.model

package object primitives {
object TableName extends Subtype[String]
type TableName = TableName.Type
}

In practice this means that we have to explicitly wrap these primitive values, for example instead of:

elasticbeanstalk.describeApplications(
DescribeApplicationsRequest(applicationNames = Some(List("my-service")))
)

now we have to write

ElasticBeanstalk.describeApplications(
DescribeApplicationsRequest(applicationNames = List(ApplicationName("my-service")))
)

and if we need to convert them to the underlying primitive type we need to call unwrap:

ResourceId.unwrap(id)

Aspects

zio-aws introduced AwsCallAspect soon after Adam Fraser's talk but it was a custom implementation, defined as:

trait AwsCallAspect[-R] { self =>
def apply[R1 <: R, A](
f: ZIO[R1, AwsError, Described[A]]
): ZIO[R1, AwsError, Described[A]]

// ...
}

These aspects can be applied to whole zio-aws service layers to add logging, metrics, retries etc for every AWS Java SDK call.

The ZIO 2.0.0 version is now using ZIO's built-in aspect support as a base:

type AwsCallAspect[-R] =
ZIOAspect[Nothing, R, AwsError, AwsError, Nothing, Described[_]]

New built-in aspects

With ZIO 2 we have logging and metrics support built-in, so zio-aws now provides ready to use aspects for logging and monitoring AWS calls:

val callLogging: AwsCallAspect[Any]
def callDuration(prefix: String, boundaries: MetricKeyType.Histogram.Boundaries): AwsCallAspect[Any]

Changes in defining aspects

The following example aspect uses rezilience to add circuit breaking for an AWS service:

def circuitBreaking(cb: CircuitBreaker[AwsError]): AwsCallAspect[Any] =
new AwsCallAspect[Any] {
override final def apply[R1 <: Any, A](
f: ZIO[R1, AwsError, Described[A]]
): ZIO[R1, AwsError, Described[A]] =
cb(f).mapError(policyError =>
AwsError.fromThrowable(policyError.toException)
)
}

because of the changed base type for AwsCallAspect with the new version the same aspect is defined like this:

def circuitBreaking(cb: CircuitBreaker[AwsError]): AwsCallAspect[Any] =
new AwsCallAspect[Any] {
override final def apply[R, E >: AwsError <: AwsError, A <: Described[_]](
f: ZIO[R, E, A]
)(implicit trace: Trace): ZIO[R, E, A] =
cb(f).mapError(policyError =>
AwsError.fromThrowable(policyError.toException)
)
}

The main differences:

  • requiring the implicit Trace
  • the constraints on the error and result types are now expressed as type bounds because the ZIO aspect is more generic

New config library

The 3.x series of zio-aws was using zio-config 1.x. The recently released 4.x series was using zio-config 2.x which is the ZIO 1 version of the config library's new API; The ZIO 2 version of zio-aws uses zio-config 3.x which is the same new config API but for ZIO 2.

This means that coming from zio-aws 3.x requires upgrading to the new config API. For more information about the changes in zio-config, see it's release notes.