Skip to content

Conversation

@olegz
Copy link
Contributor

@olegz olegz commented Apr 28, 2020

  • Streamlined CloudEvent strategy to ensure it's clean from any assumptions on possible implementation details to ensure that CloudEvent
    type created outside of SDK is just as valid and operable as the one created by SDK.
  • Created CloudEventUtils as general utility class to contain set of static method to manipulate various aspects of CloudEvent. Factory
    methods that were previosuly in CloudEvent as well as other interafces were moved. Tests and other implementations were adjusted accordingly.
  • Cleaned up logic in Message and BinaryMessage related to 'can never happen` comment as well as fixing compilation errors these classes were generating in some IDEs.

Signed-off-by: Oleg Zhurakousky ozhurakousky@pivotal.io

- Streamlined CloudEvent strategy to ensure it's clean from any assumptions on possible implementation details to ensure that CloudEvent
type created outside of SDK is just as valid and operable as the one created by SDK.
- Created CloudEventUtils as general utility class to contain set of static method to manipulate various aspects of CloudEvent. Factory
methods that were previosuly in CloudEvent as well as other interafces were moved. Tests and other implementations were adjusted accordingly.
- Cleaned up logic in Message and BinaryMessage related to 'can never happen` comment as well as fixing compilation errors these classes were generating in some IDEs.

Signed-off-by: Oleg Zhurakousky <ozhurakousky@pivotal.io>
@olegz
Copy link
Contributor Author

olegz commented Apr 28, 2020

@slinkydeveloper sorry, I didn't pat attention to .editorconfig before submitting this PR, so please let me know if you want me to submit the amended formatting PR

@slinkydeveloper
Copy link
Member

Streamlined CloudEvent strategy to ensure it's clean from any assumptions on possible implementation details to ensure that CloudEvent
type created outside of SDK is just as valid and operable as the one created by SDK.

Can you be more specific about this one? Can I ask why you should create a CloudEvent subtype outside this package?

@bsideup
Copy link
Contributor

bsideup commented Apr 28, 2020

@slinkydeveloper there are and will be CloudEvent implementations that choose not to use the SDK's implementation, but only types, e.g. for performance, or as an adapter from some other spec.

@olegz
Copy link
Contributor Author

olegz commented Apr 28, 2020

Sure, it is directly related to what you and I discussed and agreed on slack last week. Basically CloudEvent class is a type-safe representation of Cloud Events spec. One may chose for variety of reasons to have perhaps a highly specialized implementation of it, so at that point all that matters is the actual strategy (interface) and it should be as clear and as light as possible.

* @return the version of the Cloud Events specification which this event uses.
*/
Map<String, Object> getExtensions();
SpecVersion getSpecVersion();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think CloudEvent should implement Attributes interface

Copy link
Contributor

@bsideup bsideup Apr 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this change, Attributes role becomes minor, and I can even see the interface being removed (there is no such "interface" in the spec and the attributes are defined next to other fields like data anyways)

Does it make sense to keep Attributes in CloudEvent type at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I tend to agree with Sergey, but would also like to have a separate discussion on that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in go we make the top level event implement the attributes and also an accessor to get the holder of attributes, if that was translated to this lib, it would be that CloudEvents extends Attributes and also has a getter getAttributes() Attributes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@n3wscott it kind of contradicts your previous comment, but it could very well be me misinterpreting this comment for you simply stating how things are currently done in go.

*/
String getType();

BinaryMessage asBinaryMessage();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these should not be removed, since they simplify a lot the integration between the event in memory representation and the "wire" representation, please revert these.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are utility operations that simply don't belong in the core strategy of CloudEvents. I simply moved them to CloudEventUtils class as you and I discussed last week.

@Nullable
String getDataContentType();

static io.cloudevents.v1.CloudEventBuilder buildV1() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe It would make sense to have a simple CloudEventBuilder interface, and move these static methods there more than in CloudEventsUtils

Copy link
Contributor Author

@olegz olegz Apr 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder, Utils, Factory are all different concepts. In fact as Java developer when I see the class name that ends with Builder i look for builder pattern. *Utils and *Factory are different. We can certainly discuss it but in spirit of small and manageable/reviewable PRs that address one thing at the time I decided to only address things that are relevant to streamlining CE

Copy link

@markfisher markfisher May 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe worth considering "Encoders" (in addition to but separate from versioned "Builders", but also not as vague as "Utils") as the structured and binary formats are encoding modes in CloudEvents terminology, and an Encoder interface could be useful, as there will certainly be new encodings in the future.

}

@Override
public void visitExtensions(BinaryMessageExtensionsVisitor visitor) throws MessageVisitException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Contributor Author

@olegz olegz Apr 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the core pattern of message/event exchanges is their immutability since events represent moment in history and thus can not be re-written. So giving user reference to a mutable map is very dangerous

}

