Event migration improvements (#29514)

This commit is contained in:
Ignasi Marimon-Clos 2020-08-25 11:10:33 +02:00 committed by GitHub
parent 57fb9e9093
commit 728dda874e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 651 additions and 63 deletions

View file

@ -224,7 +224,7 @@ needs to have an associated code which indicates if it is a window or aisle seat
Adding fields is the most common change you'll need to apply to your messages so make sure the serialization format
you picked for your payloads can handle it apropriately, i.e. such changes should be *binary compatible*.
This is achieved using the right serializer toolkit. In the following examples we will be using protobuf.
See also @ref:[how to add fields with Jackson](serialization-jackson.md#add-field).
See also @ref:[how to add fields with Jackson](serialization-jackson.md#add-optional-field).
While being able to read messages with missing fields is half of the solution, you also need to deal with the missing
values somehow. This is usually modeled as some kind of default value, or by representing the field as an @scala[`Option[T]`]@java[`Optional<T>`]

View file

@ -205,7 +205,7 @@ We will look at a few scenarios of how the classes may be evolved.
Removing a field can be done without any migration code. The Jackson serializer will ignore properties that does
not exist in the class.
### Add Field
### Add Optional Field
Adding an optional field can be done without any migration code. The default value will be @scala[None]@java[`Optional.empty`].
@ -226,6 +226,8 @@ Scala
Java
: @@snip [ItemAdded.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v2a/ItemAdded.java) { #add-optional }
### Add Mandatory Field
Let's say we want to have a mandatory `discount` property without default value instead:
Scala
@ -361,6 +363,63 @@ binding, but it should still be possible to deserialize old data with Jackson.
It's a list of class names or prefixes of class names.
## Rolling updates
When doing a rolling update, for a period of time there are two different binaries running in production. If the schema
has evolved requiring a new schema version, the data serialized by the new binary will be unreadable from the old
binary. This situation causes transient errors on the processes running the old binary. This service degradation is
usually fine since the rolling update will eventually complete and all old processes will be replaced with the new
binary. To avoid this service degradation you can also use forward-one support in your schema evolutions.
To complete a no-degradation rolling update, you need to make two deployments. First, deploy a new binary which can read
the new schema but still uses the old schema. Then, deploy a second binary which serializes data using the new schema
and drops the downcasting code from the migration.
Let's take, for example, the case above where we [renamed a field](#rename-field).
The starting schema is:
Scala
: @@snip [ItemAdded.java](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/v1/ItemAdded.scala) { #add-optional }
Java
: @@snip [ItemAdded.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v1/ItemAdded.java) { #add-optional }
In a first deployment, we still don't make any change to the event class:
Scala
: @@snip [ItemAdded.scala](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/v1/ItemAdded.scala) { #forward-one-rename }
Java
: @@snip [ItemAdded.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v1/ItemAdded.java) { #forward-one-rename }
but we introduce a migration that can read the newer schema which is versioned `2`:
Scala
: @@snip [ItemAddedMigration.scala](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/v1withv2/ItemAddedMigration.scala) { #forward-one-rename }
Java
: @@snip [ItemAddedMigration.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v1withv2/ItemAddedMigration.java) { #forward-one-rename }
Once all running nodes have the new migration code which can read version `2` of `ItemAdded` we can proceed with the
second step. So, we deploy the updated event:
Scala
: @@snip [ItemAdded.scala](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/v2c/ItemAdded.scala) { #rename }
Java
: @@snip [ItemAdded.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v2c/ItemAdded.java) { #rename }
and the final migration code which no longer needs forward-compatibility code:
Scala
: @@snip [ItemAddedMigration.scala](/akka-serialization-jackson/src/test/scala/doc/akka/serialization/jackson/v2c/ItemAddedMigration.scala) { #rename }
Java
: @@snip [ItemAddedMigration.java](/akka-serialization-jackson/src/test/java/jdoc/akka/serialization/jackson/v2c/ItemAddedMigration.java) { #rename }
## Jackson Modules
The following Jackson modules are enabled by default: