cloudstateio / cloudstate

Distributed State Management for Serverless
https://cloudstate.io
Apache License 2.0
764 stars 97 forks source link

Golang support #3

Closed viktorklang closed 4 years ago

marcellanz commented 5 years ago

Is there any work already done or stated to support Go? I think I can't spot any, even on other branches. Does "Go Support" mean, having Go as a Client API?

jboner commented 5 years ago

Yes, a client API along the lines of the client APIs in JavaScript and Java (which is essentially a glorified gRPC client). Sorry for the really poor description on this issue.

viktorklang commented 5 years ago

Cool—having support for Go would be awesome!

Go Support means being able to implement CloudState entities in Go, in the same fashion as the JS support and Java support.

You can wire the TCK to test against the reference implementation of the proxy. See the application.conf in the TCK for inspiration.

We can help guiding you on how to hook things up. The best start is to look at either the JS and/or Java support.

-- Cheers, √

marcellanz commented 5 years ago

Thanks for the pointer and offer to help. I like the motivation for the project and would love to see a Go Client – API for it. Let me get a grasp on it by reading the README and existing client API... if I can be helpful I'd be happy to do so.

jboner commented 5 years ago

Sounds great. Thank you.

marcellanz commented 5 years ago

So after reading the README, some parts a few times, as well as code, I come back to your offer to guidance. I bluntly and with my own words re-phrase what I have the impression of the implementation. Coming from JAVA/JVM/Go space mainly I confess not to understand too much about Scala code. So bear with me if I ask too stupid questions or assumptions of mine; I'm sure some are:

The IR (CIR) sems to be paramount to understand expected semantics of a "client". Cloudstate Procotol gRPC definitions together with IR semantics are the solely interface a foreign client API.

A client API implementation is like an SPI implementing gRPC services (technically). So here the API implementation enables entities to get called and treated by the IR semantics of being CRDTs, EventSourced or a Function(?). The Sidecar (in the reference implementation Akka based) beside the CloudState protocol does not know much about a client, gets opaque gRPC domain definitions through discovery and calls the clients "code".

Entity IDs, beside that the entity behaves according to the protocol, are the most concrete property the Akka Sidecar knows about them through its type.

Persistence is done by the Akka Sidecar and transparent for the client. At most the client has a small context it holds and persists domain objects, like to implement snapshots.

What happens on a "Client API" is completely separated from the Akka Sidecar, so if I see "akka imports" in CloudStateRunner.scala there are no shortcuts to any Akka "stuff" and it might be used for convenience (Having the JS API surely prooves that I think).

