Skip to content

Module Development

Overview

The Module system is designed to create reusable and extensible plugin components that implement specific business functionalities. A module typically handles inputs, processes data according to its logic, and then produces outputs.

This document provides an overview of:

  1. Module Input and Output Parameters
  2. Steps to Develop a New Module

1. Module Input and Output Parameters

Modules generally define their input and output parameters as structured types, which ensure consistency across the application. Let’s break down the input and output concepts:

Input Parameters

Input parameters are encapsulated into a dedicated structure, which ensures type safety and consistency. Each module can define specific arguments it needs to function effectively.

  • Example (Input Type): ArgsCHxRates

    type ArgsCHxRates struct {
    Currencies []string `json:"currencies"` // List of currency codes to filter (e.g., ["USD", "EUR"]).
    }
    • Key Points:
      • Inputs are defined as JSON-serializable structs.
      • Fields in the struct represent the configurable arguments a module expects.
      • Use JSON field annotations (json:"field") for deserialization.
  • How it is Passed: During runtime, the input parameters are unmarshalled from a string argument (LayerArgs) into the specific struct using json.Unmarshal.

Output Parameters

Output parameters are also structured in a formal way to ensure uniformity in how modules return responses.

  • Example (Output Type): OutCHxRates

    type OutCHxRates struct {
    Rates CHRates // Object containing exchange rates processed by the module.
    }
    • CHRates further encapsulates the output details, such as:

      type CHRates struct {
      Date string // Date for which the exchange rates are fetched.
      Rates []CHRate // List of filtered exchange rates.
      }
    • Key Points:

      • Outputs are structured in a nested manner to handle multiple levels of data representation.
      • The top-level result (OutCHxRates) is wrapped and returned, complying with a standard response format.

2. Developing New Modules

To create a new module, follow these structured steps:

Step 1: Define Input and Output Structures

  • Input: Create a struct to encapsulate the arguments your module requires.

    • Use expressive field names that are consistent with the module’s functionality.
    • Add JSON tags for serialization/deserialization.
    type ArgsMyModule struct {
    Param1 string `json:"param1"` // Example: Some input parameter
    Param2 int `json:"param2"` // Example: Another input (e.g., an integer value)
    }
  • Output: Define a struct to encapsulate the result your module will produce.

    • Ensure it is explicit and type-safe.
    type OutMyModule struct {
    ResultDetails Details `json:"result_details"`
    }
    type Details struct {
    ID string `json:"id"`
    Name string `json:"name"`
    }

Step 2: Implement the Module Structure

Create your module by embedding shared behaviors from the base class or interfaces (e.g., PluginModule) and defining any module-specific attributes.

  • Example:
    type MyNewModule struct {
    PluginModule // Embedding shared functionality
    }

Use the NewModule function to initialize an instance of your module.

  • Example:
    func (r *PluginModule) NewMyNewModule() *MyNewModule {
    return &MyNewModule{}
    }

Step 3: Implement the Run Function

The Run function is the core of your module. It executes the business logic using input parameters and provides structured output.

  • Parse the incoming params argument.

  • Unmarshal JSON data into the input struct type.

  • Perform the module-specific operations.

  • Return the formatted output result.

  • Example Implementation:

    func (m *MyNewModule) Run(params interface{}) interface{} {
    // Parse input parameters
    pr := reflect.ValueOf(params)
    p := pr.Interface().(Params)
    var args ArgsMyModule
    err := json.Unmarshal([]byte(p.LayerArgs.(string)), &args)
    if err != nil {
    return m.ErrorResponse(err)
    }
    // Business logic (perform the required tasks)
    result := Details{
    ID: "123",
    Name: "Test Result",
    }
    // Return the final structured response
    return Response{
    State: cnst.STATE_DONE,
    Out: OutMyModule{ResultDetails: result},
    }
    }

Step 4: Add Helper Functions

If your module has operations that require additional logic (e.g., API calls, processing), define helper functions for these operations.

  • Example:
    func (m *MyNewModule) getDataFromAPI(url string) ([]byte, error) {
    resp, err := http.Get(url)
    if err != nil {
    return nil, fmt.Errorf("GET error: %v", err)
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
    return nil, fmt.Errorf("Status error: %v", resp.StatusCode)
    }
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
    return nil, fmt.Errorf("Read error: %v", err)
    }
    return body, nil
    }

Summary

The Module system follows a structured approach:

  1. Input/Output parameters are defined using dedicated structs to ensure uniformity.
  2. Modules are initialized via factory functions (e.g., NewModuleXYZ).
  3. The core logic resides in the Run method.
  4. Helper functions encapsulate additional operations like API requests, data parsing, etc.

By adhering to this structure, new modules can be easily integrated and reused while ensuring predictable behaviors in the plugin framework.