-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: JSON schema evolution support #1760
feat: JSON schema evolution support #1760
Conversation
@@ -126,7 +126,7 @@ lazy val javaSdkProtobufTestKit = project | |||
|
|||
lazy val javaSdkSpring = project | |||
.in(file("sdk/java-sdk-spring")) | |||
.dependsOn(javaSdkProtobuf) | |||
.dependsOn(javaSdkProtobuf % "compile->compile;test->test") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm reusing some test classes.
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/JacksonMigration.java
Outdated
Show resolved
Hide resolved
//TODO verify if this could be replaced by sth smarter/safer | ||
reversedCache.compute( | ||
addToReversedCache(clz, typeName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While updating typeHints
map, I'm also updating another map reversedTypeHints
, this feels wrong, but I haven't figured out a better option for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I recall correctly, we register the types when we register the components. This is supposed to be done by a single thread running over all components and finding out the types. In that sense, it's safe to update the main map and then the reversed one.
For good measure, we could synchronize this method to make sure we are safe. Just in case something changes one day.
f99a007
to
634a3b3
Compare
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/JacksonMigration.java
Outdated
Show resolved
Hide resolved
* @return The decoded object | ||
* @throws IllegalArgumentException if the given value cannot be decoded to a T | ||
*/ | ||
public static <T> T decodeJson(Class<T> valueClass, Any any, Optional<? extends JacksonMigration> jacksonMigration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A jacksonMigration
additional param to pass the migration from the upper level, otherwise I would need to move the @ Migration annotation here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make the signature with Optional param internal and just have one overload with migration and one without, for Java?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about this, but then all the invocation would look like:
extractMigration(typeClass)
.map(JsonSupport.decodeJson(typeClass, any, _))
.getOrElse(JsonSupport.decodeJson(typeClass, any))
which is also smelly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some serious doubts about this signature. I want to show serialization test examples for the user. From the test perspective, I need to pass the migration explicitly:
JsonSupport.decodeJson(AddressChanged.class, serialized, Optional.of(new AddressChangedMigration()))
the test can be green, but the in the actual implementation the linking @Migration(AddressChangedMigration.class)
annotation might be missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main question is if we want to support migration for JSON in Proto SDK, like here: https://docs.kalix.io/java-protobuf/actions-publishing-subscribing.html#_json If that's the case then I would move not only @ Migration annotation but also @ TypeName, which is even more realistic use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought user API for proto would skip the reflection-migration-lookup and be that the user supplies migrations manually, for that it just seems messy that they'd need to deal with optional, but for the reflective may-have-migration use I understand why it needs to be optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thought: if we support annotation-detection of migration also for proto, can we just hide it inside the JsonSupport instead and the user doesn't have to pass anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"I thought user API for proto would skip the reflection-migration-lookup" - in some sense, it will, because the user will just use the public static <T> T decodeJson(Class<T> valueClass, Any any) {
method. Although nothing will prevent him to use the signature with migration.
"can we just hide it inside the" - exactly that's my point one method without any extra params. The "only" drawback is that some code-first annotations will be from the java-sdk-spring
and some (@Migration
, @TypeName
?) from the java-sdk-protobuf
.
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/JsonSupport.java
Outdated
Show resolved
Hide resolved
PR is ready for the first review round. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did an initial skim-review, looking good
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/JacksonMigration.java
Outdated
Show resolved
Hide resolved
* @return The decoded object | ||
* @throws IllegalArgumentException if the given value cannot be decoded to a T | ||
*/ | ||
public static <T> T decodeJson(Class<T> valueClass, Any any, Optional<? extends JacksonMigration> jacksonMigration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make the signature with Optional param internal and just have one overload with migration and one without, for Java?
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/JsonSupport.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looking good, recognising much from Akka (which is a good thing)
@@ -0,0 +1,68 @@ | |||
/* | |||
* Copyright 2021 Lightbend Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chore: bumping the year
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's auto-generated by the plugin, I could bump the configuration and update all files, but in a separate PR
sdk/java-sdk-protobuf/src/main/java/kalix/javasdk/JsonSupport.java
Outdated
Show resolved
Hide resolved
public Optional<String> optionalStringValue; | ||
|
||
@JsonCreator | ||
public DummyClassRenamed(@JsonProperty("stringValue") String stringValue, @JsonProperty("intValue") int intValue, @JsonProperty("optionalStringValue") Optional<String> optionalStringValue) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are these JsonProperty needed if you have javacOptions += "-parameters"
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope, removed
I will create a separate PR with the documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, I think the protobuf side usage can be revisited in the future and is good enough for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Late for the party, but I think there are a few things that we should change before releasing it. See my comments.
* @return The decoded object | ||
* @throws IllegalArgumentException if the given value cannot be decoded to a T | ||
*/ | ||
public static <T> T decodeJson(Class<T> valueClass, Any any, Optional<? extends JsonMigration> jacksonMigration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Arriving late for the party, but I think we should make this one private. No need to augment the API surface with a method receiving Optional<JsonMigration>
.
Each time we call this method, we are calling MigrationExtractor.extractMigration
and passing the same valueClass
we are receiving as the first parameter. So we can simply remove this method and to this call internally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a problematic situation. Initially, this method was private, but to make it private I would need to move the @Migration
to java-sdk-protobuf.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to have the Migration annotation in the java-sdk-protobuf package, no?
Or do we plan to use this method in other contexts?
The decodeJson
method is also used when consuming json from a topic, but in such case, I doubt that it will be useful to pass a migration class as the type URL won't have the version number attached to it.
.params(777)); | ||
|
||
Thread.sleep(60000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that a leftover? Better to wrap it with await
and assert the return. Or get the state and assert it is 777
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, just ignore it, I removed it in some other PRs.
String entityId = "hello1"; | ||
execute(componentClient.forEventSourcedEntity(entityId) | ||
.call(CounterEntity::increase) | ||
.params(42)); | ||
.call(CounterEntity::increase) | ||
.params(42)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strange that this is working previous method increased hello1
to 777 and here we are using the same entity id. What am I missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably the order of tests is different than the order in the class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that has the potential to introduce flakiness. Better to use a different entityId.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I don't see how that could even work. If the ordering is different, it should also fail. One of the tests must fail, not clear why both are passing. 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because one of them is not checking anything, I was testing sth and I forgot to delete it. It is fixed on the main branch.
//TODO verify if this could be replaced by sth smarter/safer | ||
reversedCache.compute( | ||
addToReversedCache(clz, typeName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I recall correctly, we register the types when we register the components. This is supposed to be done by a single thread running over all components and finding out the types. In that sense, it's safe to update the main map and then the reversed one.
For good measure, we could synchronize this method to make sure we are safe. Just in case something changes one day.
"decode with new schema version" in { | ||
val encoded = messageCodec.encodeJava(SimpleClass("abc", 10)) | ||
val decoded = | ||
JsonSupport.decodeJson(classOf[SimpleClassUpdated], encoded, Optional.of(new SimpleClassUpdatedMigration)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we are adding this extra method receiving Optional<JsonMigration>
because we are using classes that belong to the Protobuf package and we don't have access to MigrationExtractor.extractMigration
nor the Migration
annotation.
IMO, this is another reason to remove this method. We should not increase the public API just because we want to reuse some classes.
One more thing... I see comments about Protobuf migration. If we plan to do it, we should build another abstraction. The class name is JsonMigration and it depends on Jackson classes (JsonNode). Not something that we want or can reuse for Protobuf payloads. |
reference #1332