@SuppressWarnings("rawtypes")
public static BinaryMessageVisitorFactory defaultBinaryMessageVisitorFactory() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

/**
* The event context attributes
*/
Attributes getAttributes();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If CloudEvent implements Attributes, then this method can still live here.

The reason to keep this method is to allow forward compatibility or backward compatibility with old/new attributes, accessing some specific "features" of a specific spec version

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't casting to CloudEventsV1Impl would serve the same purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User of SDK can still downcast it to CloudEventImpl and we can certainly even make it public there for reason you just explained, but with regard to CloudEvent interface we should start looking at it as something that should never change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In go we had been casting to a version to access but it turns out that putting the accessors at the top level like you might in java style code and internally redirect to the correct attribute was pretty valuable for moving code forward in the cloudevent versions.

We choose to keep the accessor interface to be aligned with the most current version of the spec and swizzle internally to make older encodings look like new ones.

Copy link
Contributor Author

@olegz olegz Apr 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@n3wscott I can get behind it and as you can see at the moment that is exactly what we're doing

@slinkydeveloper
Copy link
Member

Sure, it is directly related to what you and I discussed and agreed on slack last week. Basically CloudEvent class is a type-safe representation of Cloud Events spec. One may chose for variety of reasons to have perhaps a highly specialized implementation of it, so at that point all that matters is the actual strategy (interface) and it should be as clear and as light as possible.

I see, I don't disagree with this but I think you removed too much from the CloudEvent interface:

  • CloudEvent should implement Attributes, so getAttributes() can still be there and you can freely decide to flat the structure of the event
  • The methods to convert CloudEvent to Message should still be in the CloudEvent interface. The reason is to simplify the usage and, most important, guarantee that implementations of CloudEvent can be converted to wire/event format representations (otherwise, there is no point at all to use this sdk)
  • Builder methods should not live in CloudEventUtils, but I think they should rather live in an interface CloudEventBuilder, which has just a CloudEvent build() method and has the static methods to build the event (buildV1(), etc)

@olegz
Copy link
Contributor Author

olegz commented Apr 28, 2020

@slinkydeveloper I believe I addressed all your points in individual comments, but let me summarize to match yours"

  • As Sergey stated, individual attributes are defined by the spec not the Attributes as a collection of attributes. Further more as you can see there are questions about whether we even need this interface all together. So let's have that discussion separately.
  • The ...most important, guarantee that implementations of CloudEvent can be converted to wire/event.... That is guaranteed and managed by the spec itself. The structure one has is either Cloud Event or not and specification is very clear on what Cloud Event is. Conversion to/from (wire or any other representation) is NOT the responsibility of the interface that represents the actual structure defined by the spec. That is why frameworks have utilities, factories, builders and other mechanisms.
  • I agree, CloudEventUtils at the moment is a bucket of miscellaneous functionality gathered in a single place. Some builders, some utils, some converters etc. . . We can certainly move them around and I am more than open to that. However, the core goal of this PR was to actually move them out of core strategies - CloudEvent. We can certainly address your concerns related to CloudEventUtils in the next PR and I can commit few cycles tomorrow if you believe it's a pressing issue.

@slinkydeveloper
Copy link
Member

slinkydeveloper commented Apr 29, 2020

As Sergey stated, individual attributes are defined by the spec not the Attributes as a collection of attributes. Further more as you can see there are questions about whether we even need this interface all together. So let's have that discussion separately.

Well, since you're removing in this PR the entrypoint of the Attributes, to me it doesn't look like you're making a separate discussion for that. I would say that for this PR is enough to have CloudEvent implement Attributes and keep getAttributes()

The ...most important, guarantee that implementations of CloudEvent can be converted to wire/event.... That is guaranteed and managed by the spec itself. The structure one has is either Cloud Event or not and specification is very clear on what Cloud Event is. Conversion to/from (wire or any other representation) is NOT the responsibility of the interface that represents the actual structure defined by the spec. That is why frameworks have utilities, factories, builders and other mechanisms.

The sdk needs an unstructured view of the event to simplify the serialization/deserialization implementation, and that's what Message is all about. It's a specific way to implement serialization without caring about the spec version and allowing direct message transcoding without going through the CloudEvent implementation. This is a core feature of the sdk and of the CloudEvent contract. If you want to reimplement the CloudEvent for whatever reason and you don't provide a way to convert to Message, the rest of the sdk is unusable for you. Please check out this explanation of the Message (names differ, but concepts are the same): https://cloudevents.github.io/sdk-go/protocol_implementations.html
Also, moving as you did in a CloudEventUtils, allocates unnecessary memory for the Message, which are the low level apis designed to be fast (in theory, in practice still no work was done on this area in sdk-java)

