-
-
Notifications
You must be signed in to change notification settings - Fork 273
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
Application of message traits (intentionally) replacing existing attributes #505
Comments
Thanks for formulating this @c-pius . I've also brought this problem up in slack a while ago and discussed it with @derberg .
|
Yeah, that's a bit unfortunate but the reason we did that is that we had to choose between JSON Merge Patch or JSON Patch, which will suit this use case perfectly but its UX sucks for this purpose. If we choose to merge arrays, we can end up having duplicate keys. And more importantly, there's no standard algorithm like the ones mentioned above. This is not really a bug but it's true the UX is not great. How do you think we can solve this? I'm wondering if maybe |
Hi @fmvilas, actually I do like that you've chosen to use a standard for this. The problem is the order of merging beeing done, which is opposite to what you might expect. Effectively, the parent overwrites the properties of the child (the trait overwrites the properties of the object where it is applied to) and not vice versa. The merging of arrays is indeed complicated. I ran into this issue as well and usually end up with one of the following solutions:
Regarding a solution: I'm afrait that the specification can't change this behavior without introducing a breaking change and adjusting the various implementations (parser, renderers, etc.). I'd propose to clearly state this behavior (and it's consequences) in the specification and rethink how we want to do information merging / inheritance in the next major version of this specification. In our internal project we decided to just state a clear warning that makes clear that traits do not behave as you'd expect and give some rules when they can be used and when they shouldn't. Best regards, |
Would any of you mind opening a PR to add it to the spec?
Don't worry about breaking changes. Go ahead and create a proposal on how to solve it. We're currently changing the way to contribute to the spec, in case you're interested: #511. |
@fmvilas yes, I can create a PR to clarify this (just need to find a bit of time for this). Here is a very quick draft, but I didn't have time to have a look at the contribution guidelines yet. But I'll catch up on that. PR: #517 Regardings the proposal how to solve it, this would take a bit more thought and time. |
I didn't find time to propose the new behavior via PR, but some first ideas and sketches (still hacky and just a basis for further discussion and refinement):
Here is some JavaScript Snippet (See JSFiddle): const targetObject = {
// I don't want this to be overwritten when applying traits
type: 'my message type',
messageObject: true,
}
const trait1 = {
source: 'inherited source from trait 1',
type: 'type from trait1 that I dont want to inherit',
trait1: true,
someObject: {
trait1: true,
},
someArray: ['trait1']
}
const trait2 = {
source: 'inherited source from trait 2',
trait2: true,
someObject: {
trait2: true,
},
someArray: ['trait2']
}
const currentBehavior = _.merge({}, targetObject, trait1, trait2);
console.log('Current trait inheritance', currentBehavior)
const proposedBehavior = _.merge({}, trait1, trait2, targetObject);
console.log('Proposed trait inheritance', proposedBehavior)
// The _.merge function will overwrite primitive types, deep merge objects (recursively) and overwrite (not merge!) arrays. |
To me this proposal looks like it goes in to the direction of what I would intuitively expected from traits. As of the current porposal, is this not just the same as with json-merge-patch but with a different order (as I understood it it also deep merges objects and replaces arrays)?
would change to something like
However, for the given problem with the required array, this would still not actually solve the problem (unless we define how arrays are merged instead of replaced). Still an improvement I guess. |
Yes, I also interpreted the JSON Merge patch behavior that arrays get replaced and not merged there as well. This would correspond to the simpler merge algorithm I stated above. See https://tools.ietf.org/html/rfc7386#section-3 But I agree, for JSON Schema |
yes I exactly. The point I wanted to make is that the suggested algorithm (without merging arrays) is just the JSON Merge Patched applied in a different order than as of now |
I agree with everything here actually, @Fannon your example is plenty enough to work on the general "fix" for the parser 👍 Well explained. I think it makes sense to alter the definition of traits in the specification 👍 Related function in JS Parser: https://github.com/asyncapi/parser-js/blob/8dc1d6cfb49a6a87732b9bdd996d4092b5e111fe/lib/parser.js#L226 |
After some fiddling around, I realized that the This behavior is not what I would have expected at first and is likely not desirable for trait inheritance. As the currently defined JSON Merge Path algorithm overwrites arrays, I'd like to keep this behavior. Here is my current WIP idea on how to define the trait inheritance: #532 See tiny-merge-patch implementation: https://github.com/QuentinRoy/tiny-merge-patch/blob/master/esm/index.js |
This issue has been automatically marked as stale because it has not had recent activity 😴 |
Here is another example for an inheritance use-case (based on @c-pius example) that we can discuss on the AsyncAPI meeting today: asyncapi: 2.1.0
info:
title: Animals API
version: 1.0.0
channels:
birdchannel:
publish:
message:
$ref: '#/components/messages/birdMessage'
components:
messages:
birdMessage:
traits:
- $ref: '#/components/messageTraits/animalTrait'
headers:
type: object
description: 'A bird!'
properties:
type:
type: string
enum: ['Bird']
chirp:
type: string
required:
- name
- type
- chirp
schemas:
testSchema:
type: object
properties:
name:
type: string
messageTraits:
animalTrait:
headers:
type: object
description: 'An (abstract) animal'
properties:
name:
type: string
type:
type: string
enum: ['Bird', 'Dog']
required:
- name
- type |
And to add a real use-case and not a contrived one: If you use AsyncAPI for describing CloudEvents, there will be some standardized header messages for each event. It would be really convenient to create one trait that describes all the CloudEvent typical headers. However, if you do this with a trait with the current behavior, you can never overwrite anything that you inherited, e.g. to make it more specific. Especially problematic is the |
Yeah, the CloudEvents example is a good one. We're making some great progress with the publish/subscribe issue that will be fixed in version 3.0.0. I think we should aim for changing this behavior for v3 but would not like to then find out we're breaking other more common use cases. I don't think so but I'm being fear-driven here I guess 😄 |
No, I totally get you @fmvilas. For a spec it's really important to think this through properly and avoid making rash decisions. |
This issue has been automatically marked as stale because it has not had recent activity 😴 It will be closed in 120 days if no further activity occurs. To unstale this issue, add a comment with a detailed explanation. There can be many reasons why some specific issue has no activity. The most probable cause is lack of time, not lack of interest. AsyncAPI Initiative is a Linux Foundation project not owned by a single for-profit company. It is a community-driven initiative ruled under open governance model. Let us figure out together how to push this issue forward. Connect with us through one of many communication channels we established here. Thank you for your patience ❤️ |
@Fannon as you already are in progress of championing this, do you see this as needing to be considered for 3.0? |
@jonaslagoni : Yes, moving this to a 3.0 release makes sense as we have the chance to rethink this without worrying too much about incompatibility. Right now, it's somewhat undefined behavior. The proposals we discussed and preferred would change the behavior (which would be a breaking change). |
@Fannon do you mind if I add you to the list of champions for 3.0? Joining the meetings would give you a good oppotunity to give updates and get feedback with no preasure 🙂 |
Yes, let's try it :) |
This issue has been automatically marked as stale because it has not had recent activity 😴 It will be closed in 120 days if no further activity occurs. To unstale this issue, add a comment with a detailed explanation. There can be many reasons why some specific issue has no activity. The most probable cause is lack of time, not lack of interest. AsyncAPI Initiative is a Linux Foundation project not owned by a single for-profit company. It is a community-driven initiative ruled under open governance model. Let us figure out together how to push this issue forward. Connect with us through one of many communication channels we established here. Thank you for your patience ❤️ |
At what stage do we have this proposal? I have now encountered this problem myself, and it surprises me that traits have a higher priority than the main object itself 😆 I understand that we want to fix the behaviour of traits in v3. @jonaslagoni @Fannon |
@magicmatatjahu : I'm not that actively involved in this anymore, sorry for that. My feeling was that we lacked a bit a consensus on how to move this topic forward. Changing the existing behavior would be a breaking change, so it needs to be done with AsyncAPI 3. But if we redo this, we have multiple options, I tried to outline my own proposals here: #532 (comment) |
@Fannon Thanks a lot! You did so much and we are grateful! I also think B2 would be a better option, but if it introduces problems then B1 is good too - the way of apply the traits themselves is more important than the details like the way to apply the arrays but we could improve that too :) |
@magicmatatjahu You created the PR on the spec side so I understand you wanted to champion this. The PR has some feedback pending to be addressed. Are you still willing to champion this for v3? |
Yes it does. #907 (comment) |
This one is just missing the JS parser implementation. Anyone volunteering? |
This issue has been automatically marked as stale because it has not had recent activity 😴 It will be closed in 120 days if no further activity occurs. To unstale this issue, add a comment with a detailed explanation. There can be many reasons why some specific issue has no activity. The most probable cause is lack of time, not lack of interest. AsyncAPI Initiative is a Linux Foundation project not owned by a single for-profit company. It is a community-driven initiative ruled under open governance model. Let us figure out together how to push this issue forward. Connect with us through one of many communication channels we established here. Thank you for your patience ❤️ |
Thanks goes to you for your help and follow-up in the issue. |
Describe the bug
According to specification, message traits are applied using
json-merge-patch
protocol.The implication of this is that properties defined in a trait will "override" those defined in the message itself. I am not sure if this is actually a bug (it is just how
json-merge-patch
works) but I personally think this is a bit unfortunate.A concrete scenario where I run into troubles is that I have a trait that is defining some standard message headers where some of them are required. Then I have several messages that include some specific header attributes per message where also some of them are required. Now the problem is, that when the trait is applied, the required attributes from the message are replaced by the ones from the trait (see the examples below for more clarity).
Of course I could also just remove the required array from the trait and add them into the message, but then the trait is not really self-contained anymore... Therefore I wanted to check if this behavior of traits is really the intended one? Or if there are any other mitigations I could take?
To Reproduce
Steps to reproduce the behavior:
parser-js
Expected behavior
A clear and concise description of what you expected to happen.
Sample document
Screenshots
If applicable, add screenshots to help explain your problem.
Additional context
Add any other context about the problem here.
The text was updated successfully, but these errors were encountered: