-
Notifications
You must be signed in to change notification settings - Fork 24
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
Redesign webhook event parsing to avoid event/data conflicts #85
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
weppos
changed the title
Bugfix/panic account webhook2
Redesign webhook event parsing to avoid event/data conflicts
Jun 13, 2019
aeden
requested changes
Jun 13, 2019
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.
There is a typo that needs to be fixed.
dnsimple/webhook/webhook.go
Outdated
} | ||
|
||
func unmashalEvent(data []byte, v interface{}) error { | ||
return json.Unmarshal(data, v) | ||
func unmashalEventData(data []byte, v interface{}) error { |
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.
Suggested change
func unmashalEventData(data []byte, v interface{}) error { | |
func unmarshalEventData(data []byte, v interface{}) error { |
I have no objections to the change in general. |
jodosha
approved these changes
Jun 17, 2019
aeden
approved these changes
Jun 18, 2019
weppos
added a commit
to dnsimple/strillone
that referenced
this pull request
Jun 21, 2019
This was referenced Jun 25, 2019
Merged
arnested
added a commit
to reload/dnsimple-punktum-dk-ds-upload
that referenced
this pull request
Jun 28, 2019
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Closes #83
This is an alternate implementation of #84. It's a significant rewrite of the webhook parsing system. The key difference is how a webhook event is represented.
Current Implementation
In the current implementation, each event category (account event, domain event, etc) was represented by a specific type.
The type is composed by a fixed set of fields, called header, and a variable set of fields, defined directly in the type.
This design was largely inspired by the GO DNS library, where each RR type has exactly the same structure: a header and data.
All event types implemented a particular interface called
Event
. The interface was also used in method definitions to represent any event type. An example, the methodParse
is defined as a method that takes a payload and returns a type that implementsEvent
.Type casting (and type assertion) was required to convert interface types to the concrete implementations.
You can see an example in strillone:
https://github.com/dnsimple/strillone/blob/7471dc6e58bffe6ae597219d63345df4387edc03/message.go#L11-L39
This solution worked, but it occasionally showed issues. For instance, parsing the data node was not trivial and required some hack. That's because the payload structure is not flatten, but the data is nested within the event:
The workaround was to assign the event type struct instance to its own Data field, effectively pointing the value of the Data field of a particular event struct, to the struct itself.
This is where #83 was discovered. In the case of an account event, there is both an Account node in the event payload (the same node is present in any event being delivered), and an account within the data payload. In theory, those two entities are the same, but they have slightly different structure as the account in the data is a full representation of the account, whereas the account in the event is just a reference.
I found a fix in #84, but it was a significant hack and I felt I wanted to try some alternative.
Proposed implementation
The proposed implementation is something I though when I originally created this lib. But I discarded this design years ago because I felt the need to type-case the data was overwhelming, and I was likely tricked by trying to inject an object-oriented inheritance approach into Go.
The truth is that, no matter what, in Go we'll have to type-case if we use event. And composition turned to not work in the way I originally though when it comes to referencing the sub-struct (the header struct in the case of the current implementation).
So this new design is the exact opposite of the current one. Rather than having one struct type per event category, we have a unique type
Event
(currently called EventContainer) and the Data of the event is the part that is strictly typed. In fact, the data is the one that vary depending on the event category.This design allows to keep the data as well the payload internal to the event. It also doesn't require workaround in the parse, as this structure is not flat but reflects the event payload where Data is a node inside the event.
Comparison
The following represent todays implementation of strillone:
With the new code it will become:
The use is quite similar, but both the external and the internal implementation are actually simpler.
Here's another side-by-side comparison:
This proposed alternative also makes it possible to delay the parsing of the event data, if needed. This allows to build an more efficient system especially in the case where you are listening to all events, but you are only interested in few of them.
You can use ParseEvent to get an Event with only the event headers (such as the Name), and call GetData() (to parse the data) only for the events categories the code needs to handle.