I agree, CloudEventUtils at the moment is a bucket of miscellaneous functionality gathered in a single place. Some builders, some utils, some converters etc. . . We can certainly move them around and I am more than open to that. However, the core goal of this PR was to actually move them out of core strategies - CloudEvent. We can certainly address your concerns related to CloudEventUtils in the next PR and I can commit few cycles tomorrow if you believe it's a pressing issue.

Since you're doing the change, please move the builders where they belong more than in a CloudEventUtils.

@olegz
Copy link
Contributor Author

olegz commented Apr 29, 2020

". . .This is a core feature of the sdk and of the CloudEvent contract. . .
Couple of points here:

  • By moving those default operations representing conversion/builder functionality to a utility class does nothing to prevent user from from using it.
  • Keeping it inside the interface does nothing to force user to use it, since user can still override or proxy those default operations. In other words keeping those operations inside of the interface doesn't accomplish anything from the standpoint of additional benefit.
  • CloudEvent is a type representing part of the core specification which defines data structure. Bindings are the concepts defined by the optional specification. So, let's make sure we keep it that way. Remember one can exchange CloudEvent with another system by simply serializing/deserializing it without ever relying on bindings, hence the optional part.

With regard to CloudEventUtils as I said before there are number of questions about several operations currently in it and it is best not to conflate them into this PR which has different goals.

The same with Attributes.
Keep in mind, we can always add things but we can never remove (as it introduces immediate breaking change), so the general rule of thumb is to keep everything locked until there is a valid justification to unlock it.

@slinkydeveloper
Copy link
Member

CloudEvent is a type representing part of the core specification which defines data structure. Bindings are the concepts defined by the optional specification. So, let's make sure we keep it that way.

CloudEvent are useful in the real world if they can be serialized/deserialized to bindings/event formats.

Remember one can exchange CloudEvent with another system by simply serializing/deserializing it without ever relying on bindings, hence the optional part.

As I stated in the other comment, the serialize/deserialize process is implemented through the unstructured view aka Message: this is a core concept in the CloudEvent class and every CloudEvent implementation must implement it, otherwise they can't be serialized/deserialized. And the reason to have it in the contract is that every implementation can implement this conversion in a efficient way.
Please look at how serialization/deserialization is implemented in the submodules of the sdk to figure out how it works.

With regard to CloudEventUtils as I said before there are number of questions about several operations currently in it and it is best not to conflate them into this PR which has different goals.

Again, I think it's definitely related to this PR the change to the builder, I don't see the reason why to move temporary to a Utils class to move them back in another PR...

Keep in mind, we can always add things but we can never remove (as it introduces immediate breaking change), so the general rule of thumb is to keep everything locked until there is a valid justification to unlock it.

The SDK atm is in active development for a new major release so we can definitely remove stuff, depending on the context.

@bsideup
Copy link
Contributor

bsideup commented Apr 29, 2020

CloudEvent are useful in the real world if they can be serialized/deserialized to bindings/event formats.

I disagree here. Systems like Debezium may represent the events they generate as CloudEvents for later in-process consumption, without any serialization at all

this is a core concept in the CloudEvent class and every CloudEvent implementation must implement it, otherwise they can't be serialized/deserialized.

IMO "must" here is incorrect. As in my previous point, the CloudEvent type may be useful as an in-memory type.

Again, I think it's definitely related to this PR the change to the builder, I don't see the reason why to move temporary to a Utils class to move them back in another PR...

Because many other previous changes were done in multiple PRs, with "I will do it later" and TODOs? Keeping PRs small (given that v2 is still WIP) makes it much easier to review them and focus on a single change.

@slinkydeveloper
Copy link
Member

IMO "must" here is incorrect. As in my previous point, the CloudEvent type may be useful as an in-memory type.

Can you please argument this particular topic?

Because many other previous changes were done in multiple PRs, with "I will do it later" and TODOs? Keeping PRs small (given that v2 is still WIP) makes it much easier to review them and focus on a single change.

If you want to split in several PRs, then revert here the changes made to the buildV1() etc and let's do them in another PR

@olegz
Copy link
Contributor Author

olegz commented Apr 29, 2020

With regard to ... CloudEvent type may be useful as an in-memory type..... Examples are plenty:

  • A very basic reference passing from one operation to another.
  • User develops functionality that accepts/produces CloudEvent, but doesn't care how this event originated or where it goes. In other words, the bindings may be added later or not at all, since CloudEvent itself can be serialized/deserialized to/from simple JSON representation without ever going through Wire message- something @fabiojose and I briefly discussed few weeks ago.
  • Functional composition Function<?, CloudEvent> funcA() and Function<CloudEvent, ?> funcB() and then Function<?,?> composed = funcA.andThen(funcB()) - where two function will effectively exchange CloudEvent without it ever being serialized/deserialized.

