{"id":28315862,"url":"https://github.com/drborges/katana","last_synced_at":"2025-07-13T17:08:53.269Z","repository":{"id":36947270,"uuid":"41254807","full_name":"drborges/katana","owner":"drborges","description":"Dependency Injection package driven by constructor functions","archived":false,"fork":false,"pushed_at":"2021-02-02T16:30:24.000Z","size":140,"stargazers_count":5,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-01T11:16:01.944Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://drborges.github.io/katana","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/drborges.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-08-23T15:21:03.000Z","updated_at":"2021-02-02T16:30:26.000Z","dependencies_parsed_at":"2022-07-11T14:52:28.317Z","dependency_job_id":null,"html_url":"https://github.com/drborges/katana","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/drborges/katana","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drborges%2Fkatana","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drborges%2Fkatana/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drborges%2Fkatana/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drborges%2Fkatana/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/drborges","download_url":"https://codeload.github.com/drborges/katana/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drborges%2Fkatana/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265175567,"owners_count":23722661,"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":"2025-05-25T01:12:50.478Z","updated_at":"2025-07-13T17:08:53.260Z","avatar_url":"https://github.com/drborges.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Katana\n\n[![Build Status](https://travis-ci.org/drborges/katana.svg?branch=master)](https://travis-ci.org/drborges/katana)\n\nDependency Injection Driven By Constructor Functions\n\n## Brief Overview\n\nkatana approaches DI in a fairly simple manner. For each type that needs to be available for injection -- a.k.a `injectable` -- a [constructor function](https://golang.org/doc/effective_go.html#composite_literals) needs to be registered with an instance of `kanata.Injector`.\n\n```go\nfunc NewUserService(depA *DependencyA, depB *DependencyB) *UserService {\n\treturn \u0026UserService{depA, depB}\n}\n```\n\nOnce a provider is registered the corresponding injectable can be resolved and injected as dependency into other injectable providers, or even into arbitrary functions. Lets see how that translates into code:\n\n```go\n// Get an instance of katana's injector\ninjector := katana.New()\n\n// Register the following instances as injectables\ndepA, depB := \u0026DependencyA{}, \u0026DependencyB{}\n\n// Register a constructor function to provide instances of *UserService\ninjector.Provide(depA, depB).ProvideNew(\u0026UserService{}, NewUserService)\n\n// Grab a new instance of *UserService with all its dependencies injected\nvar service *UserService\ninjector.Resolve(\u0026service)\n```\n\nKatana will detect and panic upon any eventual `cyclic dependency` when resolving an injectable, providing the cyclic dependency graph so you can easily troubleshoot.\n\n## Example\n\nLets say you have the following types each with their own dependencies:\n\n```go\ntype Config struct {\n\tDatastoreURL string\n\tCacheTTL     int\n\tDebug        bool\n}\n\ntype Cache struct {\n\tTTL int\n}\n\ntype Datastore struct {\n\tCache *Cache\n\tURL   string\n}\n\ntype AccountService struct {\n\tDatastore *Datastore\n}\n```\n\nA constructor function for each type of injectable is created and registered with a new instance of `katana.Injector`\n\n```go\n// Grabs a new instance of katana.Injector\ninjector := katana.New()\n\n// Registers the given instance of Config to be provided as a singleton injectable\ninjector.Provide(Config{\n\tDatastoreURL: \"https://myawesomestartup.com/db\",\n\tCacheTTL:     20000,\n})\n\n// Registers a constructor function that always provides a new instance of *Cache\ninjector.ProvideNew(\u0026Cache{}, func(config Config) *Cache {\n\treturn \u0026Cache{config.CacheTTL}\n})\n\n// Registers a constructor function that always provides a new instance of *Datastore\n// resolving its dependencies -- Config and *Cache -- as part of the process\ninjector.ProvideNew(\u0026Datastore{}, func(config Config, cache *Cache) *Datastore {\n\treturn \u0026Datastore{cache, config.DatastoreURL}\n})\n\n// Registers a constructor function that lazily provides the same instance of *AccountService\n// resolving its dependencies -- *Datastore -- as part of the process.\ninjector.ProvideSingleton(\u0026AccountService{}, func(db *Datastore) *AccountService {\n\treturn \u0026AccountService{db}\n})\n```\n\nFinally you can get instances of the provided `injectables` with all their dependencies -- if any -- resolved:\n\n```go\nvar service1, service2 *AccountService\nvar db1, db2 *Datastore\nvar cache1, cache2 *Cache\nvar config Config\n\n// Katana allows you to resolve multiple instances on a single \"shot\"\n// \n// Note that:\n// 1. service1 == service2: *AccountService provider is a singleton\n// 2. db1 != db2: *Datastore injectable is not singleton\n// 3. cache1 != cache2: *Cache is not a singleton\n// 4. config will point to the Config instance defined in the previous code block, since it was provided using Injector#Provide method.\ninjector.Resolve(\u0026service1, \u0026service2, \u0026db1, \u0026db2, \u0026cache1, \u0026cache2, \u0026config)\n```\n\n# Injecting Interfaces\n\nIn Go there is no way to pass in types as function arguments and types are derived through reflection from actual instances.\n\nIn addition to that an interface cannot be instantiated either, which makes things a little trick when writing generic code like a DI container.\n\nKatana solution for injecting into interface references might seem a bit strange at first, but you'll get used :)\n\nLets say we want to provide a particular implementation of `http.ResponseWriter` to be injected as dependency. With `katana` you would do the following:\n\n```go\ninjector.ProvideAs((*http.ResponseWriter)(nil), writer)\n```\n\n`(*http.ResponseWriter)(nil)` is how we tell katana to treat `writer` as a `http.ResponseWriter` rather than its actual underlying implementation `*http.response`.\n\nWith that whenever a dependency to `http.ResponseWriter` is detected, it will be resolved as that particular `writer` instance.\n\n# Thread-Safety\n\nIn order to use `katana` in a `multi-thread` environment you should use a copy of the injector per thread.\n\nCopies of `katana.Injector` can be created using `Injector.Clone()`. This copy will have all the registered providers of the original injector and every new provider registered in the new copy will not be available to other copies of `katana.Injector`.\n\n**Note** Singleton providers will still yield the same instances across different threads.\n\n### Example: HTTP Server\n\nAssuming we have the injector instance from the example above ^\n\n```go\nhttp.HandleFunc(\"/bar\", func(w http.ResponseWriter, r *http.Request) {\n\tvar service *AccountService\n\tinjector.Clone().\n\t\tProvideAs((*http.ResponseWriter)(nil), w).\n\t\tProvide(r).Resolve(\u0026service)\n})\n\nlog.Fatal(http.ListenAndServe(\":8080\", nil))\n```\n\n# Injecting Function Arguments\n\nKatana also allows you to inject arguments into functions (that is how it resolves the arguments of a injectable provider):\n\n```go\nfetchAllAccounts := injector.Inject(func(srv *AccountService, conf Config) ([]*Account, error) {\n\tif conf.Debug {\n\t\treturn mocks.Accounts(), nil\n\t}\n\treturn srv.Accounts()\n})\n```\n\n`Injector#Inject` returns a closure holding all the resolved function arguments and when called returns a `katana.Output` with the function returning values.\n\n```go\nif result := fetchAllAccounts(); !result.Empty() {\n\taccounts, err := result[0], result[1]\n}\n```\n\n# Contributing\n\nPlease feel free to submit issues, fork the repository and send pull requests!\n\nWhen submitting an issue, please include a test function that reproduces the issue, that will help a lot to reduce back and forth :~\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrborges%2Fkatana","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrborges%2Fkatana","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrborges%2Fkatana/lists"}