Skip to content
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

refactor: Rework SDK to use Interfaces and factory methods #741

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
#
# Copyright (c) 2021 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

.PHONY: test

GO=CGO_ENABLED=1 GO111MODULE=on go
Expand All @@ -8,9 +24,11 @@ build:
test-template:
make -C ./app-service-template test

test: build test-template
test-sdk:
$(GO) test ./... -coverprofile=coverage.out ./...
$(GO) vet ./...
gofmt -l .
[ "`gofmt -l .`" = "" ]
./app-service-template/bin/test-go-mod-tidy.sh
./app-service-template/bin/test-go-mod-tidy.sh

test: build test-template test-sdk
5 changes: 4 additions & 1 deletion app-service-template/Attribution.txt
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,7 @@ github.com/mattn/go-isatty (MIT) https://github.com/mattn/go-isatty
https://github.com/mattn/go-isatty/blob/master/LICENSE

golang.org/x/sys (Unspecified) https://github.com/golang/sys
https://github.com/golang/sys/blob/master/LICENSE
https://github.com/golang/sys/blob/master/LICENSE

stretchr/objx (MIT) https://github.com/stretchr/objx
https://github.com/stretchr/objx/blob/master/LICENSE
lenny-goodell marked this conversation as resolved.
Show resolved Hide resolved
16 changes: 16 additions & 0 deletions app-service-template/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
#
# Copyright (c) 2021 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

.PHONY: build test clean docker