I can continue but that should be enough.

With regard to "...CloudEvent are useful in the real world if they can be serialized/deserialized to bindings/event formats....". That is an opinion not a fact. Bindings being optional specification, on the other hand, is a fact.

With regard to "...then revert here the changes made to the buildV1()...".
We can't do that since the sole purpose of this PR is to remove those operations from the interface.

Also ". . . SDK atm is in active development for a new major release so we can definitely remove stuff,. . ". - that is exactly what we're doing here, removing functionality that is not justified. Basically look at it this way; If this is indeed an active development, then let's apply one of the best principals of software design and pretend we have a clean sheet of paper and argue about what to put on it as opposed to put everything on it and argue about what to remove.

@slinkydeveloper
Copy link
Member

slinkydeveloper commented Apr 29, 2020

User develops functionality that accepts/produces CloudEvent, but doesn't care how this event originated or where it goes. In other words, the bindings may be added later or not at all, since CloudEvent itself can be serialized/deserialized to/from simple JSON representation without ever going through Wire message- something @fabiojose and I briefly discussed few weeks ago.

This is an antipattern: the business logic of an application should not depend on CloudEvent, which is merely a standard exchange format, but on its attributes and payload. It's like saying that the application logic should depend on a SOAP envelope...

With regard to "...CloudEvent are useful in the real world if they can be serialized/deserialized to bindings/event formats....". That is an opinion not a fact. Bindings being optional specification, on the other hand, is a fact.

As you can see here, bindings are not optional to sdk development: https://github.com/cloudevents/spec/blob/master/SDK.md#technical-requirements

With regard to "...then revert here the changes made to the buildV1()...".
We can't do that since the sole purpose of this PR is to remove those operations from the interface.

I'm sorry, but I don't see the reason to remove these methods in this PR and move in an Utils class.

@olegz
Copy link
Contributor Author

olegz commented Apr 29, 2020

"...This is an antipattern: the business logic of an application should not depend on CloudEvent..."
So you're saying that CloudEvent type - the namesake strategy that represents the core concept of the Cloud Events effort is really just an internal strategy to only define functionality to assist with transmission and marshalling of the event? If so, then I need to assume that this is also an invalid issue and we and many other java frameworks should only depend on specification and create our own types representing CloudEvent not on the one from SDK? I didn't think of it that way, but. . .

Anyway, would love to see what @jamesward @n3wscott and others think

"...As you can see here, bindings are not optional. . ."
First, the link you posted provides technical requirements for the SDK which is one of the mechanisms to support the actual specification. So let's not confuse the two.
It also states that SDK must "Supports CloudEvents at spec milestones. . ", "Encode/Decode a canonical Event . . ." etc., and I wholeheartedly agree with those statements.
But CloudEvent type is a part of SDK (not an SDK itself) and all that we are discussing here is should this specific type, which represents the core abstraction of the specification, also be responsible for the optional part(s) of the actual specification.

@slinkydeveloper
Copy link
Member

If so, then I need to assume that this is also an invalid issue and we and many other java frameworks should only depend on specification and create our own types representing CloudEvent not on the one from SDK? I didn't think of it that way, but. . .

Ok maybe I didn't explained myself clearly: Let's assume you want to create a CQRS application where commands comes from kafka as cloudevents. Your business logic should not depend from the fact that you received the command as CloudEvent: You should take the cloudevent and convert it into the domain specific command POJO.

@slinkydeveloper
Copy link
Member

But CloudEvent type is a part of SDK (not an SDK itself) and all that we are discussing here is should this specific type, which represents the core abstraction of the specification, also be responsible for the optional part(s) of the actual specification.

CloudEvent is the main entry point of the sdk features and it's a primary goal to make as easy as possible the interaction between the event and the other sdk features.

@olegz
Copy link
Contributor Author

olegz commented Apr 29, 2020

"...You should take the cloudevent and convert it into the domain specific command POJO...".
Again, that's an opinion, but for the sake of this discussion i'll get behind it. . .
So what you said does indeed mean that I have to be aware of CloudEvent type (at least for the conversion). But I also have to be aware of CloudEvent type to support it's creation from non-CloudEvent as described in SDK specification "I have this data that is not formatted as a CloudEvent and I want it to be."

"... CloudEvent is the main entry point of the sdk features and it's a primary goal to make as easy as possible the interaction between the event and the other sdk features....."
I can't agree with this statement as it implies that CloudEvent type itself is effectively a bucket of arbitrary functionality which contains everything (things you need and don't) . . as if the SDK itself is just one class.

@slinkydeveloper
Copy link
Member

I can't agree with this statement as it implies that CloudEvent type itself is effectively a bucket of arbitrary functionality which contains everything (things you need and don't)