Am I off the rails or is that kind of accurate (I'm sure I miss a lot oft stuff) what the "API Client" can be characterised of or what it sees from its perspective?

viktorklang commented 5 years ago

@marcellanz In broad strokes—yes. The user's code interfaces with the proxy via the CloudState gRPC protocol, so for every language "implementation" a gRPC server will be running in that process, the proxy will connect to it via the CloudState protocol, and the only interactions via the proxy and the user code's process will be over gRPC. This means that any programming language which can have a gRPC server is a possible candidate to use CloudState. the java-support and the node-support libraries exist to remove the need for every user to implement the CloudState protocol themselves, and to provide a language-appropriate end-user API for the users to write their stateful entities in. This also means that there can be any number of end-user APIs for every language, it just happens that me and James are more fluent in Akka to base the java-support implementation on that.

There are so many benefits to this approach—since the proxy does all the data access, we can keep track of DB-metrics completely isolated from user code metrics (think latencies and such). Since the proxy is given the gRPC protocol for the user's service, it can seamlessly proxy it, and not only that, we can generate HTTP+JSON endpoints for all those gRPC endpoints. The proxy also exposes the users gRPC protocol via gRPC Server Reflection, so all tooling which understands that can use it (think ServerReflection2OpenAPI, or grpc_cli).

marcellanz commented 5 years ago

Thank you.

marcellanz commented 5 years ago

Today I made some progress with a go-support API client with which I'm quite happy for a first stint and a few hours. Having one of the TCK tests green and the EntityDiscoveryManager happy with the EntitySpec it gets back from the go client with the shopping-chart example, including dependency protos.

Its raw and needs lot more work, but I closed some circles and I got into all things I did not knew before. Going forward, I think I'll make all the TCK tests happy and then refactor it into an idiomatic go library.

But for now, its good how it is and I like how things came together.

go_support_002

(CloudStateProxy is satisfied first time with what he gets from Discovery from the "other side")

jboner commented 5 years ago

That's great news. Thanks for the update.

viktorklang commented 5 years ago

That’s very cool news, Marcel! Let us know how it goes, and if you need any input from us. 😊 -- Cheers, √

marcellanz commented 5 years ago

I have the second bullet green on the TCK but I'm pretty sure that the TCK is too kind with the current implementation as it is not complete for sure. I'm currently going down to implement the EventSourced service.

The points where I could get some help are regarding how to build the project (properly).

I honestly don't know how to build the project properly I think from a "sbt" point of view. Either I did something wrong, but I:

I think I understand dependencies in general but with sbt I'm a bit lost :-D

I imported the project in IntelliJ like in the screenshot shown below. I build with IntelliJ and also tried on the CLI or with "sbt shell" by:

Screenshot 2019-09-02 at 11 01 40

viktorklang commented 5 years ago

Hi Marcel,

you manually add an Akka + Go configuration here: https://github.com/cloudstateio/cloudstate/blob/master/tck/src/it/resources/application.conf And then you run from the command line, in the cloudstate project root directory:

sbt it:test

(stands for integration test)

marcellanz commented 5 years ago

sbt it:test

@viktorklang, that worked, thanks.

viktorklang commented 5 years ago

@marcellanz You're most welcome! I'll make sure that the documentation gets improved in that regard.

akramtexas commented 5 years ago

Cool—having support for Go would be awesome! Go Support means being able to implement CloudState entities in Go, in the same fashion as the JS support and Java support. You can wire the TCK to test against the reference implementation of the proxy. See the application.conf in the TCK for inspiration. We can help guiding you on how to hook things up. The best start is to look at either the JS and/or Java support. -- Cheers, √

This is going to be so 🆒

viktorklang commented 5 years ago

@akramtexas I can only agree! ^^

marcellanz commented 5 years ago

This evening was pretty productive. After two longer code reading sessions with the implementation of EventSourcedImpl I got into how things have to work I think and then after having GetCart implemented for the first time and running with the TCK, most of the rest of the shopping cart example went quicker and I had some fun.

Accordingly to the TCK, the implemetation lacks proper handling of events through EventSourcedReply and I don't yet support snaphots.

The implementation for now is completely in PoC quality and I have to find out how the API might look like from a "API client" user perspective. Then having proper build integration into the project and some documentation will be next. And then the CRDT (new for me) and the function protocol., and I'm sure I missed some stuff.

Screenshot 2019-09-06 at 00 21 40 (TCK nearly completely green)

Screenshot 2019-09-06 at 00 21 53 ( the flow of TCK."verify that items can be added to, and removed from, a shopping cart" )

viktorklang commented 5 years ago

Great to hear that you're making progress. The failed assertion is making sure that it gets the correct number of expected events back. Let me see how I can improve the TCK output to better guide the implementor in the right direction. :)

marcellanz commented 5 years ago

Go has an opinionated behaviour when it comes to maps iteration order.

Since Go 1.0, iterating maps has an unpredictable and random order when it comes to iterating maps and, depending on the implementation of stuff, the following can happen

Screenshot 2019-09-06 at 22 38 55

The TCK here expects the order of a shopping carts LineItems to be predictable, which isn't for this implementation.

Go being opinionated in this brought some eyebrows up sometimes, even if defined in the Spec: The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next.

But I find it refreshing that it forces now the question if the TCK should respect that from an implementation @viktorklang

viktorklang commented 5 years ago

@marcellanz That's a great question! In this case, isn't it the iteration order of a sequence though?

ghost commented 5 years ago

Good morning Marcel

I was planning to start from scratch with this task a week ago, but I've seen that you've been advancing a lot of work. If you're happy with sharing with me your code I can have a look at it and start contributing there.

Regarding to your last input, I think it might be helpful to solve this problem if you used channels (https://tour.golang.org/concurrency/2) to guarantee the messaging order?

Kind Regards

Arturo

akramtexas commented 5 years ago

Good morning gentlemen 🌞: @marcellanz, @viktorklang, @ArturoTarinVillaescusa

@marcellanz - I like a lot two of your observations in particular, both spot on 🎯

  1. Go has an opinionated behavior when it comes to maps iteration order.
  2. But I find it refreshing that it forces now the question if the TCK should respect that from an implementation @viktorklang
    • Oh yes, Go has raised some eyebrows for sure 😉
    • Here's some thinking—followed by a snippet containing a workaround—which you may find helpful in solving the ordering (aka sequencing) problem at hand when working with Go maps:

      The Go language designers noticed that people were relying on the fact that keys were normally stored in the order they were added in, so they randomized the order in which the keys are iterated over. Thus, if you want to output keys in the order they were added in, you need to keep track of which value is in which position in the order yourself like so:

      
      import "sort"

var m map[int]string var keys []int for k := range m { keys = append(keys, k) } sort.Ints(keys) for _, k := range keys { fmt.Println("Key:", k, "Value:", m[k]) }


- More details here at the following, excellent article, online: [A Surprising Feature of Golang that Colored Me Impressed](https://nathanleclaire.com/blog/2014/04/27/a-surprising-feature-of-golang-that-colored-me-impressed/) - I hope this helps...
- Good luck 🍀 
akramtexas commented 5 years ago

Also please check out: go-maps-in-action 🗺

marcellanz commented 5 years ago

I was planning to start from scratch with this task a week ago, but I've seen that you've been advancing a lot of work. If you're happy with sharing with me your code I can have a look at it and start contributing there.

Hi @ArturoTarinVillaescusa. Sure, I'll be happy to share. I currently work to get the current TCK 100% passed and having a proposal for the client API. I'm not comfortable to share the state of code I have at the moment, but with the progress I made the last few days I'm pretty sure I can propose a first version of the go api client with which we can move on sometimes this week or the end of it.

There is plenty of stuff to do and I'm sure and looking forward to any contributions to it :-) Next steps will be CRDTs and the function protocol I think or anything the community is thinking of.

Regarding to your last input, I think it might be helpful to solve this problem if you used channels (https://tour.golang.org/concurrency/2) to guarantee the messaging order?

The order of the shopping cart items that get back by issuing the ShoppingCart#GetCart command during development of the shopping cart example was backed by a map[string]LineItems where the key of the map was the product_id. I then simply replied by ranging over the values of the map. This had the effect of getting random order of the line items which was affected by Go's random iteration order as I stated in my comment above. I changed that now to be in line with the TCKs expectations.

I'm not quite sure if I understand how channels would assure the order of the lines items added to the cart but I'm happy to discuss and hear what your idea was to do that.

marcellanz commented 5 years ago

Good morning gentlemen 🌞: @marcellanz, @viktorklang, @ArturoTarinVillaescusa @marcellanz - I like a lot two of your observations in particular, both spot on 🎯

  • Here's some thinking—followed by a snippet containing a workaround—which you may find helpful in solving the ordering (aka sequencing) problem at hand when working with Go maps:

The Go language designers noticed that people were relying on the fact that keys were normally stored in the order they were added in, so they randomized the order in which the keys are iterated over. Thus, if you want to output keys in the order they were added in, you need to keep track of which value is in which position in the order yourself like so:

import "sort"

var m map[int]string
var keys []int
for k := range m {
    keys = append(keys, k)
}
sort.Ints(keys)
for _, k := range keys {
    fmt.Println("Key:", k, "Value:", m[k])
}

Good evening @akramtexas. Thanks for the pointers about maps in Go. I was fast implementing the cart using a map and not something like java.util.LinkedHashMap that is used by the java-shopping-cart and then told by the TCK to be wrong. I think its valid to expect the order in the cart to be the same as the line items got into it. I fixed that and now the TCK is happy with that.

Regarding your proposed code, which I captured into a Go Playground snipped: https://play.golang.org/p/PvD5hEsk3Vf I think that snipped does not ensure the order properly. The keys get sorted, but that sorting clears , as an example, the order of something like 3,1,2,4 into 1,2,3,4. How was the snipped to retain the order? Looking forward to your ideas. Best Regards, Marcel

marcellanz commented 5 years ago

This travis build https://travis-ci.com/marcellanz/cloudstate/jobs/232175160 captures my efforts of the weekend to get a 100% pass on the TCK and build integration into the existing travis CI build job.

My goal for the next week is now to have a proposal for the Go Client API by somewhere the end of the week based on this implementation. I also plan to write some documentation similar to existing or planned API documentation of the other language implementations.

I'm looking forward to discussions and future work to get a full implementation of the Go Client API.

Screenshot 2019-09-08 at 22 59 16

(first CloudState TCK 100% pass on travis-ci.org)

viktorklang commented 5 years ago

@marcellanz Wow! Congratulations—the feeling when the TCK passes is a great one, for sure! ^^

marcellanz commented 5 years ago

@viktorklang thanks, it felt great :-)

viktorklang commented 5 years ago

Let me know when you want/need to create a PR! 😊 -- Cheers, √

marcellanz commented 5 years ago

@viktorklang I will. Give me a bit more time to make it "right"…

viktorklang commented 5 years ago

@marcellanz Absolutely! :)

marcellanz commented 5 years ago

@viktorklang I think I will PR this today. I have some tasks left but documented them.

viktorklang commented 5 years ago

Very cool @marcellanz! Please add //TODO //FIXME on everything which you know that might represent edge-cases or otherwise, this makes it easier for others to spot things which may not be optimal yet.

Also, if you have time, it's really great to have a bit of rationale w.r.t. the end-user API, if there are any opportunities for improvements or otherwise. :)

Thanks for spending your time and efforts on this important feature!

marcellanz commented 5 years ago

Very cool @marcellanz! Please add //TODO //FIXME on everything which you know that might represent edge-cases or otherwise, this makes it easier for others to spot things which may not be optimal yet.

I will do and already had on a number of places. I'll go through it again and mark where appropriate.

Also, if you have time, it's really great to have a bit of rationale w.r.t. the end-user API, if there are any opportunities for improvements or otherwise. :)

I started the usual documents under cloudstate/docs/.../user/lang/go. I'll finish it and update the PR with an explaining gettingstarted.md and similar eventsourced.md as the Java one.

viktorklang commented 5 years ago

@marcellanz

I will do and already had on a number of places. I'll go through it again and mark where appropriate.

Perfect, thank you!

I started the usual documents under cloudstate/docs/.../user/lang/go. I'll finish it and update the PR with an explaining gettingstarted.md and similar eventsourced.md as the Java one.

Nice!

marcellanz commented 4 years ago

@marcellanz ...

@viktorklang The PR branch has now documentation for go support.

viktorklang commented 4 years ago

Check here for future/current progress of Go Support in CloudState: https://github.com/cloudstateio/go-support