-
Notifications
You must be signed in to change notification settings - Fork 10
Go support for CloudState User Functions #5
Conversation
# Conflicts: # README.md
- handleCommand cleanup - added codecov to travis build - refined snapshot handling - snapshot support without tests - implemented protobuf any serialization by CloudState conventions - added benchmarks for CloudState serialization - cleaned up documentation - travis build for other OS (win,linux, macOS) - refactored EventSourcedHandler.handleCommand - aligned and cleaned up failure handling
# Conflicts: # README.md
…t on first implementation.
Codecov Report
@@ Coverage Diff @@
## master #5 +/- ##
=========================================
Coverage ? 51.78%
=========================================
Files ? 9
Lines ? 645
Branches ? 0
=========================================
Hits ? 334
Misses ? 255
Partials ? 56
Continue to review full report at Codecov.
|
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.
First review pass :). There's a lot here so will take another pass tomorrow. In addition to my comments I had a few other general thoughts:
- It would be nice if we can run the TCK in this repo so we get immediate feedback from changes whether we broke anything. But not sure how feasible that is, at least to do now.
- Need to think how we handle the go "logging problem" where we need to give some level of control to the user over logging.
- I think the API needs some improvements which is where my first pass was focused on but I didn't quite get to everything.
Bear with me as I'm also fairly new to cloudstate, so I'm learning the protocol as well as I go.
persistenceID = e.PersistenceID | ||
} | ||
r.entitySpec.Entities = append(r.entitySpec.Entities, &protocol.Entity{ | ||
EntityType: EventSourced, |
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.
Is there any way to get the value of EventSourced
from the service descriptor?
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.
I think we can't. Like for the other comments about a type safe way to get the service descriptor, we can't (see below the example for _EventSourced_serviceDesc). I explained this also in the then Draft - PR: cloudstateio/cloudstate#103 (comment) on how to access this information. The comment also mentions a few partly open Issues on the gRPC project for Go.
event_sourced.pb.go
var _EventSourced_serviceDesc = grpc.ServiceDesc{
ServiceName: "cloudstate.eventsourced.EventSourced",
HandlerType: (*EventSourcedServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "handle",
Handler: _EventSourced_Handle_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "cloudstate/event_sourced.proto",
}
@jsravn do you see any other way to do that? Tho, I've seen the https://github.com/gogo/protobuf project and that it is more open in this regard, but I have not tried it.
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.
I think gogo is only protobuf 2? It's been a while since I looked at it.
It does seem like golang protobuf is fairly anemic, unfortunately.
But I think we can do this with protoreflect:
func (r *EntityDiscoveryService) registerEntity(e *EventSourcedEntity, config DescriptorConfig) error {
if err := r.resolveFileDescriptors(config); err != nil {
return fmt.Errorf("failed to resolveFileDescriptor for DescriptorConfig: %+v: %w", config, err)
}
persistenceID := e.entityName
if e.PersistenceID != "" {
persistenceID = e.PersistenceID
}
fd, err := desc.LoadFileDescriptor("cloudstate/event_sourced.proto")
if err != nil {
return err
}
r.entitySpec.Entities = append(r.entitySpec.Entities, &protocol.Entity{
EntityType: fd.FindService("EventSourced").GetFullyQualifiedName(),
ServiceName: e.ServiceName,
PersistenceId: persistenceID,
})
return r.updateSpec()
}
Sorry about the close/reopen noise, was just testing the CLA checker integration. |
@marcellanz does it make sense to enable codecov for this repo? I've never enabled it before, if you could point me to instructions that would be helpful. |
@jroper I haven't used it before and configured the simplest way I found through their bash uploader https://docs.codecov.io/v4.3.0/docs/about-the-codecov-bash-uploader which does not need a token configured because of the public repo. I simply used their proposed integration I added the badge to the README.md and my intention was to have just this badge together with two others there (godoc, travis build). I think the comment on the pull request is not needed, but having test coverage stats on a PR seems to be nice to have, even if its coverage at the moment is not that great. So in this regard, I think you don't have to configure anything for the repo. |
No worries - note though that we could have everything more automatic, with webhooks and commit statuses, if desired. |
@jsravn Thanks a lot James for this thorough review of the PR, I appreciate it lot. I'm away this week but I'm on the contributors call today if we might want to discuss certain issues. I think especially of the "logging problem" where we could define how to do it for language support libraries in general. I'm looking forward to your 2nd pass :-) |
@jroper all right, let me know if we should have a closer look to codecov where it helps and how to integrate it best. |
Hi @marcellanz, no problem :). I'd like to get this PR merged soon so we can collaborate more easily. The logging can probably be done in another PR - this PR doesn't need to be perfect. I was unable to make the call yesterday, but I will try to make next week. |
@jsravn Great, to get up-to-date with the calls, there are minutes available too https://docs.google.com/document/d/18_gS04R2ELAVmatW2nN5iR5PRmYqzQDbtQeS41oOIys |
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.
Okay, I finished my second pass and so I think I'm mostly done w/ my review.
I think my main concern on this pass is the implicitness of the API. There is a lot of runtime stuff going on (reflection finding of methods by convention, runtime casts to interfaces), that doesn't feel like idiomatic go to me. I think we should err on the side of explicitness - right now it's not very obvious "what to implement" on the event sourced entity.
I'd like to see a single interface with the required handler methods on it that the EventSourcedEntity
needs to implement (including handleCommand, handleEvents, handleSnapshot). Then it becomes clear to a user what they should implement and the control flow is more obvious and explicit. It will also be a compile error rather than runtime if something is missing. I also think we should really avoid finding methods by convention, matching against their types with reflection - I haven't seen that kind of approach in a mainstream golang library, and I think it may dissuade users from using it.
for { | ||
if failed != nil { | ||
return failed | ||
} |
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.
How come we close the stream if the user function returned an error? Shouldn't we just return the protocol.Failure
and keep processing the stream?
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.
I think this here is ok; handleFailure
will handle every error out of any Command, Event or Init msg and itself sends a protocol.Failure
and if sending this failure does not work, we close the stream.
But: I see now handleFailure
lets the failure through if it does not match. Will fix this.
Edit: while thinking more about this I'm not sure if an error (failure) that is not a cloudState.ErrFailure or cloudstate.ErrClientActionFailure as in handleFailure
checked indeed should close the stream as it is an error we would not expect.
|
||
type SnapshotHandler interface { | ||
HandleSnapshot(snapshot interface{}) (handled bool, err 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.
I wonder how we can make this a bit more explicit. In other words there is no compilation error if a user doesn't implement these functions - only at runtime. Maybe we should require the event sourced entity to implement them all?
Great feedback and input on this PR. I'm still on some days off but will have a look soon on it. |
@marcellanz Is this PR ready to merge? /cc @jroper @jsravn |
@viktorklang I'd say we can merge it tomorrow, perhaps after the contributors call. James had some great input and I made some changes accordingly. I'll push that tomorrow evening. |
@marcellanz Awesome, looking forward to the call! |
Ship it! |
* [go-support] as discussed in weekly call 2019-10-08: go_packages added to the main repo. * [go-support] paradox documentation for go-support. * [go-support] enable the TCK to run the shopping cart example for Go. * [PR Review Feedback] first batch of changes after review of cloudstateio/go-support#5 * [go-support] Update doc and config to Cloudstate instead of CloudState * [PR-review] fixes and issues addressed from the review feedback as well as API changes reflected from the latest updates to the go-support repo. * [PR-review] cross-PR review changes from go-support PR #10
This PR implements Go support as a CloudState API Client Library and should fix cloudstateio/cloudstate#3 to a good amount.
Included is
Before this separate repository was created, a previous Draft-PR was opened and reviewed partly.