I'm specifically speaking about toStructuredMessage and toBinaryMessage, I completely agree on moving the other methods, as I underlined in previous comments.

"I have this data that is not formatted as a CloudEvent and I want it to be."

This is specifically covered by the builders: with the previous CQRS example, the eventual integration between my CQRS command POJOs and the kafka producer uses the builder to build a CloudEvent representation of the command

@olegz
Copy link
Contributor Author

olegz commented Apr 29, 2020

". . .I'm specifically speaking about toStructuredMessage and toBinaryMessage. . ."
which is exactly what I mean when I say "...things you need and don't..." as we already established that one may never need to convert CloudEvent to structured or binary representation - hence the optional part of specification.

Creation of CloudEvents "...is specifically covered by the builders:... "
No it is not.
There is an application event (non-cloud-event) encapsulated into a structure known only to the application (e.g., some PersonHiredDTO) and we want to communicate it to a system that can only understand Cloud Events, so we need to create CloudEvent. Only we know how to turn PersonHiredDTO to CloudEvent.
Just to clarify further. . . I may use some utilities from SDK such as builders to facilitate creation of CloudEvent, but that is not requirement, since the structure of cloud event contract is defined and expected from CloudEvent type itself and that is the only thing that is relevant to this issue - keep it clean and concise to clearly represent the data structure defined by the specification and stay free of any assumptions with regard to behavior of such structure. That is the job for utilites.

@slinkydeveloper
Copy link
Member

Creation of CloudEvents "...is specifically covered by the builders:... "
No it is not.
There is an application event (non-cloud-event) encapsulated into a structure known only to the application (e.g., some PersonHiredDTO) and we want to communicate it to a system that can only understand Cloud Events, so we need to create CloudEvent. Only we know how to turn PersonHiredDTO to CloudEvent.

Builders provides a structured way to build the event, MessageVisitor provides an unstructured way to build events

@olegz
Copy link
Contributor Author

olegz commented Apr 29, 2020

Throughout the life of this PR no one was questioning builders or visitors. We are simply moving them out of CloudEvent to a different place.

@n3wscott
Copy link
Member

Hello folks. Honestly I can't say if this is a better or worse change than what is in the code base at the moment because there is no agreement on the higher level vision for how the SDK should be architected.

I think @fabiojose, @slinkydeveloper, @olegz and @bsideup (I would be happy to help this process too) need to pause and write some docs to understand what the goal in the v2 of the java SDK is. It helped for go to start with the UX you want to provide to the integrators, and it helped to think of that UX in terms of several personas: a middleware, a sdk extender (someone adding a protocol, perhaps not in-tree), a consumer (and their can be several of these, like http, FaaS, green field, legacy integrators... etc)

If you do not see and agree about the north star goals of what we are trying to provide then these large refactors that add and remove features only hurt adoption because no one can use the code. So let's take a pause and have some meetings, write some docs, and then get to back work. It helped to start with a google doc and then turn that into markdown but you can do anything you want... maybe a hackmd would be better here.

tl;dr: without a plan, we are lost.

@duglin
Copy link
Contributor

duglin commented Apr 30, 2020

Can we discuss this on the SDK call tomorrow?

@olegz
Copy link
Contributor Author

olegz commented Apr 30, 2020

Thank you @duglin I will certainly join. Perhaps @bsideup can do as well

@slinkydeveloper
Copy link
Member

@slinkydeveloper
Copy link
Member

slinkydeveloper commented May 4, 2020

I'm sorry, maybe i explained myself badly, but i explicitly reject the PR for several reasons:

  • I don't agree with removing the opportunity to visit the event in an unstructured manner. I prefer to reach an agreement on that subject in another PR. As I stated both in this document comments and on PR comments this makes a lot harder supporting several spec versions in an efficient way.
  • I think several different discussions are baked into that PR. I totally agree with some of them, so i would love to split and threat them separately, so we can reach an agreement on the separate topics

