Skip to content

Refactoring flavors

Ondrej Fabry edited this page Jul 17, 2018 · 19 revisions

Goals

Easy to get simple stuff working

  • minimal code needed to start even with single plugin

Provide options to customize plugins

  • allow dependencies and configuration to be changed

Use cases

Using sane defaults as fallback for plugin dependencies

  • app should allow using plugins without setting all of their dependencies explicitly

Customized plugin dependencies

  • app uses customized instances for some of its deps
  • plugin deps that were set will use custom dep instead of default
  • plugin deps that were removed will not use default
  • plugin deps that were not customized will use default values
  • the app will use customized instance of the plugin
  • some other plugins can still use default instance

Global plugin instances

  • app uses default instances defined in plugin packages
  • the default deps for plugins will be set when plugin is found
  • all plugins only use default instances defined in their packages

Changing global instance

  • app replaces default instance of some plugin to custom instance
  • plugin deps that were not customized will use default values
  • the app will use customized default instance as direct dep
  • or it can use some plugin that has the default instance as dep
  • all plugins will now use new customized default instance

Proposal

Components

  • NewPlugin() function to create new plugin instances
  • DefaultPlugin variable providing global default instance
  • Option function that can customize plugin instance
    • UseDeps() option to set plugin deps
    • UseConf() option to set plugin config
  • Deps struct defining deps of plugins
  • SetDefaults() method to fill default deps

Considerations

  • setting global instance as default dep for other plugins that have global instance has to happen after possible changes of the instance and not in constructors
  • changing the global instance DefaultPlugin to custom one will not be changed in other global instances that use it as default dep

    • this is because global instances are created before main and copy the pointer
    • this forces default deps to be set after all initialization is done during which the global instances might be changed
    • another workaround would be to use pointers to interfaces/pointers
  • setting some dep to nil via UseDeps option will be overriden by SetDefaults function

    • this is because it cannot differentiate between undefined dep and disabled one
    • this could be solved by defining option to disable specific dep define global variable Disabled to represent disabled instance and set defaults would replace this with nil instead of default value

Implementation details

  • constructors take arbitrary number of options
  • options are all applied in constructor
  • recursively lookup deps of plugins to find all plugins
  • add optional method to fill to defaults

Using plugins

  • Default global instances

    • can be used by multiple plugins
    • can be set to custom plugin instance
  • New plugin

    • create new instance
    • loop over given options
      • call the options
        • UseDeps will set custom deps
          • UseConf will set custom config
      • return instance

Plugin hierarchy lookup

  • find all plugins recursively
    • value must be non-nil
    • check if type's kind is struct
    • loop over fields
      • must be exported
      • if field implements Plugin interface
        • field must be non-nil
          • check if plugin is not already in list
          • set defaults
          • if field is plugin or field name is "Deps"
            • call find plugins recursively
            • add found plugins to list
          • if is plugin
            • add to list
      • return list

Examples

	etcdDataSync := kvdbsync.NewPlugin(
		kvdbsync.UseDeps(kvdbsync.Deps{
			KvPlugin:   etcd.DefaultPlugin,
			ResyncOrch: resync.DefaultPlugin,
		}),
	)

	p := &ExamplePlugin{
		Log:          logging.ForPlugin(PluginName),
		PluginConfig: config.ForPlugin(PluginName),
		ServiceLabel: servicelabel.DefaultPlugin,
		Publisher:    etcdDataSync,
		Watcher:      etcdDataSync,
		exampleFinished: make(chan struct{}),
	}

	a := agent.NewAgent(
		agent.AllPlugins(p),
		agent.QuitOn(p.exampleFinished),
	)
	if err := a.Run(); err != nil {
		log.Fatal(err)
	}