{"id":13563446,"url":"https://github.com/progrium/go-extpoints","last_synced_at":"2025-04-03T20:30:48.629Z","repository":{"id":11808873,"uuid":"14357174","full_name":"progrium/go-extpoints","owner":"progrium","description":"Make Go packages extensible","archived":true,"fork":false,"pushed_at":"2016-03-09T21:39:58.000Z","size":1505,"stargazers_count":328,"open_issues_count":2,"forks_count":26,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-11-04T16:44:24.301Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/progrium.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-11-13T07:33:27.000Z","updated_at":"2024-04-02T09:22:28.000Z","dependencies_parsed_at":"2022-08-26T23:41:11.029Z","dependency_job_id":null,"html_url":"https://github.com/progrium/go-extpoints","commit_stats":null,"previous_names":["progrium/go-plugins"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/progrium%2Fgo-extpoints","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/progrium%2Fgo-extpoints/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/progrium%2Fgo-extpoints/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/progrium%2Fgo-extpoints/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/progrium","download_url":"https://codeload.github.com/progrium/go-extpoints/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247074338,"owners_count":20879233,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T13:01:19.366Z","updated_at":"2025-04-03T20:30:48.302Z","avatar_url":"https://github.com/progrium.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# go-extpoints\n\nThis Go generator, named short for \"extension points\", provides a generic [inversion of control](http://en.wikipedia.org/wiki/Inversion_of_control) model for making extensible Go packages, libraries, and applications.\n\nIt generates package extension point singletons from extension types you define. Extension points are then used to both register extensions and use the registered extensions with a common [meta-API](#extension-point-api).\n\n[Logspout](https://github.com/gliderlabs/logspout) is a real application built using go-extpoints. [Read about it here.](http://gliderlabs.com/blog/2015/03/31/new-logspout-extensible-docker-logging/)\n\n## Getting the tool\n\n\t$ go install github.com/progrium/go-extpoints\n\n## Concepts\n\n#### Extension Types\n\nThese define your hooks. They can be Go interfaces or simple function signature types. Here are some generic examples:\n\n```go\ntype ConfigStore interface {\n\tGet(key string) (string, error)\n\tSet(key, value string) error\n\tDel(key string) error\n}\n\ntype AuthProvider interface {\n\tAuthenticate(user, pass string) bool\n}\n\ntype EventListener interface {\n\tNotify(event Event)\n}\n\ntype HttpEndpoint func() http.Handler\n\ntype RequestModifier func(request *http.Request)\n\n```\n\n#### Extension Points\n\nWith types defined, go-extpoints generates package singletons for each type. When your program starts, extensions are registered with extension points. Then you can then use registered extensions in a number of ways:\n\n```go\n// Lookup a single registered extension for drivers\nconfig := ConfigStores.Lookup(configStore)\nif config == nil {\n\tlog.Fatalf(\"config store '%s' not registered\", configStore)\n}\nconfig.Set(\"foo\", \"bar\")\n\n// Iterate until you get what you need\nfunc authenticate(user, pass string) bool {\n\tfor _, provider := range AuthProviders.All() {\n\t\tif provide.Authenticate(user, pass) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Fire and forget events to all extensions\nfor _, listener := range EventListeners.All() {\n\tlistener.Notify(event)\n}\n\n// Use name and return value of all extensions for registration\nfor name, handler := range HttpEndpoints.All() {\n\thttp.Handle(\"/\"+name, handler())\n}\n\n// Pass by reference to all extensions for middleware\nfor _, modifier := range RequestModifiers.All() {\n\tmodifier(req)\n}\n\n```\n\n## Extension Point API\n\nAll extension types passed to go-extpoints will be turned into extension point singletons, using the pluralized name of the extension type. These extension point objects implement this simple meta-API:\n\n```go\ntype \u003cExtensionPoint\u003e interface {\n\t// if name is \"\", the specific extension type is used.\n\t// returns false if doesn't implement type or already registered.\n\tRegister(extension \u003cExtensionType\u003e, name string) bool\n\n\t// returns false if not registered to start with\n\tUnregister(name string) bool\n\n\t// returns nil if not registered\n\tLookup(name string) \u003cExtensionType\u003e\n\n\t// for sorted subsets. each name is looked up in order, nil or not\n\tSelect(names []string) []\u003cExtensionType\u003e\n\n\t// all registered, keyed by name\n\tAll() map[string]\u003cExtensionType\u003e\n\n\t// convenient list of names\n\tNames() []string\n\n}\n```\n\nIt also generates top-level registration functions that will run extensions through all known extension points, registering or unregistering with any that are based on an interface the extension implements. They return the names of the interfaces they were registered/unregistered with.\n\n```go\n\nfunc RegisterExtension(extension interface{}, name string) []string\n\nfunc UnregisterExtension(name string) []string\n\n```\n\n## Example Application\n\nHere is a full Go application that lets extensions hook into `main()` as subcommands simply by implementing an interface we'll make called `Subcommand`. This interface will have just one method `Run()`, but you can make extension points based on any interface.\n\nAssuming our package lives under `$GOPATH/src/github.com/quick/example`, here is our `main.go`:\n\n```go\n//go:generate go-extpoints\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/quick/example/extpoints\"\n)\n\nvar subcommands = extpoints.Subcommands\n\nfunc usage() {\n\tfmt.Println(\"Available commands:\\n\")\n\tfor name, _ := range subcommands.All() {\n\t\tfmt.Println(\" - \", name)\n\t}\n\tos.Exit(2)\n}\n\nfunc main() {\n\tif len(os.Args) \u003c 2 {\n\t\tusage()\n\t}\n\tcmd := subcommands.Lookup(os.Args[1])\n\tif cmd == nil {\n\t\tusage()\n\t}\n\tcmd.Run(os.Args[2:])\n}\n```\nTwo things to note. First, the `go:generate` directive at the top. This tells `go generate` it needs to run `go-extpoints`, which will happen in a moment.\n\nAnother thing to note, the extension point is accessed by a variable named by the plural of our interface `Subcommand` and in this case lives under a separate `extpoints` subpackage.\n\nWe need to create that subpackage with a Go file in it to define our interface used for this extension point. This is our `extpoints/interfaces.go`:\n\n```go\npackage extpoints\n\ntype Subcommand interface {\n\tRun(args []string)\n}\n```\n\nWe use `go generate` now to produce the extension point code in our `extpoints` subpackage. These extension points are based on any Go interfaces you've defined in there. In our case, just `Subcommand`.\n\n\t$ go generate\n\t ...\n\t$ go install\n\nOkay, but it doesn't *do* anything! Let's make a builtin command extension that implements `Subcommand`. Add a `hello.go` file:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/quick/example/extpoints\"\n)\n\nfunc init() {\n\textpoints.Register(new(HelloComponent), \"hello\")\n}\n\ntype HelloComponent struct {}\n\nfunc (p *HelloComponent) Run(args []string) {\n\tfmt.Println(\"Hello world!\")\n}\n```\n\nNow when we build and run the app, it shows `hello` as a subcommand. We've registered the component with the name `hello`, which we happen to use to identify the name of the subcommand. Component names are optional, but can be handy for situations like this one.\n\nCertainly, the value of extension points becomes clearer with larger applications and more interesting interfaces. But just consider now that the component defined in `hello.go` *could* exist in another package in another repo. You'd just have to import it and rebuild to let it hook into our application.\n\nThere are two more in-deptch example applications in this repo to take a look at:\n\n * [tool](https://github.com/progrium/go-extpoints/tree/master/examples/tool) ([extpoints](http://godoc.org/github.com/progrium/go-extpoints/examples/tool/extpoints)), a more realistic CLI tool with subcommands and lifecycle hooks\n * [daemon](https://github.com/progrium/go-extpoints/tree/master/examples/daemon), ... doesn't exist yet\n\n\n\n## Making it easy to install extensions\n\nAssuming you tell third-party developers to call your package or extension point `Register` in their `init()`, you can link them with a side-effect import (using a blank import name).\n\nYou can make this easy for users to enable/disable via comments, or add their own without worrying about messing with your code by having a separate `extensions.go` or `plugins.go` file with just these imports:\n\n```go\npackage yourpackage\n\nimport (\n\t_ \"github.com/you/some-extension\"\n\t_ \"github.com/third-party/another-extension\"\n)\n\n```\n\nUsers can now just edit this file and `go build` or `go install`.\n\n## Usage Patterns\n\nHere are different example ways to use extension points to interact with extensions:\n\n#### Simple Iteration\n```go\nfor _, listener := range extpoints.EventListeners.All() {\n\tlistener.Notify(\u0026MyEvent{})\n}\n```\n\n#### Lookup Only One\n```go\ndriverName := config.Get(\"storage-driver\")\ndriver := extpoints.StorageDrivers.Lookup(driverName)\nif driver == nil {\n\tlog.Fatalf(\"storage driver '%s' not installed\", driverName)\n}\ndriver.StoreObject(object)\n```\n\n#### Passing by Reference\n```go\nfor _, filter := range extpoints.RequestFilters.All() {\n\tfilter.FilterRequest(req)\n}\n```\n\n#### Match and Use\n```go\nfor _, handler := range extpoints.RequestHandlers.All() {\n\tif handler.MatchRequest(req) {\n\t\thandler.HandleRequest(req)\n\t\tbreak\n\t}\n}\n```\n\n## Why the `extpoints` subpackage?\n\nSince we encourage the convention of a subpackage called `extpoints`, it makes it very easy to identify a package as having extension points from looking at the project tree. You then know where to look to find the interfaces that are exposed as extension points.\n\nThird-party packages have a well known package to import for registering. Whether you have extension points for a library package or a command with just a `main` package, there's always a definite `extpoints` package there to import.\n\nIt also makes it clearer in your code when you're using extension points. You have to explicitly import the package, then call `extpoints.\u003cExtensionPoint\u003e` when using them. This helps identify where extension points actually hook into your program.\n\n## Groundwork for Dynamic Extensions\n\nAlthough this only seems to allow for compile-time extensibility, this itself is quite a win. It means power users can build and compile in their own extensions that live outside your repository.\n\nHowever, it also lays the groundwork for other dynamic extensions. I've used this model to wrap extension points for components in embedded scripting languages, as hook scripts, as remote plugin daemons via RPC, or all of the above implemented as components themselves!\n\nNo matter how you're thinking about dynamic extensions later on, using `go-extpoints` gives you a lot of options. Once Go supports dynamic libraries? This will work perfectly with that, too.\n\n## Inspiration\n\nThis project and component model is a lightweight, Go idiomatic port of the [component architecture](http://trac.edgewall.org/wiki/TracDev/ComponentArchitecture) used in Trac, which is written in Python. It's taken about a year to get this right in Go.\n\n![Trac Component Architecture](http://trac.edgewall.org/raw-attachment/wiki/TracDev/ComponentArchitecture/xtnpt.png)\n\n## License\n\nBSD\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprogrium%2Fgo-extpoints","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprogrium%2Fgo-extpoints","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprogrium%2Fgo-extpoints/lists"}