GO=CGO_ENABLED=1 go
Expand Down
49 changes: 28 additions & 21 deletions app-service-template/functions/sample.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import (
"fmt"
"strings"

"github.com/edgexfoundry/app-functions-sdk-go/v2/appcontext"
"github.com/edgexfoundry/go-mod-core-contracts/v2/clients"

"github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/interfaces"

"github.com/edgexfoundry/go-mod-core-contracts/v2/v2"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos"
)
Expand All @@ -39,27 +42,28 @@ type Sample struct {

// LogEventDetails is example of processing an Event and passing the original Event to to next function in the pipeline
// For more details on the Context API got here: https://docs.edgexfoundry.org/1.3/microservices/application/ContextAPI/
func (s *Sample) LogEventDetails(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) {
edgexcontext.LoggingClient.Debug("LogEventDetails called")
func (s *Sample) LogEventDetails(ctx interfaces.AppFunctionContext, data interface{}) (bool, interface{}) {
lc := ctx.LoggingClient()
lc.Debug("LogEventDetails called")

if len(params) < 1 {
if data == nil {
// Go here for details on Error Handle: https://docs.edgexfoundry.org/1.3/microservices/application/ErrorHandling/
return false, errors.New("no Event Received")
}

event, ok := params[0].(dtos.Event)
event, ok := data.(dtos.Event)
if !ok {
return false, errors.New("type received is not an Event")
}

edgexcontext.LoggingClient.Infof("Event received: ID=%s, Device=%s, and ReadingCount=%d",
lc.Infof("Event received: ID=%s, Device=%s, and ReadingCount=%d",
event.Id,
event.DeviceName,
len(event.Readings))
for index, reading := range event.Readings {
switch strings.ToLower(reading.ValueType) {
case strings.ToLower(v2.ValueTypeBinary):
edgexcontext.LoggingClient.Infof(
lc.Infof(
"Reading #%d received with ID=%s, Resource=%s, ValueType=%s, MediaType=%s and BinaryValue of size=`%d`",
index+1,
reading.Id,
Expand All @@ -68,7 +72,7 @@ func (s *Sample) LogEventDetails(edgexcontext *appcontext.Context, params ...int
reading.MediaType,
len(reading.BinaryValue))
default:
edgexcontext.LoggingClient.Infof("Reading #%d received with ID=%s, Resource=%s, ValueType=%s, Value=`%s`",
lc.Infof("Reading #%d received with ID=%s, Resource=%s, ValueType=%s, Value=`%s`",
index+1,
reading.Id,
reading.ResourceName,
Expand All @@ -83,14 +87,15 @@ func (s *Sample) LogEventDetails(edgexcontext *appcontext.Context, params ...int
}

// ConvertEventToXML is example of transforming an Event and passing the transformed data to to next function in the pipeline
func (s *Sample) ConvertEventToXML(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) {
edgexcontext.LoggingClient.Debug("ConvertEventToXML called")
func (s *Sample) ConvertEventToXML(ctx interfaces.AppFunctionContext, data interface{}) (bool, interface{}) {
lc := ctx.LoggingClient()
lc.Debug("ConvertEventToXML called")

if len(params) < 1 {
if data == nil {
return false, errors.New("no Event Received")
}

event, ok := params[0].(dtos.Event)
event, ok := data.(dtos.Event)
if !ok {
return false, errors.New("type received is not an Event")
}
Expand All @@ -103,34 +108,36 @@ func (s *Sample) ConvertEventToXML(edgexcontext *appcontext.Context, params ...i
// Example of DEBUG message which by default you don't want to be logged.
// To see debug log messages, Set WRITABLE_LOGLEVEL=DEBUG environment variable or
// change LogLevel in configuration.toml before running app service.
edgexcontext.LoggingClient.Debug("Event converted to XML: " + xml)
lc.Debug("Event converted to XML: " + xml)

// Returning true indicates that the pipeline execution should continue with the next function
// using the event passed as input in this case.
return true, xml
}

// OutputXML is an example of processing transformed data
func (s *Sample) OutputXML(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) {
edgexcontext.LoggingClient.Debug("OutputXML called")
func (s *Sample) OutputXML(ctx interfaces.AppFunctionContext, data interface{}) (bool, interface{}) {
lc := ctx.LoggingClient()
lc.Debug("OutputXML called")

if len(params) < 1 {
if data == nil {
return false, errors.New("no XML Received")
}

xml, ok := params[0].(string)
xml, ok := data.(string)
if !ok {
return false, errors.New("type received is not an string")
}

edgexcontext.LoggingClient.Debug(fmt.Sprintf("Outputting the following XML: %s", xml))
lc.Debug(fmt.Sprintf("Outputting the following XML: %s", xml))
lenny-goodell marked this conversation as resolved.
Show resolved Hide resolved

// This sends the XML as a response. i.e. publish for MessageBus/MQTT triggers as configured or
// HTTP response to for the HTTP Trigger
// For more details on the Complete() function go here: https://docs.edgexfoundry.org/1.3/microservices/application/ContextAPI/#complete
edgexcontext.Complete([]byte(xml))
// For more details on the SetResponseData() function go here: https://docs.edgexfoundry.org/1.3/microservices/application/ContextAPI/#complete
ctx.SetResponseData([]byte(xml))
ctx.SetResponseContentType(clients.ContentTypeXML)

// Returning false terminates the pipeline execution, so this should be last function specified in the pipeline,
// which is typical in conjunction with usage of .Complete() function.
// which is typical in conjunction with usage of .SetResponseData() function.
return false, nil
}
42 changes: 27 additions & 15 deletions app-service-template/functions/sample_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,44 @@ package functions
import (
"testing"

"github.com/edgexfoundry/app-functions-sdk-go/v2/appcontext"
"github.com/edgexfoundry/go-mod-core-contracts/v2/clients"
"github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2"
"github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos"
"github.com/stretchr/testify/require"

"github.com/edgexfoundry/app-functions-sdk-go/v2/pkg"
"github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/interfaces"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)

// This file contains example of how to unit test pipeline functions
// TODO: Change these sample unit tests to test your custom type and function(s)

var appContext interfaces.AppFunctionContext

func TestMain(m *testing.M) {
//
// This can be changed to a real logger when needing more debug information output to the console
// lc := logger.NewClient("testing", "DEBUG")
//
lc := logger.NewMockClient()
correlationId := uuid.New().String()

// NewAppFuncContextForTest creates a context with basic dependencies for unit testing with the passed in logger
// If more additional dependencies (such as mock clients) are required, then use
// NewAppFuncContext(correlationID string, dic *di.Container) and pass in an initialized DIC (dependency injection container)
appContext = pkg.NewAppFuncContextForTest(correlationId, lc)
}

func TestSample_LogEventDetails(t *testing.T) {
expectedEvent := createTestEvent(t)
expectedContinuePipeline := true

target := NewSample()
actualContinuePipeline, actualEvent := target.LogEventDetails(createTestAppSdkContext(), expectedEvent)
actualContinuePipeline, actualEvent := target.LogEventDetails(appContext, expectedEvent)

assert.Equal(t, expectedContinuePipeline, actualContinuePipeline)
assert.Equal(t, expectedEvent, actualEvent)
Expand All @@ -49,7 +68,7 @@ func TestSample_ConvertEventToXML(t *testing.T) {
expectedContinuePipeline := true

target := NewSample()
actualContinuePipeline, actualXml := target.ConvertEventToXML(createTestAppSdkContext(), event)
actualContinuePipeline, actualXml := target.ConvertEventToXML(appContext, event)

assert.Equal(t, expectedContinuePipeline, actualContinuePipeline)
assert.Equal(t, expectedXml, actualXml)
Expand All @@ -58,17 +77,17 @@ func TestSample_ConvertEventToXML(t *testing.T) {

func TestSample_OutputXML(t *testing.T) {
testEvent := createTestEvent(t)
expectedXml, _ := testEvent.ToXML()
xml, _ := testEvent.ToXML()
expectedContinuePipeline := false
appContext := createTestAppSdkContext()
expectedContentType := clients.ContentTypeXML

target := NewSample()
actualContinuePipeline, result := target.OutputXML(appContext, expectedXml)
actualXml := string(appContext.OutputData)
actualContinuePipeline, result := target.OutputXML(appContext, xml)
actualContentType := appContext.ResponseContentType()

assert.Equal(t, expectedContinuePipeline, actualContinuePipeline)
assert.Nil(t, result)
assert.Equal(t, expectedXml, actualXml)
assert.Equal(t, expectedContentType, actualContentType)
}

func createTestEvent(t *testing.T) dtos.Event {
Expand All @@ -87,10 +106,3 @@ func createTestEvent(t *testing.T) dtos.Event {

return event
}

func createTestAppSdkContext() *appcontext.Context {
return &appcontext.Context{
CorrelationID: uuid.New().String(),
LoggingClient: logger.NewMockClient(),
}
}
38 changes: 23 additions & 15 deletions app-service-template/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ package main
import (
"os"

"github.com/edgexfoundry/app-functions-sdk-go/v2/appsdk"
"github.com/edgexfoundry/app-functions-sdk-go/v2/pkg"
"github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/interfaces"
"github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/transforms"

"new-app-service/functions"
Expand All @@ -34,38 +35,45 @@ func main() {
// TODO: See https://docs.edgexfoundry.org/1.3/microservices/application/ApplicationServices/
// for documentation on application services.

edgexSdk := &appsdk.AppFunctionsSDK{ServiceKey: serviceKey}
if err := edgexSdk.Initialize(); err != nil {
edgexSdk.LoggingClient.Errorf("SDK initialization failed: %s", err.Error())
os.Exit(-1)
code := CreateAndRunService(serviceKey, pkg.NewAppService)
os.Exit(code)
}

// CreateAndRunService wraps what would normally be in main() so that it can be unit tested
func CreateAndRunService(serviceKey string, newServiceFactory func(string) (interfaces.ApplicationService, bool)) int {
service, ok := newServiceFactory(serviceKey)
if !ok {
return -1
}

lc := service.LoggingClient()

// TODO: Replace with retrieving your custom ApplicationSettings from configuration
deviceNames, err := edgexSdk.GetAppSettingStrings("DeviceNames")
deviceNames, err := service.GetAppSettingStrings("DeviceNames")
if err != nil {
edgexSdk.LoggingClient.Errorf("failed to retrieve DeviceNames from configuration: %s", err.Error())
os.Exit(-1)
lc.Errorf("failed to retrieve DeviceNames from configuration: %s", err.Error())
return -1
}

// TODO: Replace below functions with built in and/or your custom functions for your use case.
// See https://docs.edgexfoundry.org/1.3/microservices/application/BuiltIn/ for list of built-in functions
sample := functions.NewSample()
err = edgexSdk.SetFunctionsPipeline(
err = service.SetFunctionsPipeline(
transforms.NewFilterFor(deviceNames).FilterByDeviceName,
sample.LogEventDetails,
sample.ConvertEventToXML,
sample.OutputXML)
if err != nil {
edgexSdk.LoggingClient.Errorf("SetFunctionsPipeline returned error: %s", err.Error())
os.Exit(-1)
lc.Errorf("SetFunctionsPipeline returned error: %s", err.Error())
return -1
}

if err := edgexSdk.MakeItRun(); err != nil {
edgexSdk.LoggingClient.Errorf("MakeItRun returned error: %s", err.Error())
os.Exit(-1)
if err := service.MakeItRun(); err != nil {
lc.Errorf("MakeItRun returned error: %s", err.Error())
return -1
}

// TODO: Do any required cleanup here, if needed

os.Exit(0)
return 0
}
Loading