I'm looking forward to a split of the PR as follows:

  • attributes interface/attributes getters into the CloudEvent interface
  • Move event version converters (where? utils doesn't seem reasonable imo, maybe should we impose it just through the builders?)
  • Event Builder with unstructured build
  • Unstructured read of the CloudEvent

After the discussion we had last week, I thought about how we could provide an unstructured view of the cloudevent without putting this burden in the hands of the CloudEvent implementer. I'm going to propose a PR for the last two topics in order to discuss further about that.

Feel free to close this one or open another one for the other changes

@olegz
Copy link
Contributor Author

olegz commented May 4, 2020

@slinkydeveloper Let me address your individual points

  • When you say "I don't agree" in a respectful OSS collaboration process it usually follows with an alternative proposal, which your above comment is lacking.
  • When you say "this makes a lot harder supporting several spec versions in an efficient way." again you are not providing any details based on some technical merits as to what makes it harder, what is your definition of efficient way and how it is measured.
  • When you say "several different discussions are baked into that PR" it was not our choice. This PR is small, concise and clear in its goals, so please go back to its origins and feel free to ask any technical question or demand clarification on subjects that you believe are not clear.
  • "Move event version converters (where? utils doesn't seem reasonable imo, maybe should we impose it just through the builders?)" as I stated several times, builders, factories, transformers and converters are patterns with certain expectations well recognized by java developers, so we need to be extremely careful when calling something a builder vs. transformer etc. So with regard to those two methods I am not yet sure what they are with regard to these patterns, hence me throwing it into intermediary utils class. So I am begging you once again to have a separate discussion, separate issue and separate PR on these type of subjects.
  • Last but not least I am not even sure what the last two bullets mean (- Event Builder with unstructured build - Unstructured read of the CloudEvent)

I am certainly not closing this PR and urging you to reconsider or provide detail technical justification based on the original merits of this PR as to why it is invalid.

Copy link
Contributor

@fabiojose fabiojose left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite the rich discussion, I want to register my point of view as a java programmer:

First, +1 for this PR. Let's keep a clear definition, it's better than an interface with a lot of methods.

  • it really uncommon to have utility methods defined at interfaces. Interfaces define signatures, not behaviors (it's just a friendly remember :)
  • fellows, this is java! What about use idiomatic Java?
  • Let's maintain just the support for spec 1.0, it will be easiest to get things done
  • I understand the wish to have the same API for every language, but this SDK will use by people who uses java.
  • I agree with @n3wscott, we need to define a direction and together reach it
  • I understand the nature of OSS collaboration, agreement, and disagreement are very common. But, let take the best of this PR and make our considerations.

return this.attributes;
}

public CloudEvent toV03() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be dropped away, one version will be easiest to maintain.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SDK should support v0.3!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if instead of CloudEventImpl with version converting methods there could instead be V1CloudEvent as the "impl" and a CloudEventBuilder could support conversion if a V1CloudEvent is passed in but V03 is requested via something like builder.from(somev1CloudEvent).asV03().

}

@SuppressWarnings("rawtypes")
public static BinaryMessageVisitorFactory defaultBinaryMessageVisitorFactory() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

return builder.build().toV1();
}
switch (sv) {
case V03:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's drop out the v0.3, it is not required to support this version.

}

@ParameterizedTest()
@MethodSource("io.cloudevents.test.Data#v03Events")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's drop out the support for spec 0.3


assertThat(eventV03.getAttributes().getSpecVersion())
assertThat(eventV03.getSpecVersion())
.isEqualTo(SpecVersion.V03);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's drop out the support for spec 0.3

@slinkydeveloper
Copy link
Member

slinkydeveloper commented May 5, 2020

Hi @fabiojose , it would be amazing to have just support for one spec version because it could vastly over simplify the architectures of the sdks, but we can't:

  • First, we have an hard requirement as explained here: https://github.com/cloudevents/spec/blob/master/SDK.md#contribution-acceptance
  • We need to be prepared for changes to the spec. Whatever change it is, even smaller, that causes a bump of the spec version, an sdk should be able to support several specversions at the same time. A user should be able to upgrade its services from one specversion to another progressively, and i'm not even mentioning external services. Let's say i have 10 services, which all talk using specversion 1.0. At some point, you need to upgrade to 1.1, How do you handle that update progressively if the next version of the sdk supports only 1.1? An old sdk should be able to talk to a newer one and so on... This is a fundamental usability issue we need to cover.

@bsideup
Copy link
Contributor

bsideup commented May 5, 2020

First, we have an hard requirement as explained here:
https://github.com/cloudevents/spec/blob/master/SDK.md#contribution-acceptance

The URL does not state that there is any hard requirement:

Note: v1.0 is a special case and it is recommended that as long as v1.0
is the latest version, SDKs should also support v0.3.

We need to be prepared for changes to the spec.

I agree. This is why it is important to have main interfaces spec-free and encapsulate every per-spec aspect into a dedicated subset of the SDK. But that's a separate discussion and does not belong to this PR.

@slinkydeveloper
Copy link
Member

BTW for who didn't noticed, this is my alternative proposal: #146

@slinkydeveloper
Copy link
Member

slinkydeveloper commented May 5, 2020

I agree. This is why it is important to have main interfaces spec-free and encapsulate every per-spec aspect into a dedicated subset of the SDK. But that's a separate discussion and does not belong to this PR.

True, but you can't ignore that you need to marshal/unmarshal event formats/protocol bindings supporting different spec versions, hence the need for an unstructured read/write of the event. Otherwise, each event format/protocol binding must handle by itself the different spec versions

@bsideup
Copy link
Contributor

