|
| 1 | +# Plugin Development Guide |
| 2 | + |
| 3 | +This guide explains how to develop plugins for Dingo's storage system. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Dingo supports pluggable storage backends through a registration-based plugin system. Plugins can extend the system with new blob storage (blocks, transactions) and metadata storage (indexes, state) implementations. |
| 8 | + |
| 9 | +## Plugin Types |
| 10 | + |
| 11 | +### Blob Storage Plugins |
| 12 | +Store blockchain data (blocks, transactions, etc.). Examples: |
| 13 | +- `badger` - Local BadgerDB key-value store |
| 14 | +- `gcs` - Google Cloud Storage |
| 15 | +- `s3` - AWS S3 |
| 16 | + |
| 17 | +### Metadata Storage Plugins |
| 18 | +Store metadata and indexes. Examples: |
| 19 | +- `sqlite` - SQLite relational database |
| 20 | + |
| 21 | +## Plugin Interface |
| 22 | + |
| 23 | +All plugins must implement the `plugin.Plugin` interface: |
| 24 | + |
| 25 | +```go |
| 26 | +type Plugin interface { |
| 27 | + Start() error |
| 28 | + Stop() error |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +## Plugin Registration |
| 33 | + |
| 34 | +Plugins register themselves during package initialization using the `plugin.Register()` function: |
| 35 | + |
| 36 | +```go |
| 37 | +func init() { |
| 38 | + plugin.Register(plugin.PluginEntry{ |
| 39 | + Type: plugin.PluginTypeBlob, // or PluginTypeMetadata |
| 40 | + Name: "myplugin", |
| 41 | + Description: "My custom storage plugin", |
| 42 | + NewFromOptionsFunc: NewFromCmdlineOptions, |
| 43 | + Options: []plugin.PluginOption{ |
| 44 | + // Plugin-specific options |
| 45 | + }, |
| 46 | + }) |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +## Plugin Options |
| 51 | + |
| 52 | +Plugins define configuration options using the `PluginOption` struct: |
| 53 | + |
| 54 | +```go |
| 55 | +plugin.PluginOption{ |
| 56 | + Name: "data-dir", // Option name |
| 57 | + Type: plugin.PluginOptionTypeString, // Data type |
| 58 | + Description: "Data directory path", // Help text |
| 59 | + DefaultValue: "/tmp/data", // Default value |
| 60 | + Dest: &cmdlineOptions.dataDir, // Destination variable |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +Supported option types: |
| 65 | +- `PluginOptionTypeString` |
| 66 | +- `PluginOptionTypeBool` |
| 67 | +- `PluginOptionTypeInt` |
| 68 | +- `PluginOptionTypeUint` |
| 69 | + |
| 70 | +## Environment Variables |
| 71 | + |
| 72 | +Plugins automatically support environment variables with the pattern: |
| 73 | +`DINGO_DATABASE_{TYPE}_{PLUGIN}_{OPTION}` |
| 74 | + |
| 75 | +Examples: |
| 76 | +- `DINGO_DATABASE_BLOB_BADGER_DATA_DIR=/data` |
| 77 | +- `DINGO_DATABASE_METADATA_SQLITE_DATA_DIR=/metadata.db` |
| 78 | + |
| 79 | +## YAML Configuration |
| 80 | + |
| 81 | +Plugins can be configured in `dingo.yaml`: |
| 82 | + |
| 83 | +```yaml |
| 84 | +database: |
| 85 | + blob: |
| 86 | + plugin: "myplugin" |
| 87 | + myplugin: |
| 88 | + option1: "value1" |
| 89 | + option2: 42 |
| 90 | + metadata: |
| 91 | + plugin: "sqlite" |
| 92 | + sqlite: |
| 93 | + data-dir: "/data/metadata.db" |
| 94 | +``` |
| 95 | +
|
| 96 | +## Configuration Precedence |
| 97 | +
|
| 98 | +1. Command-line flags (highest priority) |
| 99 | +2. Environment variables |
| 100 | +3. YAML configuration |
| 101 | +4. Default values (lowest priority) |
| 102 | +
|
| 103 | +## Command Line Options |
| 104 | +
|
| 105 | +Plugins support command-line flags with the pattern: |
| 106 | +`--{type}-{plugin}-{option}` |
| 107 | + |
| 108 | +Examples: |
| 109 | +- `--blob-badger-data-dir /data` |
| 110 | +- `--metadata-sqlite-data-dir /metadata.db` |
| 111 | + |
| 112 | +## Plugin Development Steps |
| 113 | + |
| 114 | +### 1. Create Plugin Structure |
| 115 | + |
| 116 | +```text |
| 117 | +database/plugin/{type}/{name}/ |
| 118 | +├── plugin.go # Registration and options |
| 119 | +├── options.go # Option functions |
| 120 | +├── database.go # Core implementation |
| 121 | +└── options_test.go # Unit tests |
| 122 | +``` |
| 123 | + |
| 124 | +### 2. Implement Core Plugin |
| 125 | + |
| 126 | +Create the main plugin struct that implements `plugin.Plugin`: |
| 127 | + |
| 128 | +```go |
| 129 | +type MyPlugin struct { |
| 130 | + // Fields |
| 131 | +} |
| 132 | +
|
| 133 | +func (p *MyPlugin) Start() error { |
| 134 | + // Initialize resources |
| 135 | + return nil |
| 136 | +} |
| 137 | +
|
| 138 | +func (p *MyPlugin) Stop() error { |
| 139 | + // Clean up resources |
| 140 | + return nil |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +### 3. Define Options |
| 145 | + |
| 146 | +Create option functions following the pattern: |
| 147 | + |
| 148 | +```go |
| 149 | +func WithOptionName(value Type) OptionFunc { |
| 150 | + return func(p *MyPlugin) { |
| 151 | + p.field = value |
| 152 | + } |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +### 4. Implement Constructors |
| 157 | + |
| 158 | +Provide both options-based and legacy constructors: |
| 159 | + |
| 160 | +```go |
| 161 | +func NewWithOptions(opts ...OptionFunc) (*MyPlugin, error) { |
| 162 | + p := &MyPlugin{} |
| 163 | + for _, opt := range opts { |
| 164 | + opt(p) |
| 165 | + } |
| 166 | + return p, nil |
| 167 | +} |
| 168 | +
|
| 169 | +func New(legacyParam1, legacyParam2) (*MyPlugin, error) { |
| 170 | + // For backward compatibility |
| 171 | + return NewWithOptions( |
| 172 | + WithOption1(legacyParam1), |
| 173 | + WithOption2(legacyParam2), |
| 174 | + ) |
| 175 | +} |
| 176 | +``` |
| 177 | + |
| 178 | +### 5. Register Plugin |
| 179 | + |
| 180 | +In `plugin.go`, register during initialization: |
| 181 | + |
| 182 | +```go |
| 183 | +var cmdlineOptions struct { |
| 184 | + option1 string |
| 185 | + option2 int |
| 186 | +} |
| 187 | +
|
| 188 | +func init() { |
| 189 | + plugin.Register(plugin.PluginEntry{ |
| 190 | + Type: plugin.PluginTypeBlob, |
| 191 | + Name: "myplugin", |
| 192 | + Description: "My custom plugin", |
| 193 | + NewFromOptionsFunc: NewFromCmdlineOptions, |
| 194 | + Options: []plugin.PluginOption{ |
| 195 | + { |
| 196 | + Name: "option1", |
| 197 | + Type: plugin.PluginOptionTypeString, |
| 198 | + Description: "First option", |
| 199 | + DefaultValue: "default", |
| 200 | + Dest: &cmdlineOptions.option1, |
| 201 | + }, |
| 202 | + // More options... |
| 203 | + }, |
| 204 | + }) |
| 205 | +} |
| 206 | +
|
| 207 | +func NewFromCmdlineOptions() plugin.Plugin { |
| 208 | + p, err := NewWithOptions( |
| 209 | + WithOption1(cmdlineOptions.option1), |
| 210 | + WithOption2(cmdlineOptions.option2), |
| 211 | + ) |
| 212 | + if err != nil { |
| 213 | + panic(err) |
| 214 | + } |
| 215 | + return p |
| 216 | +} |
| 217 | +``` |
| 218 | + |
| 219 | +### 6. Add Tests |
| 220 | + |
| 221 | +Create comprehensive tests: |
| 222 | + |
| 223 | +```go |
| 224 | +func TestOptions(t *testing.T) { |
| 225 | + // Test option functions |
| 226 | +} |
| 227 | +
|
| 228 | +func TestLifecycle(t *testing.T) { |
| 229 | + p, err := NewWithOptions(WithOption1("test")) |
| 230 | + // Test Start/Stop |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | +### 7. Update Imports |
| 235 | + |
| 236 | +Add your plugin to the import list in the appropriate store file: |
| 237 | +- `database/plugin/blob/blob.go` for blob plugins |
| 238 | +- `database/plugin/metadata/metadata.go` for metadata plugins |
| 239 | + |
| 240 | +## Example: Complete Plugin |
| 241 | + |
| 242 | +See the existing plugins for complete examples: |
| 243 | +- `database/plugin/blob/badger/` - BadgerDB implementation |
| 244 | +- `database/plugin/metadata/sqlite/` - SQLite implementation |
| 245 | +- `database/plugin/blob/gcs/` - Google Cloud Storage implementation |
| 246 | +- `database/plugin/blob/aws/` - AWS S3 implementation |
| 247 | + |
| 248 | +## Best Practices |
| 249 | + |
| 250 | +1. **Error Handling**: Always return descriptive errors |
| 251 | +2. **Resource Management**: Properly implement Start/Stop for resource lifecycle |
| 252 | +3. **Thread Safety**: Ensure plugins are safe for concurrent use |
| 253 | +4. **Configuration Validation**: Validate configuration during construction |
| 254 | +5. **Backward Compatibility**: Maintain compatibility with existing deployments |
| 255 | +6. **Documentation**: Document all options and their effects |
| 256 | +7. **Testing**: Provide comprehensive unit and integration tests |
| 257 | + |
| 258 | +## Testing Your Plugin |
| 259 | + |
| 260 | +### Unit Tests |
| 261 | +Test individual components and option functions. |
| 262 | + |
| 263 | +### Integration Tests |
| 264 | +Test the complete plugin lifecycle and interaction with the plugin system. |
| 265 | + |
| 266 | +### CLI Testing |
| 267 | +Use the CLI to test plugin listing and selection: |
| 268 | + |
| 269 | +```bash |
| 270 | +./dingo --blob list |
| 271 | +./dingo --metadata list |
| 272 | +``` |
| 273 | + |
| 274 | +### Configuration Testing |
| 275 | +Test environment variables and YAML configuration: |
| 276 | + |
| 277 | +```bash |
| 278 | +DINGO_DATABASE_BLOB_MYPLUGIN_OPTION1=value ./dingo --blob myplugin |
| 279 | +``` |
0 commit comments