Skip to content

A CloudEvent should be readonly but provide a way to augment itself #233

Closed

Description

@lance

With recent API changes, a CloudEvent is nicely represented as a plain old JS object. For example, for most purposes, this is actually a valid CE.

const ce = {
  source: '/',
  type: 'example',
  specversion: 'v1.0',
  id: 'asd0aan3412juv'
}

However, because we want to have spec compliance checks, and provide default values (e.g. a UUID or timestamp), we have a constructor and a class. The example above can be written as:

const ce = new CloudEvent({
  source: '/',
  type: 'example',
});

Here, code in the constructor is providing default values for id and specversion.

However this introduces some tricky edge cases. For example, extensions. According to the spec, extensions must exist as siblings of the CE properties. And JS being as loosey goosey as it is, this means a user can do some things outside of the spec boundaries. For example

const ce = new CloudEvent({ source: '/', type: 'example' });
ce[extension-1] = // <- note invalid extension name
  {
    foo: 'bar' // <-- note invalid extension value
  };

A solution to this is to

  1. At the bottom of the CloudEvent constructor, call this.validate()
  2. Make the CloudEvent object read only just after validation with Object.freeze(this);
  3. Provide a "builder" of some kind that enables cloning an event with new properties/extensions, e.g. myEvent.cloneWith( { extension: 'value' }); (this could be a function on CloudEvent or a class unto itself.

Note that both #228 and #229 should be implemented and added to the validation step as a part of this.

Related: #29

Activity

  1. lholmquist commented on Jun 30, 2020

    @lholmquist
    Contributor

    Sounds good. Going to assign to myself

    In the example, you are just talking about extensions, but i'm assuming this could go for any other value, like source or type or version, etc..?

  2. self-assigned this
    on Jun 30, 2020
  3. lance commented on Jun 30, 2020

    @lance
    MemberAuthor

    In the example, you are just talking about extensions, but i'm assuming this could go for any other value, like source or type or version, etc..?

    Yes. Though it's obviously not required, since using Object.freeze() doesn't prevent the object's properties from being changed. I could, for example, change the timestamp on a "readonly" CE. freeze() just prevents adding and removing properties. We could get fancy and use TS to make the properties themselves readonly. I'm pretty sure under the covers, TS just uses the property descriptor to do that. Hmm... maybe this should be part of the change to really enforce the readonly aspect of the object.

  4. lholmquist commented on Jul 1, 2020

    @lholmquist
    Contributor

    Something i was thinking about was trying to use a Proxy for this instead of Freezing the object and having another method that would be needed to "update" the CloudEvent.

    This way, whenever a property is updated, the Proxy can "trap" the set call and run the validate method.

    So doing something like this:

    const ce = new CloudEvent({.....});
    
    ce['addingNewExtenstion'] = 'new extenstion' 
    

    would trigger the validate method

  5. lholmquist commented on Jul 1, 2020

    @lholmquist
    Contributor

    Although, doing it this way(using a Proxy) would not satisfy issue #29

    @lance @grant any thoughts on using the Proxy approach. I'm sort of leaning toward the Proxy approach if it works(testing things out now) instead of having a separate method to update the object

  6. lholmquist commented on Jul 1, 2020

    @lholmquist
    Contributor

    just realized not everyone might know what this Proxy object is, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

  7. grant commented on Jul 1, 2020

    @grant
    Member

    How about we make CloudEvents immutable like they aught to be and use Object.preventExtensions(),
    and make it really easy to construct new CloudEvents?

  8. grant commented on Jul 1, 2020

    @grant
    Member

    I think #29 should be addressed first before this issue.

  9. lholmquist commented on Jul 1, 2020

    @lholmquist
    Contributor

    After doing a bit of research on Proxies and a WIP PR #234 , it might be easier/more efficient to have a method an a cloudEvent that takes fields to update and merges then with the existing CloudEvents properties and then returns a new CloudEvent.

    sort of what @lance suggested with myEvent.cloneWith( { extension: 'value' }); :)

    While i do like the proxy approach, the implementation was starting to get a bit convoluted.

  10. grant commented on Jul 1, 2020

    @grant
    Member

    I would expect CloudEvents to be lightweight and immutable.

    If you want to clone or slightly modify a CloudEvent, I'd propose this radical idea:

    Create a CloudEvent from a CloudEvent (with additional overridden attributes):
    https://github.com/cloudevents/sdk-javascript/blob/master/src/event/cloudevent.ts#L42

    const modifiedCE = new CloudEvent(oldCE, { id: 'my-new-id' });

    Or I could imagine a new clone(overrides) method.

  11. lholmquist commented on Jul 1, 2020

    @lholmquist
    Contributor

    Create a CloudEvent from a CloudEvent

    Yup. Doing something now. Probably sticking to the "cloneWith" method

  12. changed the title [-]A CloudEvent should be readonly but provide a way to augment itsef[/-] [+]A CloudEvent should be readonly but provide a way to augment itself[/+] on Jul 2, 2020
  13. added
    version/3.xIssues related to the 3.0 release of this library
    on Jul 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

module/libRelated to the main source codetype/enhancementNew feature or requestversion/3.xIssues related to the 3.0 release of this library

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions