Closed ilgooz closed 6 years ago
We already have a go client for the application part that can be found in the github.com/mesg-foundation/core/api/client
package
and you can find one use case in this application https://github.com/mesg-foundation/application-devcon-update-on-slack/
It's really basic and definitely need to make it better but this could be a nice base.
I would personally prefer to have it as a package in the core (like that we can keep it in sync with the development of the core) but definitely we can have a library that uses this package and that will have its own readme with documentation etc... that would be more user friendly
Oh okay, I haven't notice that. Thanks!
here is an experimental client with a high level api: https://github.com/ilgooz/mesg-go
Can you please check and confirm the package apis for service and application please? https://godoc.org/gopkg.in/ilgooz/mesg-go.v0
I find it a bit confusing to have the New
and the GetApplication
or GetService
, I understand that the New
doesn't use the singleton but maybe in this case we shouldn't export this function or otherwise use the singleton in the new. I think the idea is to have only one function to call either New
or Get...
but both is a bit confusing
The tasks always have to return the outputKey
and the data
associated to this output so why not having a task handler that need to return a string
and a interface{}
or map[string]interface{}
like that the task just output the results and don't have to explicitly call the req.Reply
.
Continuing on the same idea, why not delegating the req.Get
to the executeTask
and like that we can have the task handler with only the inputs needed for this task.
Like that tasks don't need to know anything about the API, not even the executionID
I'm not really a big fan of the API for the Application. I kind of like the api of the mesg-js lib. I would love to have this simplicity on the go lib too with something like
whenEvent(&Event{serviceID: 'XXX', eventType: 'YYY'})
.Execute(&Task{
serviceID: 'XXX',
taskType: 'YYY',
inputs: func (data map[string]interface{}) map[string]interface {
...
}
})
I know it's less "go style" but it's closest to the future workflow file and I feel it's really accessible.
These are just ideas and definitely open to discussion on all this would love to have the pro and con for all this :)
Totally right. I removed the GetX APIs. I think it's a good idea using New with their options for customization.
TaskRequest.Get
. We have a mesg.yml
file where we can pre-define data types so users already know what data types passed between a service and an application. Thus, providing a native support for decoding those data to go structs is something that we need to provide. Otherwise there will be lots of type asserting from interface{}
or map[string]interface{}
that developers needs to deal with. TaskRequest is like an http.Request
or gin.Context
. And req.Get
is like Request.ParseForm
. It shouldn't be looking too inconvenient to use for Go community.
type TaskResponse interface{} // will be defined in service package
func sendHandler(req *TaskRequest) TaskResponse { var data emailRequest if err := req.Get(&data); err != nil { return emailResponse{ Error: &emailErrorResponse{"Unexpected input data type"}, } }
return emailResponse{
Success: &emailSuccessResponse{"202", "Accepted"},
}
}
type emailRequest struct {
Email string json:"email"
SendgridAPIKey string json:"sendgridAPIKey"
}
type emailResponse struct {
// Output keys holds a pointer for their output data structs
// Only one outputKey with it's data will be sent as a response by
// using a pointer and omitempty
tag.
Success emailSuccessResponse json:"success,omitempty"
Error emailErrorResponse json:"error,omitempty"
}
type emailSuccessResponse struct {
Code string json:"code"
Message string json:"message"
}
type emailErrorResponse struct {
Message string json:"message"
}
### Application API
I'm a big fan of incoming Workflow API(pipelining data from events or tasks to multiple tasks). For now we don't a have decent way of providing a complete workflow API in Go. _- I don't know situation with conf file yet. -_
The only drawback I can see with mesg-js style event based task executions is we cannot execute tasks based on such conditions calculated by the data we gather from event output. I may execute one or many tasks depending on the data I received from an event but that syntax require me to always run a task without a cond. I think either we need to provide a more flexible API or stick with the current one like below:
```go
// Package main is an application that uses functionalities from following services:
// https://github.com/mesg-foundation/service-webhook
// https://github.com/mesg-foundation/service-discord-invitation
package main
import (
"fmt"
"log"
"github.com/ilgooz/mesg-go/application"
)
var webhookServiceID = "v1_4bbafb91f94cd437dc21b5770e986d09"
var discordInvServiceID = "v1_5cd4c617ef5334cfddfb28171de53a9e"
var sendgridKey = "SG.XXX"
var email = "ilkergoktugozturk@gmail.com"
func main() {
app, err := application.New()
if err != nil {
log.Fatal(err)
}
events, err := app.WhenEvent(webhookServiceID).Event("request").Listen()
if err != nil {
log.Fatal(err)
}
for range events {
req := sendgridRequest{
Email: email,
SendgridAPIKey: sendgridKey,
}
var res sendgridResponse
err = app.Execute(discordInvServiceID, "send", req, &res)
fmt.Println(err, res)
// outputs:
// <nil> {{202 Accepted} {}}
}
}
type sendgridRequest struct {
Email string `json:"email"`
SendgridAPIKey string `json:"sendgridAPIKey"`
}
type sendgridResponse struct {
Success *struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"success"`
Error *struct {
Message string `json:"message"`
} `json:"error"`
}
After a long discussion, we propose this API for application:
package main
import (
"fmt"
mesg "mesg-fondation/go-application"
)
func main() {
mesgClient, err := mesg.New(mesg.EndpointOption("localhost:3737"), mesg.LogOutputOption(stdout))
// WhenEvent returns a type that implements Filter, Map and Execute
observable1 := mesgClient.WhenEvent("SERVICE_ID_1",
mesg.KeyEventCondition("EVENT_KEY"),
mesg.DataEventCondition(Data: map[string]interface{}{
"KEY_1": 42,
"KEY_2": "3433",
})
)
// Filter returns a type that implements Filter, Map and Execute
observable2 := observable1.Filter(func(event *mesg.Event) bool {
var eventData serviceID1EventData
fmt.Println(event.Key)
event.Data(&eventData)
return eventData.Name == "hello"
})
// Map returns a type that implements Execute
observable3 := observable2.Map(func(event *mesg.Event) interface{} {
var eventData serviceID1EventData
fmt.Println(event.Key)
event.Data(&eventData)
return &serviceID2TaskInputs{
Method: "POST",
Code: eventData.Code,
Message: eventData.Message,
}
})
// Only the execution function actually do the listening and connect to Core
serviceID2TaskKeyExecution, err := observable3.Execute("SERVICE_ID_2", "TASK_KEY")
defer serviceID2TaskKeyExecution.Close()
for taskExecution := range serviceID2TaskKeyExecution.TaskExecutions {
// taskExecution.executionID
}
}
type serviceID1EventData struct {
Name string `json:"name"`
Code int `json:"code"`
Message string `json:"message"`
}
type serviceID2TaskInputs struct {
Method string `json:"method"`
Code int `json:"code"`
Message string `json:"message"`
}
// THE FOLLOWING CODE SHOULD BE IN GO MESG PACKAGE
type Execution struct {
TaskExecutions chan(*TaskExecution)
Errors chan(*error)
// TaskExecutionErrors chan(*error) Should we have 2 distinct errors chan? Or merge them into 1 err chan?
// EventListenerErrors chan(*error)
eventListener *Stream
}
func (e *Execution) Close() error {
return e.eventListener.Close()
}
With chaining:
package main
import (
"fmt"
mesg "mesg-fondation/go-application"
)
func main() {
mesgClient, err := mesg.New(mesg.EndpointOption("localhost:3737"), mesg.LogOutputOption(stdout))
// WhenEvent returns a type that implements Filter, Map and Execute
serviceID2TaskKeyExecution, err := mesgClient.WhenEvent("SERVICE_ID_1",
mesg.KeyEventCondition("EVENT_KEY"),
mesg.DataEventCondition(Data: map[string]interface{}{
"KEY_1": 42,
"KEY_2": "3433",
})
)
.Filter(func(event *mesg.Event) bool {
var eventData serviceID1EventData
fmt.Println(event.Key)
event.Data(&eventData)
return eventData.Name == "hello"
})
.Map(func(event *mesg.Event) interface{} {
var eventData serviceID1EventData
fmt.Println(event.Key)
event.Data(&eventData)
return &serviceID2TaskInputs{
Method: "POST",
Code: eventData.Code,
Message: eventData.Message,
}
})
.Execute("SERVICE_ID_2", "TASK_KEY")
defer serviceID2TaskKeyExecution.Close()
for taskExecution := range serviceID2TaskKeyExecution.TaskExecutions {
// taskExecution.executionID
}
}
Thank you for the feedbacks. After some internal discussions too, I've finally came up with 4 new packages for this go client. Please check them out:
Please do following if you want to try out examples/application-quickstart and examples/service-logger.
mesg-core service deploy https://github.com/mesg-foundation/service-webhook
mesg-core service deploy https://github.com/mesg-foundation/service-discord-invitation
examples/service-logger
and do a mesg-core service test
examples/application-quickstart/cmd/main.go
and edit the configuration here.examples/application-quickstart
and do a go run cmd/main.go
We have good amount of test coverage in service, servicetest and application. I'll add tests for applicationtest too.
Do you have an agreement on where to host this package? Under mesg-core, independently or as a two independent packages/repos as service and application?
@ilgooz Nice jobs 👍
We agree to host the packages in 2 repos: mesg-foundation/go-application
and mesg-foundation/go-service
. This way, the version and issues will be managed separately.
Both package should also be named "mesg". So we don't use the common names "application" and "services", and from a outsider developer point of view, it will be simpler to use the lib if its name is "mesg" (type eg: mesg.Result
, mesg.Event
, mesg.Application
)
We will do the same for the "mesg-js" libs.
Closing this issue. Let's continue the conversation on https://github.com/mesg-foundation/go-service and https://github.com/mesg-foundation/go-application
We need a decent Go client, maybe with a name
mesg-go
would be brilliant. :) Any plans on this? I can propose a developer friendly package API within next week.