bsideup commented May 5, 2020

BTW for who didn't noticed, this is my alternative proposal: #146

I prefer to deal with it in FIFO order, and, since this PR is not "out" yet (and it seems that the majority agrees on a positivity of this change), unless you have strong counter arguments based on facts, I recommend we deliver this PR first.

True, but you can't ignore that you need to marshal/unmarshal event formats/protocol bindings supporting different spec versions, hence the need for an unstructured read/write of the event. Otherwise, each event format/protocol binding must handle by itself the different spec versions

I am not arguing here. But it does not mean that there is only one approach (the current one) to the problem.
For example, have you considered adding getAttributeNames() + getAttribute(String) that will allow iterating over the attributes and essentially give an unstructured view in a very idiomatic Java way, without any unnecessary complexity of visitors and other things that needs to be implemented in the current version? It is similar to the .NET SDK with an exception that we're not using the full Map interface to avoid allocations.

@slinkydeveloper
Copy link
Member

slinkydeveloper commented May 5, 2020

For example, have you considered adding getAttributeNames() + getAttribute(String) that will allow iterating over the attributes and essentially give an unstructured view in a very idiomatic Java way, without any unnecessary complexity of visitors and other things that needs to be implemented in the current version?

I thought about that, but what I don't like is that getAttribute(String) then must return an Object, which is one of the valid cloudevent types [String, ZonedDateTime, URI] or null, so the callee needs to handle that complexity (unless you want to have getStringAttribute, getZonedDateTimeAttribute which is even worse imo). And this is definitely more complex to implement for the CloudEvent implementer more than a visitor (where btw we can already provide a default implementation like in #146). It also breaks the symmetry with protocol binding implementations, making harder to glue protocol bindings <-> event formats <-> event all together.
Also this approach doesn't map well to protocol binding/event format implementations, that still needs a visit like approach for direct transcoding. I find the visitor far more lean to use and implement, and a visitor is definitely Java idiomatic.

@bsideup
Copy link
Contributor

bsideup commented May 5, 2020

the callee needs to handle that complexity

The callee of getAttribute is the logic that will be done the serialization (end users would use typed getters). It can the a middle abstraction (think Message) to abstract away dealing with the types. You write it once anyways. But, as an implementor of CloudEvent, I don't want to think about the serialization, I just return types as they are.

what I don't like

I think we need more here than just a personal taste.

And this is definitely more complex to implement for the CloudEvent implementer more than a visitor

I would not agree with it being definitely more complex. In fact, I think it is the opposite.

where btw we can already provide a default implementation like in #146

If it can be a default method (aka - the implementors are not required to provide anything to make it work), does it even belong to the interface then?

It also breaks the symmetry with protocol binding implementations, making harder to glue protocol bindings <-> event formats <-> event all together.

Was there a symmetry?

byte[] serialized = EventFormatProvider
  .getInstance()
  .resolveFormat("application/json")
  .serialize(event);
byte[] serialized = event
    .asStructuredMessage(format)
    .visit(new KafkaSerializerMessageVisitorImpl(headers))
byte[] serialized = data
    .asBinaryMessage()
    .visit(new KafkaSerializerMessageVisitorImpl(headers));
CloudEvent event = EventFormatProvider.getInstance()
    .resolveFormat(JsonFormat.CONTENT_TYPE)
    .deserialize(bytes)
MessageUtils.parseStructuredOrBinaryMessage(
    () -> KafkaHeaders.getParsedKafkaHeader(headers, KafkaHeaders.CONTENT_TYPE),
    format -> new GenericStructuredMessage(format, payload),
    () -> KafkaHeaders.getParsedKafkaHeader(headers, KafkaHeaders.SPEC_VERSION),
    sv -> new KafkaBinaryMessageImpl(sv, headers, payload),
    UnknownEncodingMessage::new
).toEvent()

I only see a variety of ways to serialize/deserialize CloudEvents. Some are using methods on the interface, some accept the event itself, some make use of the Message abstraction.

