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
Replace protocol-util module with explicit mapping via forward-declarations #9015
Conversation
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.
👍 Nice work @npepinpe.
❓ Have you tested this already with shading like Operate does it?
It feels slightly strange to annotate a new type with @ImmutableProtocol(immutable=ImmutableNewClassName.class, builder=ImmutableNewClassName.Builder.class)
while the ImmutableNewClassName
and the ImmutableNewClassName.Builder
haven't been generated yet. Especially, because they are only generated because of the @ImmutableProtocol annotation. To annotate with @ImmutableProtocol
both the immutable
and the builder
parameters are required but need to be filled with a not yet existing class. It feels a bit like a chicken/egg problem. Then again, once it's declared it will compile just fine. 🤔 Perhaps we just need to provide a clear direction for developers on how to add new types. Because that is the confusing part.
I don't necessarily mind having to add the parameters to the annotation for new types. It's not much more work or so. Developers already need to add the annotation when creating new protocol types and we have a test case for this to guide them. 🔧 Perhaps the test case needs to provide additional directions on how to annotate the interface.
I didn't test it with Operate, mostly because running their tests takes forever 😄 I did test a small shading example however, and it works nicely because the type you wish to deserialize carries all the information it needs. So if you shade two versions of the protocol, relocating @io.camunda.zeebe.protocol.v14.ImmutableProtocol(
immutable = io.camunda.zeebe.protocol.v14.ImmutableRecord.class,
builder = io.camunda.zeebe.protocol.v14.ImmutableRecord.Builder.class
)
public interface io.camunda.zeebe.protocol.v14.Record<T extends io.camunda.zeebe.protocol.v14.RecordValue> {
// ...
} So when Jackson wants to, say, find the builder, then the annotation returns the shaded version as well. There is no need anymore for the I get that it looks a little weird initially, since the annotation which generates the types (e.g.
I think the second option is also something we can explore, but I'm wary of having conventions that aren't made explicit in the code, and having the annotation values works around that. As the protocol is very much something the engine team will maintain (meaning not me 😄), I'm also not super keen on pushing something that you will not want to maintain, so I want to be sure that, well, you're sure of this 😄 |
Nice example. Thanks for sharing.
That's a good point.
I agree that the current proposal is better.
That makes sense. I was wondering about polling the team, but I think that's not necessary. I'm happy to accept a PR with the proposed solution. |
6641b02
to
cd2bcb8
Compare
The latest version shows an example of what it would look like without the forward declarations in the annotations, and instead rely on looking up the builder by name. Let me know what you think. I thought it might be good to see both and check which you prefer. I personally prefer the forward declarations as I'll know quicker if anything goes wrong, but the arch unit tests were more complicated to write (though that's probably just because I'm not super familiar with ArchUnit yet). |
It feels more brittle due to the fqdn construction and class loading. I guess it works with shading because the package is taken from the annotated type at runtime instead of being hardcoded. Nevertheless, this setup seems more complicated because you need to be aware of the naming convention. My preference is still on the first option with forward declarations. |
cd2bcb8
to
0d09d20
Compare
Here's one final option 😄 We only really need the forward declaration of the builder. For the immutable one, as we have the marker annotation, the factory can find it using classgraph (as it's already finding the protocol types anyway). So for now we don't need this. I also added a test for the builder declaration. So if you have, say,
If you were to annotate it with
|
9200f8c
to
82c169a
Compare
82c169a
to
68d6c67
Compare
Now that's fancy. So, let me get this straight. The immutable type doesn't have to be directly declared because it was only needed for the ProtocolFactory. The builder is needed because it is used by protocol-jackson, but it does not need the direct type. Right?
Love the comprehensiveness of the message 😄 It's a bit of a novel, but clearly describes what is wrong. I like it 👍 |
68d6c67
to
a4db9bc
Compare
BTW, when I run the auto format profile locally, nothing happens. Any ideas? |
It works on my machine. How are you running it? And what do you see? |
|
I don't understand, but it works on my machine... gh pr checkout 9015
git status
mvn install -PautoFormat -DskipTests -T2C
git status
If you want I can commit the changes, but I'd rather we find out why it doesn't work for you. Did you have a look at bugs in google-java-format or spotless related to your OS? |
Not sure what bug I would look for, since it shows everything as working 😄 |
Have you tried running: |
So I was running this in a separate worktree, and for some reason there it did nothing. When running this in the main one it worked perfectly. I wonder if it's not related to the ratcheting 🤔 |
That's very interesting. Sounds like it is. You could consider raising a bug for spotless which is doing the ratchetting. |
/** | ||
* @return the builder class for this abstract protocol type | ||
*/ |
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.
🔧 Please change the return tag into a summary fragment:
/** Returns the builder class for this abstract protocol type */
See #9045
@npepinpe It seems you have some failing tests in |
That's what I get for rebasing 😄 |
I would guess it's a caching issue, since Jenkins doesn't show these error, and the class does exist. Jenkins: 1, GHA: 0 😉 Jenkins only shows flaky tests that are unrelated. |
It's an issue with the GHA CI. You can see in the tests that it built the protocol module, but then when running the test it downloaded a remote snapshot which doesn't have the latest changes. Please only use the Jenkins tests for validating the PR. |
Are you sure you want to ignore this problem with the GHA CI? This PR will be the one adding a situation where the problem arises. I'd prefer to find a solution to the caching problem before merging. What do you think? |
I'm going to be unpopular and say that while I appreciate the engagement and motivation you all show to push to migrate to GHA, it's important that it doesn't disrupt us, and if we wanted to work on it we should account for it in our planning instead of simply going ahead and forcing us to make time to migrate to an incomplete solution. So while yes, we should figure it out, I disagree that we should block PRs. Instead, please open an issue for this which describes the problem, as it is entirely unrelated to the code changes here. P.S. my tone is very serious I guess, but its not that big a deal. Just in case it doesn't come out correctly via text 😄 |
Although I disagree, I guess we can allow it since a fix is underway. |
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.
Thanks for these changes @npepinpe 🥳 LGTM
Maps the immutable protocol directly via annotations. Instead of relying on ClassGraph to build a map of all the types, we can simply map the abstract, concrete, and builder types directly via annotations. These can be used at runtime for deserialization, random generation, etc. Here we are then adding a little bit of overhead when creating new values, but hopefully reducing the amount of maintenance, as well as making things a little bit more explicit.
Instead of using the `protocol-util` module, use the protocol marker annotations to correctly deserialize the protocol's abstract types into concrete types. This will also allow deserializing the immutable classes as well.
Use ClassGraph directly to discover protocol types instead of relying on the `ProtocolTypeMapping` to do so. This allows us to remove `protocol-util` entirely.
32d86c6
to
1028236
Compare
bors merge |
Looks like this batch crashed 🤔 bors r- |
bors r+ |
9015: Replace protocol-util module with explicit mapping via forward-declarations r=npepinpe a=npepinpe ## Description This PR drops the `protocol-util` module entirely, instead opting for a more explicit mapping done via a forward-declaration in the `ImmutableProtocol` annotation. The annotation now has a mandatory member, `builder`, which should be assigned to the builder class generated by the immutable protocol. This will allow classes to later find the correct builder class, enabling deserialization use cases. It could be further extended to also enable mapping by declaring the immutable class as well. Doing this means the type itself carries all the information it needs to be deserialized by `protocol-jackson`, which makes the shading use case much, much simpler. Operate now only has to shade the protocol itself, nothing else needs to be shaded (though they may still want to shade the deserialization module to be safe, as shading is there for major versions). This PR also marks the generated types with two marker annotations: `ImmutableProtocol.Type` and `ImmutableProtocol.Builder`. The generated types (`ImmutableRecord`, `ImmutableVariableRecordValue`, etc.) will be marked with `ImmutableProtocol.Type`, and their builders with `ImmutableProtocol.Builder`. Doing so allows us to more easily detect whether a type needs special deserialization configuration (e.g. ``@JsonIgnore(unknownProperties` = true)` for the types marked), and also lets the `ProtocolFactory` easily find the concrete, immutable types for the abstract protocol types (i.e. look for `ImmutableProtocol` annotated types, then look for their implementing classes annotated with `ImmutableProtocol.Type`). We can then move the `ValueTypeMapping` class into the protocol since it has no dependency other than the protocol. This allows us to get rid entirely of the `protocol-util` module, which was admittedly a very broad module by definition, and most likely would have become a shallow module over time. A few tests were added to compensate, mostly `ArchUnit` tests to ensure that the forward-declared builder builds the correct type, and again in the factory to ensure we can generate objects for all protocol types. 9068: chore(benchmark): remove obsolete files r=pihme a=pihme ## Description Removes obsolete files ## Related issues closes #9066 Co-authored-by: Nicolas Pepin-Perreault <nicolas.pepin-perreault@camunda.com> Co-authored-by: pihme <pihme@users.noreply.github.com>
Build failed (retrying...): |
Seems like bors crashed once again... bors retry |
Description
This PR drops the
protocol-util
module entirely, instead opting for a more explicit mapping done via a forward-declaration in theImmutableProtocol
annotation. The annotation now has a mandatory member,builder
, which should be assigned to the builder class generated by the immutable protocol. This will allow classes to later find the correct builder class, enabling deserialization use cases. It could be further extended to also enable mapping by declaring the immutable class as well. Doing this means the type itself carries all the information it needs to be deserialized byprotocol-jackson
, which makes the shading use case much, much simpler. Operate now only has to shade the protocol itself, nothing else needs to be shaded (though they may still want to shade the deserialization module to be safe, as shading is there for major versions).This PR also marks the generated types with two marker annotations:
ImmutableProtocol.Type
andImmutableProtocol.Builder
. The generated types (ImmutableRecord
,ImmutableVariableRecordValue
, etc.) will be marked withImmutableProtocol.Type
, and their builders withImmutableProtocol.Builder
. Doing so allows us to more easily detect whether a type needs special deserialization configuration (e.g.@JsonIgnore(unknownProperties = true)
for the types marked), and also lets theProtocolFactory
easily find the concrete, immutable types for the abstract protocol types (i.e. look forImmutableProtocol
annotated types, then look for their implementing classes annotated withImmutableProtocol.Type
).We can then move the
ValueTypeMapping
class into the protocol since it has no dependency other than the protocol. This allows us to get rid entirely of theprotocol-util
module, which was admittedly a very broad module by definition, and most likely would have become a shallow module over time.A few tests were added to compensate, mostly
ArchUnit
tests to ensure that the forward-declared builder builds the correct type, and again in the factory to ensure we can generate objects for all protocol types.Definition of Done
Not all items need to be done depending on the issue and the pull request.
Code changes:
backport stable/1.3
) to the PR, in case that fails you need to create backports manually.Testing:
Documentation: