Operations

In the following sections, we will explore mechanisms for reusing and augmenting code using Functions, Actions, Operations, Decorators, Filters, and Handlers.

Functions

In go, functions are the lowest level of reusable execution. They accept a specific set of arguments and return a set of values.

func add(number1, number2 int) int {
  return number1 + number2
}

Function Types

Go functions are first-class, meaning they can be passed around as values, including as parameters to other functions, or return values from functions. This enables powerful code composition and reuse. This is also a key feature of the functional style of programming:

In functional programming, functions are treated as first-class citizens, meaning that they can be bound to names (including local identifiers), passed as arguments, and returned from other functions, just as any other data type can. This allows programs to be written in a declarative and composable style, where small functions are combined in a modular manner.

-- Functional Programming, Wikipedia

In go, to accept a function as an argument, you can declare a function type, and use it to declare the receiving parameter:

type unaryOperator func (int) int
type binaryOperator func(int, int) int

func evaluateUnaryExpression(operand int, operator unaryOperator) int {
  return unaryOperator(operand)
}

func evaluateBinaryExpression(leftOperand, rightOperand int, operator binaryOperator) int {
  return binaryOperator(leftOperatnd, rightOperand)
}

Actions

go-msx defines an ActionFunc type to describe an executable function (Action) signature:

type ActionFunc func(ctx context.Context) error

An ActionFunc accepts a single Context argument (to allow access to dependencies and operation-scoped data), and returns a single error value indicating success (nil) or failure (non-nil). As described above, this enables you to pass around these functions and abstractly re-use them:

// Send a message to the ANSWER_TOPIC channel
func deepThought(ctx context.Context) error {
  return stream.PublishObject('ANSWER_TOPIC', map[string]any{
    "answer": 42, 		
  })
}

// Call the deepThought function when the application is running
func init() {
  app.OnEvent(app.EventRun, app.PhaseDuring, deepThought)
}

In this example, we register an application event observer Action to be executed when the application has finished startup. The Action sends a simple message to a stream.

Operations

To simplify reusing code to work with Actions, go-msx has an Operation type:

type Operation struct {...}
func (o Operation) Run(ctx context.Context) error {...}

Operations provide a Run method to execute the operation, along with other methods to create derived Operations using Filters and Decorators. These will be discussed in the next section.