A true symmetry would be 3 types:
CloudEvent - a lean interface that follows the spec
Function< CloudEvent, Message> to convert it to Message (for future serialization)
Function<Message, CloudEvent> to convert an instance of Message to an instance of CloudEvent (I don't really care what it will use for the implementation, be it CloudEventImpl, or CloudEventFromMessage, or even class Message implements CloudEvent - we have an interface for that)

@olegz
Copy link
Contributor Author

olegz commented May 5, 2020

While public support for this PR has already been expressed by members of java community representing several large organizations including organization behind the maintainers of this repository, as well as members representing other non-java communities as it was evident on the CNCF call we had last week (30/04/2020) we seem to remain at an impasse. So I am writing this up to primarily raise awareness of such impasse and to provide a summary, so we as a community can find a path forward.
Here is the short summary to assist those who are trying to make sense of this long discussion.

Non of the engineers behind this PR ever questioned or argued the fact that SDK must:

  • support core parts of the specification
  • support any optional parts of the specification including conversion between the versions of the spec, serialization/deserialization etc. . .
  • other functionality that would assist users in dealing with Cloud Events

These and more are the responsibility of the Cloud Events SDK, period.

But SDKs consist of many moving parts designed to collaborate with one another and in relation to CloudEvents Java SDK this PR is addressing one such moving part, and that is CloudEvent Java Interface which consequently is at the core of this impasse.

The emphasis here is on Java Interface, which means we need to take into consideration certain design principles that are widely followed in Java community as we would be expected to do the same for communities representing other languages and runtime environments

And while there are many that could be applied here, the core design principles that are questioned by the opposition to this PR are Single Responsibility and Interface Segregation.
These principles simply ensure that interface stays clear and concise in defining its responsibility (note the use of singular in 'responsibility') and that it does not force user to implement something that may not be relevant (e.g., optional features) to such user in the context of current execution.

So what this PR does is the following:

  • Defines a Java Interface to represent the core abstraction of the Cloud Event specification which is CloudEvent itself, by exposing its payload, attributes and extensions as defined in the core specification.
  • Moves operations that deal with optional parts of the specification as well as other miscellaneous operations to a temporary utility class until we had a chance to have a fair design discussion to find a more appropriate place for such functionality. ALSO NOTE: The functionality in question was never in the CloudEvent in the first place and was added there several weeks ago.

There is nothing else in this PR. It does not remove any features and it does not break any test.
It simply defines CloudEvent type without making any assumptions on who implemented it or how or where it will be used.

So, we are looking for suggestions from senior members of the greater Cloud Events community on how to move forward, so tagging a few that I know, but please feel free to share it @clemensv, @duglin @jamesward @n3wscott

@olegz
Copy link
Contributor Author

olegz commented May 21, 2020

While the limited scope of this PR is outlined in the last comment, we do realize that there are still several points of contention here. However, by looking at some of the individual comments on these contentious points, it also appears that we are closer to an agreement than we think (if we look at each of them separately).

So, here is the outline of the main points of contention:

  • builder methods should be consolidated and moved to versioned CloudEventBuilders which appears to be a source of agreement. (this PR puts them in general utility class).
  • operations that deal with encoding of CloudEvent to binary/structured representation should be encapsulated in the appropriate encoding utility (e.g., MessageEncoder or CloudEventEncoder) and not some general utility class as proposed by this PR (see previous point).
  • ability to access individual attributes as well as iterator to all. This one also appears to be the source of agreement. (the iterator access is missing from this PR)

To address these point we are proposing the withdrawal of this PR in favor of 3 independent PRs corresponding to the 3 following issues which address the 3 points of contention outlined above.

  1. Enrich and consolidate CloudEventBuilder(s) to ensure they encapsulate all functionality necessary to build Cloud Events.
  2. Define message encoder to encapsulate functionality to deal with encoding CloudEvent to binary or structured representation.
  3. Enrich attributes exposure on CloudEvent interface to support individual access as well as iteration over collection of attributes.

@slinkydeveloper
Copy link
Member

Next week I'll try to sum up a proposal with all the interfaces in a separate temporary module. I would say we should try to find an agreement on that, so then we can then plumb the interfaces with the actual implementation of all sdk pieces

@slinkydeveloper
Copy link
Member

Here it is: #155

@olegz
Copy link
Contributor Author

olegz commented May 21, 2020

Thank you Francesco!

While we may continue our debate about value of visitors in general, we can now do it some other time ;)
The #155 is sufficient to address key concerns expressed in #143

public interface CloudEvent extends CloudEventAttributes, CloudEventExtensions {..}
public interface CloudEventAttributes {..}
public interface CloudEventExtensions {..}

Exactly what we've been advocating for. Clean, concise and free of any assumptions.
I also see that you renamed the module to '-api’, which is also good and will keep things lighter.

Great to be moving forward!

@slinkydeveloper
Copy link
Member

slinkydeveloper commented May 21, 2020

@olegz if you agree with that, please state explicitly in that pr so we can merge and move forward. I'm gonna do 2/3 prs in the next week to re-glue things together

@olegz
Copy link
Contributor Author

olegz commented May 21, 2020

@slinkydeveloper i certainly will, give me till tomorrow morning. Getting late here ;)

@olegz
Copy link
Contributor Author

olegz commented May 22, 2020

The core concerns expressed in this PR are now addressed in #155, as changes proposed by this PR are included in #155.
The #155 also defines a separate -api module which provides a clear path for multiple CloudEvent implementation to co-exist.

CloudEvent is now clean, concise and free of any assumptions.

Rejecting and closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants