Skip to content

Proposal: Plug-ins v2 and Thin Buffalo Binary #1791

@markbates

Description

@markbates

The current plug-in system for Buffalo is slow, error prone, and confusing for many users. This is a proposal for a alternative approach to plug-ins that, hopefully, addresses these issues.

As well as proposing a major revision of plug-ins, this proposal also introduces the concept of a "thin" Buffalo binary.

In this proposal I will use github.com/gobuffalo/pop as a library, package, and tool, that some people may not want to use.

The Fat Binary

Let's start with the buffalo binary. It is a "fat" binary. This means that it contains everything, from the web-server, to the generators, etc... Because of this several problems arise. The first is that this "fat" binary contains a lot of dependencies, regardless of whether the end user will ever use those features.

The current binary has a hard dependency on pop. The buffalo generate resource command, for example, generates pop style resources. So if the app in question uses another ORM, or none at all, or if they only generate JSON, then this dependency shouldn't be present.

Another problem this "fat" binary presents is that it can often be a version mismatch with the app it is being run against. If the app is v0.12.x but the binary is v0.15.0, then the results of running buffalo generate resource, for example, wouldn't be guaranteed to be consistent.

The Plug-in Problem.

Buffalo plugins have a set of rules that must be followed for them to be consider, by Buffalo, as a plugin.

  • Plug-ins must be named in the format of buffalo-<plugin-name>. For example, buffalo-myplugin.
  • Plug-ins must be executable and must be available in one of the following places:
    • in the $BUFFALO_PLUGIN_PATH
    • if not set, $GOPATH/bin, is tried
    • in the ./plugins folder of your Buffalo application
  • Plug-ins must implement an available command that prints a JSON response listing the available commands.

When a command is run, such as buffalo db migrate, Buffalo will try and find the plug-in (binary) and if found, it will then try and shell out the command of migrate to it.

This constant shelling out and searching for executables is error prone, confusing, slow, and again, has similar versioning concerns to that of the buffalo binary itself.

Thin Binary, Fat Binary

To solve these problems, I suggest the following.

First, we make the buffalo binary a "thin" one. We remove anything needed for running inside of an application. For example, buffalo dev is not required outside of an application, so why is it needed in the binary?

The buffalo binary should only contain what it needs when running its "outside" duties, such as buffalo new.

Instead, I propose using a similar approach to github.com/markbates/grift.

The "thin" buffalo binary when a command such as buffalo db migrate would [hands wave wildly in the air] compile a "fat" buffalo specifically for the current application, using the versions of Buffalo, and other libraries declared in the go.mod.

The pop plugin would be added to the application via a standard import.

// <app>/plugins/plugins.go
package plugins

import _ "github.com/gobuffalo/buffalo-pop"

The buffalo-pop plugin would register any sub-commands it may have for the new "fat" binary that is built.

Other Changes

With these changes the buffalo-plugins.toml file will no longer be needed, as well as all of the buffalo plugin sub-commands.

Sub-commands would be forced under their plug-in's name. So if the buffalo-pop plugin is registered with the name "pop" then the buffalo db migrate command would become buffalo pop migrate. Gone, also, would be "wrapping" another command.

Interfaces would be used to allow for simplier access points into the plug-in system. For example, we might have interfaces for such things as listing any commands, or implementing a "fix" command for things like buffalo fix to hook into.

// Not final interfaces, just rough ideas.

type Plugin interface {
  Name() string
}

type Fixable interface {
  // optional interface for hooking into "fix" sub-command
  // buffalo fix (will also call buffalo-pop/plugin.Plugin#Fix - if implemented)
  Fix(context.Context) error
}

type Commanding interface {
  Commands() (Commands, error)
}

Conclusion

I would love to hear community feedback on this proposal. Also, if there is anyone out there that would want to take lead on such a proposal, please speak up. :)

Metadata

Metadata

Assignees

Labels

breaking changeThis feature / fix introduces breaking changesdependencyupdate module dependenciesenhancementNew feature or requesthelp wantedFeel free to contribute!proposalA suggestion for a change, feature, enhancement, etc

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions