Cdaprod / repocate

1 stars 0 forks source link

Load Internal Builtin Operations (*_ops.go) as Shared Objects with Go Build Mode: plugins #4

Open Cdaprod opened 2 months ago

Cdaprod commented 2 months ago

Use Go's native plugin package to dynamically load operational modules (like Docker, Git, etc.) into their respective managers without hardcoding them into your application. This approach allows you to keep your core application lightweight and extensible, enabling dynamic loading of features as plugins at runtime.

How to Use the Native Plugin Package

  1. Define Plugin Interfaces: Ensure each operational module (like Docker, Git, etc.) implements a common interface defined in your internal/interfaces/interfaces.go. This allows the core application to interact with them uniformly.
package interfaces

import "github.com/Cdaprod/registry-service/internal/registry"

type Operation interface {
    Initialize() error
    RegisterWithRegistry(reg registry.Registry) error
    // Additional methods
}
  1. Implement Plugins in Separate Packages: Each operational module should implement the Operation interface and be compiled as a Go plugin (.so file).

Example for Docker operations (docker_ops.go):

package main

import (
    "github.com/Cdaprod/registry-service/internal/registry"
)

type DockerOps struct {
    // Docker-specific fields
}

func (d *DockerOps) Initialize() error {
    // Initialize Docker client or other setup
    return nil
}

func (d *DockerOps) RegisterWithRegistry(reg registry.Registry) error {
    // Register Docker-related items in the registry
    return nil
}

// Exported symbol for plugin use
var Ops DockerOps
  1. Compile Plugins: Compile the Docker operations as a shared object:
go build -buildmode=plugin -o docker_ops.so docker_ops.go
  1. Dynamically Load Plugins in Your Main Application: Use the plugin package to load these .so files at runtime.
package main

import (
    "plugin"
    "github.com/Cdaprod/registry-service/internal/registry"
    "github.com/cdaprod/repocate/internal/interfaces"
    "fmt"
)

func main() {
    reg := registry.NewCentralRegistry()

    // Load Docker plugin
    p, err := plugin.Open("docker_ops.so")
    if err != nil {
        fmt.Println("Error loading plugin:", err)
        return
    }

    // Look up the exported symbol (variable) "Ops"
    symOps, err := p.Lookup("Ops")
    if err != nil {
        fmt.Println("Error looking up Ops:", err)
        return
    }

    // Assert the loaded symbol is of the correct type
    ops, ok := symOps.(interfaces.Operation)
    if !ok {
        fmt.Println("Unexpected type from module symbol")
        return
    }

    // Use the dynamically loaded plugin
    if err := ops.Initialize(); err != nil {
        fmt.Println("Error initializing operations:", err)
        return
    }

    if err := ops.RegisterWithRegistry(reg); err != nil {
        fmt.Println("Error registering with registry:", err)
        return
    }

    fmt.Println("Plugin loaded and operations registered successfully.")
}

Benefits

Conclusion

Using Go's plugin package allows you to dynamically load and manage operational modules without hardcoding them, maintaining a clean, extensible, and maintainable architecture in your repocate-service. This approach enables SRP compliance while also supporting dynamic functionality enhancements.

Resulting Structure

To structure your repository for using Go's native plugin package effectively, you'll want to create a modular directory layout that separates core functionality, plugins, and shared interfaces. Here’s a suggested structure:

Repository Structure

repocate-service/
├── cmd/
│   └── server/
│       └── main.go           # Main entry point of the application
├── internal/
│   ├── interfaces/
│   │   └── interfaces.go     # Interfaces for plugins (e.g., Operation interface)
│   ├── registry/
│   │   └── registry_handler.go # Common registry handling functions
├── plugins/
│   ├── docker/
│   │   ├── docker_ops.go     # Docker plugin implementation
│   │   └── Dockerfile        # Dockerfile to build Docker plugin as .so
│   ├── git/
│   │   ├── git_ops.go        # Git plugin implementation
│   │   └── Dockerfile        # Dockerfile to build Git plugin as .so
│   └── other_plugins/        # Additional plugin folders as needed
├── build/
│   ├── docker_ops.so         # Compiled Docker plugin
│   ├── git_ops.so            # Compiled Git plugin
│   └── other_plugins.so      # Compiled plugins
├── pkg/                      # Optional: For common utility packages
│   └── utils/
│       └── helper.go
├── go.mod
├── go.sum
└── README.md

Steps to Organize the Repository

  1. cmd/server/main.go:

    • The main entry point of the application where you dynamically load plugins and initialize the registry.
  2. internal/interfaces/interfaces.go:

    • Contains lightweight interfaces (Operation, ContainerManager, GitManager, etc.) that define the expected behavior for plugins and operations.
  3. internal/registry/registry_handler.go:

    • Centralizes the logic for interacting with the registry. This ensures all registration-related tasks are handled consistently across the application.
  4. plugins/docker/docker_ops.go:

    • Implement the Docker-specific operations as a plugin. Ensure this file exports the necessary symbols for the plugin system (var Ops DockerOps).

    Dockerfile for Plugin:

    FROM golang:1.17
    WORKDIR /go/src/app
    COPY . .
    RUN go build -buildmode=plugin -o docker_ops.so docker_ops.go
  5. plugins/git/git_ops.go:

    • Similar to Docker, but for Git operations. Implement all methods that adhere to the interfaces defined.

    Dockerfile for Plugin:

    FROM golang:1.17
    WORKDIR /go/src/app
    COPY . .
    RUN go build -buildmode=plugin -o git_ops.so git_ops.go
  6. build/:

    • Store compiled plugins (*.so files) here. This keeps them separate from the source code, making it clear what is dynamically loaded.
  7. pkg/:

    • (Optional) Use this for shared utilities or common helper functions that multiple plugins or core logic might use.

Workflow

  1. Develop Plugins Independently: Each plugin (e.g., Docker, Git) should be developed and tested independently. They should implement the interfaces defined in internal/interfaces/interfaces.go.

  2. Compile Plugins: Use the Dockerfile in each plugin directory to compile the Go files into .so shared objects.

  3. Load Plugins Dynamically in main.go: Use the plugin package to load plugins dynamically, as shown in previous responses.

  4. Register and Use Plugins: After loading, initialize and register the plugins using the registry handler functions.

Conclusion

This repository structure allows for modular development and dynamic loading of plugins, adhering to the SRP and Liskov Substitution Principle while maintaining loose coupling. It provides a scalable architecture for managing multiple operational components within the repocate-service using the Go plugin package.