{"id":19028065,"url":"https://github.com/ffcactus/go-container","last_synced_at":"2025-10-27T02:02:33.262Z","repository":{"id":127618534,"uuid":"200367640","full_name":"ffcactus/go-container","owner":"ffcactus","description":null,"archived":false,"fork":false,"pushed_at":"2019-08-04T07:15:24.000Z","size":1520,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-02T03:18:52.463Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/ffcactus.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-03T11:28:09.000Z","updated_at":"2020-12-11T03:13:17.000Z","dependencies_parsed_at":"2023-07-09T08:46:45.898Z","dependency_job_id":null,"html_url":"https://github.com/ffcactus/go-container","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffcactus%2Fgo-container","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffcactus%2Fgo-container/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffcactus%2Fgo-container/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ffcactus%2Fgo-container/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ffcactus","download_url":"https://codeload.github.com/ffcactus/go-container/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240079610,"owners_count":19744720,"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-11-08T21:09:53.019Z","updated_at":"2025-10-27T02:02:28.230Z","avatar_url":"https://github.com/ffcactus.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Overview\n\n\nIf you ever write code with Java Spring perhaps you do not quite understand the benefit it brings to you until you write code in something like Go. For example, you write controller in Java like this:\n```java\n@Controller\nclass Controller {\n\n  @Autowired\n  private MessageService message\n  \n  public String HttpGet() {\n    return message.Get()\n  }\n}\n```\nSpring will help you create the Controller and set the message with an appropriate value, it will search the Classes that implements the MessageService interface, create it and assign it to message. message may have to be a singleton instance, in its constructor, a connection to the DB is made, so you should not create message again. Without Spring, you may need to create the message in the main() and pass it as an argument in Controller's constructor. However, it doesn't sound good, because:\n- If the controller needs many others, all of them have to be passed in the constructor.\n- The DB component and the controller component are not quite related, they both can work independently, why do you put DB component as an argument in the controller's constructor?\n- The controller itself is a singleton, and the message service component may use other singleton object. So if we follow this style, we need to carefully create all the singletons at the beginning, it's hard to maintain for a large project.\nAnother benefit Spring brings is it make the code quite easy to test. You can replace the implementation in the UT without modifying the function.\n\nHow can we solve this problem with Go? We can mock what Spring does.\n\nTo do so, we need to create the singleton object in the right order. We can utilize the init() function.  Suppose you have controller.go and message.go and both of them have init() functions. Since controller.go will use Message interface, the init() function in message.go will be called first. If we insert an ID into a queue in the init() function and that ID represents the class, we would know the right order to initialize all the singletons.\n\nThen we can initialize all the singletons in main(). Why not initialize the singletons directly in init()? Because that prevents us to replace the implementations in go test.\n\nHere is the code, you can find the full code in the\n\nhttps://github.com/ffcactus/go-container\n\nSuppose we have controller, service and message components. The controller will call service, and service will call message.\n\nThe container:\n\n```go\n// container/container.go\n\n// Package container includes the files that represents the container concept.package container\npackage container\n\nimport (\n\t\"fmt\"\n)\n\nvar (\n\t// to record the order to constructor all the instances.\n\torder []string\n\t// saving all the instances.\n\tbeans = make(map[string]interface{})\n\t// saving all the instances' constructor.\n\tconstructors = make(map[string]func() interface{})\n)\n\n// Register will register the bean's name and constructor to the container\nfunc Register(name string, constructor func() interface{}) {\n\tif beans[name] != nil {\n\t\tfmt.Printf(\"Registering duplicated bean: %s\", name)\n\t\treturn\n\t}\n\torder = append(order, name)\n\tconstructors[name] = constructor\n}\n\n// Bean returns bean's instance by bean's name.\nfunc Bean(name string) interface{} {\n\treturn beans[name]\n}\n\n// Replace means instance, after this method the bean will be considered as initialized.\nfunc Replace(name string, newInstance interface{}) {\n\tbeans[name] = newInstance\n}\n\n// Init will initialize all the instances.\nfunc Init() {\n\tfor _, name := range order {\n\t\tfmt.Printf(\"Init bean %s\\n\", name)\n\t\tbeans[name] = constructors[name]()\n\t}\n}\n```\n\nThe controller:\n\n```go\n// controller/controller.go\n\n// Package controller includes the files for controller component.\npackage controller\n\nimport (\n\t\"go-container/container\"\n\t\"go-container/service\"\n)\n\nconst (\n\t// BeanName can be used to get the instance of the controller.\n\tBeanName = \"Controller\"\n)\n\nvar (\n\tsvc service.Service\n)\n\nfunc init() {\n\tcontainer.Register(BeanName, func() interface{} {\n\t\tsvc = container.Bean(service.BeanName).(service.Service)\n\t\treturn \u0026DefaultImpl{}\n\t})\n}\n\n// Controller defines the interface.\ntype Controller interface {\n\tHTTPGet() string\n}\n\n// DefaultImpl implements the Controller interface.\ntype DefaultImpl struct {\n}\n\n// HTTPGet mock HTTP Get.\nfunc (DefaultImpl) HTTPGet() string {\n\treturn svc.Message()\n}\n```\n\nThe service:\n\n```go\n// service/service.go\n\n// Package service contains service components.package service\npackage service\n\nimport (\n\t\"go-container/container\"\n\t\"go-container/message\"\n)\n\nconst (\n\t// BeanName is the name to get the service instance.\n\tBeanName = \"Service\"\n)\n\nvar (\n\tmessageService message.Message\n)\n\nfunc init() {\n\tcontainer.Register(BeanName, func() interface{} {\n\t\tmessageService = container.Bean(message.BeanName).(message.Message)\n\t\treturn \u0026DefaultImpl{}\n\t})\n}\n\n// Service define the interface.\ntype Service interface {\n\tMessage() string\n}\n\n// DefaultImpl is the default implementation of Service.\ntype DefaultImpl struct {\n}\n\n// Message return the message.\nfunc (s *DefaultImpl) Message() string {\n\treturn messageService.Get()\n}\n```\n\nThe message:\n\n```go\n// message/message.go\n\n// Package message includes the files for message component.\npackage message\n\nimport (\n\t\"go-container/container\"\n)\n\nconst (\n\t// BeanName can be used to get the instance.\n\tBeanName = \"Message\"\n)\n\nfunc init() {\n\tcontainer.Register(BeanName, func() interface{} {\n\t\treturn \u0026DefaultImpl{\n\t\t\tmessage: \"Hello, World\",\n\t\t}\n\t})\n}\n\n// Message is the interface for message component.\ntype Message interface {\n\tGet() string\n}\n\n// DefaultImpl implements the Message interface.\ntype DefaultImpl struct {\n\tmessage string\n}\n\n// Get returns the message.\nfunc (impl *DefaultImpl) Get() string {\n\treturn impl.message\n}\n```\n\nThe controller's test:\n\n```go\n// controller/controller_test.go\n\npackage controller\n\nimport (\n\t\"testing\"\n)\n\nconst (\n\texpected = \"mock Message\"\n)\n\ntype mockService struct {\n}\n\n// Message return the message.\nfunc (s *mockService) Message() string {\n\treturn expected\n}\n\nfunc TestController_HTTPGet(t *testing.T) {\n\tsvc = \u0026mockService{}\n\tc := \u0026DefaultImpl{}\n\tactual := c.HTTPGet()\n\tif expected != actual {\n\t\tt.Errorf(\"expect %v, actual %v\", expected, actual)\n\t}\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fffcactus%2Fgo-container","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fffcactus%2Fgo-container","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fffcactus%2Fgo-container/lists"}