diff --git a/Gopkg.lock b/Gopkg.lock index 3161270..65e7462 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -10,7 +10,7 @@ revision = "3a0bb77429bd3a61596f5e8a3172445844342120" [[projects]] - digest = "1:4c0989ca0bcd10799064318923b9bc2db6b4d6338dd75f3f2d86c3511aaaf5cf" + digest = "1:f5ce1529abc1204444ec73779f44f94e2fa8fcdb7aca3c355b0c95947e4005c6" name = "github.com/golang/protobuf" packages = [ "proto", @@ -20,8 +20,8 @@ "ptypes/timestamp", ] pruneopts = "UT" - revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" - version = "v1.2.0" + revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7" + version = "v1.3.2" [[projects]] branch = "master" @@ -229,7 +229,6 @@ "github.com/opentracing/opentracing-go", "github.com/opentracing/opentracing-go/ext", "github.com/opentracing/opentracing-go/log", - "github.com/uber/jaeger-client-go", "github.com/uber/jaeger-client-go/config", "go.uber.org/zap", "go.uber.org/zap/zapcore", diff --git a/README.md b/README.md index df5397a..f62be18 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,50 @@ ACE SDK allows developers to implement microservices that can be used as executa Implement the callback method that will process the received message. Below is the signature of the method ``` -func businessMessageProcessor(ctx context.Context, businessMsg *messaging.BusinessMessage, msgProducer linker.MsgProducer) error +func businessMessageProcessor(executionContext linker.ExecutionContext) error +``` + +### Execution Context + +Get the Open Tracing Span Context + +``` +ctx := executionContext.GetSpanContext(); +``` + +Get the Business Message list + +``` +businessMsgs := executionContext.GetBusinessMessages(); +``` + +Get the Message Producer Function + +``` +msgProducer := executionContext.GetMsgProducer(); +``` + +Get a String Config Value + +``` +stringParamNameValue := executionContext.GetStringConfigValue("stringParamName"); +``` + +Get an Int Config Value + +``` +intParamNameValue := executionContext.GetIntConfigValue("intParamName"); +``` + +Get a Boolean Config Value + +``` +boolParamNameValue := executionContext.GetBooleanConfigValue("boolParamName"); ``` ### Input processing -The businessMsg parameter of type messaging.BusinessMessage identifies the business message and holds the payload and the metadata associated with the payload. +The businessMsgs is an array of type messaging.BusinessMessage. Each identify a business message and holds the payload and the metadata associated with the payload. #### Processing payload @@ -82,6 +120,24 @@ span.LogFields(opentractingLog.String("event", "processed message")) span.Finish() ``` +## Add log lines (Optional) + +ACE SDK sets up a log object that can be used by the service to add additional logging to the console. Using the provided logger will write the lines consistent with the ACE SDK. + +A call to the log method and level will include a message and additional fields provided with zap logger calls. The SDK provides field names that may be used. + +``` +import "github.com/Axway/ace-golang-sdk/util/logging" +var log = logging.Logger() + +log.Debug("Business service config", + zap.String(logging.LogFieldServiceName, cfg.ServiceName), + zap.String(logging.LogFieldServiceVersion, cfg.ServiceVersion), + zap.String(logging.LogFieldServiceType, cfg.ServiceType), + zap.String(logging.LogFieldServiceDesc, cfg.ServiceDescription), +) +``` + ## Handling errors in the service execution ACE SDK had three error types defined. @@ -111,6 +167,9 @@ The service registration needs following details - Service Name - Service Version +- Service Type + - NATIVE + - AGGREGATION - Service Description - Callback method diff --git a/linker/executionContext.go b/linker/executionContext.go new file mode 100644 index 0000000..d1e6e7f --- /dev/null +++ b/linker/executionContext.go @@ -0,0 +1,130 @@ +package linker + +import ( + "context" + "strconv" + + "github.com/Axway/ace-golang-sdk/util/logging" + "go.uber.org/zap" + + "github.com/Axway/ace-golang-sdk/messaging" + "github.com/Axway/ace-golang-sdk/rpc" +) + +const ( + stringType = "string" + intType = "int" + boolType = "boolean" +) + +// messageContext - Represents the current execution context that holds the message and related properties +type messageContext struct { + ctx context.Context + businessMessages []*messaging.BusinessMessage + configMap map[string]*rpc.ConfigParameter + msgProducer MsgProducer +} + +// ExecutionContext - Interface exposed to clients to access the current message context +type ExecutionContext interface { + GetSpanContext() context.Context + GetBusinessMessages() []*messaging.BusinessMessage + GetMsgProducer() MsgProducer + GetStringConfigValue(string) string + GetIntConfigValue(string) int + GetBooleanConfigValue(string) bool +} + +// GetSpanContext - Returns the current context +func (msgCtx *messageContext) GetSpanContext() context.Context { + return msgCtx.ctx +} + +// GetBusinessMessages - Returns business messages +func (msgCtx *messageContext) GetBusinessMessages() []*messaging.BusinessMessage { + return msgCtx.businessMessages +} + +// GetMsgProducer - Returns the interfacee for producing messages +func (msgCtx *messageContext) GetMsgProducer() MsgProducer { + return msgCtx.msgProducer +} + +// GetStringConfigValue - Returns the string confign value +func (msgCtx *messageContext) GetStringConfigValue(name string) string { + cfgParam, ok := msgCtx.configMap[name] + if !ok { + log.Warn("The requested configuration did not exist, returning default for type string", + zap.String(logging.LogConfigParamName, name), + zap.String(logging.LogConfigParamTypeRequested, stringType)) + return "" + } + + if cfgParam.GetType() != "string" { + log.Warn("The requested configuration is not correct type, returning default for type string", + zap.String(logging.LogConfigParamName, name), + zap.String(logging.LogConfigParamType, cfgParam.GetType()), + zap.String(logging.LogConfigParamTypeRequested, stringType)) + return "" + } + + return cfgParam.GetValue() +} + +// GetIntConfigValue - Returns the int config value +func (msgCtx *messageContext) GetIntConfigValue(name string) int { + cfgParam, ok := msgCtx.configMap[name] + if !ok { + log.Warn("The requested configuration did not exist, returning default for type int", + zap.String(logging.LogConfigParamName, name), + zap.String(logging.LogConfigParamTypeRequested, boolType)) + return 0 + } + + if cfgParam.GetType() != "int" { + log.Warn("The requested configuration is not correct type, returning default for type int", + zap.String(logging.LogConfigParamName, name), + zap.String(logging.LogConfigParamType, cfgParam.GetType()), + zap.String(logging.LogConfigParamTypeRequested, intType)) + return 0 + } + + intVal, err := strconv.Atoi(cfgParam.GetValue()) + if err != nil { + log.Warn("Could not parse the config value to an int, returning default for type int", + zap.String(logging.LogConfigParamName, name), + zap.String(logging.LogConfigParamType, cfgParam.GetType()), + zap.String(logging.LogConfigParamTypeRequested, intType)) + return 0 + } + return intVal +} + +// GetBooleanConfigValue - Returns the boolean config value +func (msgCtx *messageContext) GetBooleanConfigValue(name string) bool { + cfgParam, ok := msgCtx.configMap[name] + if !ok { + log.Warn("The requested configuration did not exist, returning default for type boolean", + zap.String(logging.LogConfigParamName, name), + zap.String(logging.LogConfigParamTypeRequested, boolType)) + return false + } + + if cfgParam.GetType() != "boolean" { + log.Warn("The requested configuration is not correct type, returning default for type boolean", + zap.String(logging.LogConfigParamName, name), + zap.String(logging.LogConfigParamType, cfgParam.GetType()), + zap.String(logging.LogConfigParamTypeRequested, boolType)) + return false + } + + boolVal, err := strconv.ParseBool(cfgParam.GetValue()) + if err != nil { + log.Warn("Could not parse the config value to a boolean, returning default for type boolean", + zap.String(logging.LogConfigParamName, name), + zap.String(logging.LogConfigParamType, cfgParam.GetType()), + zap.String(logging.LogConfigParamTypeRequested, boolType)) + return false + } + return boolVal +} diff --git a/linker/executionContext_test.go b/linker/executionContext_test.go new file mode 100644 index 0000000..ac4ad33 --- /dev/null +++ b/linker/executionContext_test.go @@ -0,0 +1,115 @@ +package linker + +import ( + "context" + "strconv" + "testing" + + "github.com/Axway/ace-golang-sdk/messaging" + "github.com/Axway/ace-golang-sdk/rpc" +) + +// var testProcBusMsg *messaging.BusinessMessage + +var testExecutionContext ExecutionContext + +func testBusinessProcess(ec ExecutionContext) error { + testExecutionContext = ec + return nil +} + +type testMsgProducer struct{} + +// Send - +func (p testMsgProducer) Send(bm *messaging.BusinessMessage) error { + return nil +} + +func TestExecutionContext(t *testing.T) { + ctx := context.Background() + msgProducer := testMsgProducer{} + configMap := make(map[string]*rpc.ConfigParameter) + + // Add all the config map values we will need + configMap["string-param"] = &rpc.ConfigParameter{ + Name: "string-param", + Type: "string", + Value: "string-value", + } + configMap["boolean-param"] = &rpc.ConfigParameter{ + Name: "boolean-param", + Type: "boolean", + Value: "true", + } + configMap["int-param"] = &rpc.ConfigParameter{ + Name: "int-param", + Type: "int", + Value: "123", + } + configMap["int-param-misconfig"] = &rpc.ConfigParameter{ + Name: "int-param", + Type: "int", + Value: "abc", + } + + msgContext := messageContext{ + ctx: ctx, + msgProducer: msgProducer, + configMap: configMap, + } + + testBusinessProcess(&msgContext) + + if ctx != testExecutionContext.GetSpanContext() { + t.Error("incorrect Context received from GetSpanContext") + } + + // Test GetStringConfigValue + if testExecutionContext.GetStringConfigValue("string-param") != configMap["string-param"].Value { + t.Errorf("The GetStringConfigValue method returned %s but we expected %s", testExecutionContext.GetStringConfigValue("string-param"), configMap["string-param"]) + } + + if testExecutionContext.GetStringConfigValue("int-param") != "" { + t.Error("The GetStringConfigValue method returned a value for an int type but we expected \"\"") + } + + if testExecutionContext.GetStringConfigValue("param-does-not-exist") != "" { + t.Error("The GetStringConfigValue method returned a value for a non existent params but we expected \"\"") + } + + // Test GetIntConfigValue + intVal, _ := strconv.Atoi(configMap["int-param"].Value) + if testExecutionContext.GetIntConfigValue("int-param") != intVal { + t.Errorf("The GetIntConfigValue method returned %d but we expected %s", testExecutionContext.GetIntConfigValue("int-param"), configMap["int-param"]) + } + + if testExecutionContext.GetIntConfigValue("string-param") != 0 { + t.Error("The GetIntConfigValue method returned a value for a string type but we expected 0") + } + + if testExecutionContext.GetIntConfigValue("param-does-not-exist") != 0 { + t.Error("The GetIntConfigValue method returned a value for a non existent params but we expected 0") + } + + if testExecutionContext.GetIntConfigValue("int-param-misconfig") != 0 { + t.Error("The GetIntConfigValue method returned a value for a non parsable value but we expected 0") + } + + // Test GetBooleanConfigValue + boolVal, _ := strconv.ParseBool(configMap["boolean-param"].Value) + if testExecutionContext.GetBooleanConfigValue("boolean-param") != boolVal { + t.Errorf("The GetBooleanConfigValue method returned %t but we expected %s", testExecutionContext.GetBooleanConfigValue("boolean-param"), configMap["boolean-param"]) + } + + if testExecutionContext.GetBooleanConfigValue("string-param") != false { + t.Error("The GetBooleanConfigValue method returned a value for a string type but we expected false") + } + + if testExecutionContext.GetBooleanConfigValue("param-does-not-exist") != false { + t.Error("The GetBooleanConfigValue method returned a value for a non existent params but we expected false") + } + + if testExecutionContext.GetBooleanConfigValue("boolean-param-misconfig") != false { + t.Error("The GetBooleanConfigValue method returned a value for a non parsable value but we expected false") + } +} diff --git a/linker/linker.go b/linker/linker.go index 3de9692..7a3d99c 100644 --- a/linker/linker.go +++ b/linker/linker.go @@ -69,7 +69,7 @@ type MsgProducer interface { } // BusinessMessageProcessor type of 'business' function used to process paylod relayed to Linker -type BusinessMessageProcessor func(context.Context, []*messaging.BusinessMessage, MsgProducer) error +type BusinessMessageProcessor func(ExecutionContext) error // Add config parameter to list func addConfigParam(configParam *rpc.ConfigParameter) { @@ -190,29 +190,43 @@ var ( func (link Link) OnRelay(aceMessage *rpc.Message) { ctxWithSpan := tracing.IssueTrace(aceMessage, "Agent message receive") - switch msgProcessor := link.MsgProcessor.(type) { - case BusinessMessageProcessor: - clientRelay, buildErr := link.client.BuildClientRelay(ctxWithSpan, aceMessage, sidecarHost, sidecarPort) - if buildErr != nil { - log.Fatal("Unable to initialize relay client", zap.String(logging.LogFieldError, buildErr.Error())) - return - } - log.Debug("Relay client created, executing service callback to process message", + clientRelay, buildErr := link.client.BuildClientRelay(ctxWithSpan, aceMessage, sidecarHost, sidecarPort) + if buildErr != nil { + log.Fatal("Unable to initialize relay client", zap.String(logging.LogFieldError, buildErr.Error())) + return + } + log.Debug("Relay client created, executing service callback to process message", + zap.String(logging.LogFieldChoreographyID, aceMessage.GetCHN_UUID()), + zap.String(logging.LogFieldExecutionID, aceMessage.GetCHX_UUID()), + zap.String(logging.LogFieldMessageID, aceMessage.GetUUID()), + zap.String(logging.LogFieldParentMessageID, aceMessage.GetParent_UUID())) + + defer func() { + log.Info("Service callback execution completed", zap.String(logging.LogFieldChoreographyID, aceMessage.GetCHN_UUID()), zap.String(logging.LogFieldExecutionID, aceMessage.GetCHX_UUID()), zap.String(logging.LogFieldMessageID, aceMessage.GetUUID()), zap.String(logging.LogFieldParentMessageID, aceMessage.GetParent_UUID())) + clientRelay.CloseSend() + }() - defer func() { - log.Info("Service callback execution completed", - zap.String(logging.LogFieldChoreographyID, aceMessage.GetCHN_UUID()), - zap.String(logging.LogFieldExecutionID, aceMessage.GetCHX_UUID()), - zap.String(logging.LogFieldMessageID, aceMessage.GetUUID()), - zap.String(logging.LogFieldParentMessageID, aceMessage.GetParent_UUID())) - clientRelay.CloseSend() - }() + step := aceMessage.GetPattern() + var configMap = make(map[string]*rpc.ConfigParameter) + for _, configParam := range step.ServiceConfig { + configMap[configParam.Name] = configParam + } + + switch msgProcessor := link.MsgProcessor.(type) { + case BusinessMessageProcessor: + + msgContext := messageContext{ + ctx: ctxWithSpan, + businessMessages: aceMessage.GetBusinessMessage(), + msgProducer: clientRelay, + configMap: configMap, + } + err := msgProcessor(&msgContext) - err := msgProcessor(ctxWithSpan, aceMessage.GetBusinessMessage(), clientRelay) if err != nil { tracing.IssueErrorTrace( aceMessage, diff --git a/linker/linker_test.go b/linker/linker_test.go index d82eca1..3d4995d 100644 --- a/linker/linker_test.go +++ b/linker/linker_test.go @@ -16,8 +16,8 @@ import ( var testProcBusMsg *messaging.BusinessMessage -func testProc(c context.Context, bm []*messaging.BusinessMessage, mp MsgProducer) error { - testProcBusMsg = bm[0] +func testProc(ec ExecutionContext) error { + testProcBusMsg = ec.GetBusinessMessages()[0] return nil } @@ -243,8 +243,8 @@ func TestOnRelayNoErrors(t *testing.T) { } } -func testProcReturnProcessingError(c context.Context, bm []*messaging.BusinessMessage, mp MsgProducer) error { - testProcBusMsg = bm[0] +func testProcReturnProcessingError(ec ExecutionContext) error { + testProcBusMsg = ec.GetBusinessMessages()[0] return NewProcessingError(errors.New("test-processing-error")) } func TestOnRelayProcessingError(t *testing.T) { diff --git a/util/logging/constants.go b/util/logging/constants.go index 2e2e5cb..d109236 100644 --- a/util/logging/constants.go +++ b/util/logging/constants.go @@ -32,4 +32,8 @@ const ( LogFieldAgentHost = "agentHost" LogFieldAgentPort = "agentPort" LogFieldSidecarID = "sidecarId" + + LogConfigParamName = "configName" + LogConfigParamType = "configType" + LogConfigParamTypeRequested = "configTypeRequested" )