init() vs dependency injection

polaris · · 724 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I am in the process of deciding how to organize my application. Structs that will be accessed in various places like <em>configuration</em>, <em>database</em> and others could be injected (e.g. with <a href="https://github.com/facebookgo/inject">facbook inject</a>) or handled in subpackages.</p> <p>Consider the following structure:</p> <pre><code>. ├── config │   ├── development.yaml │   └── loader.go ├── database │   └── database.go └── server.go </code></pre> <p>With <code>loader.go</code> looking like this</p> <pre><code>package config var Config configuration func init(){ Config := loadConfig() } </code></pre> <p>And <code>server.go</code> like this</p> <pre><code>package main import &#34;github.com/brasilikum/server/config&#34; func main() { configuration := config.Config } </code></pre> <p>So far I have figured out some things:</p> <ul> <li>init() is less <em>magic</em></li> <li>init() runs only once, even if the subpackage is imported multiple times in the same project</li> <li>with DI, you don&#39;t have to know where in your code the struct is initialized/populated</li> </ul> <p>How do you handle things like configuration and db-access? Thoughts on when to use init() an when to use DI? </p> <p>Edit: Formatting</p> <hr/>**评论:**<br/><br/>bkeroack: <pre><p>I tend to use global config structs in most cases. I&#39;ll use context for things that (obviously) are specific to a particular request (for web applications). Anything &#34;magic&#34; is technical debt--I prefer simplicity and transparency (always knowing where a structure/object is coming from and how it&#39;s populated/instantiated) to the momentary convenience of &#34;everything is everywhere&#34; of DI.</p> <p>One thing: Keep in mind that init() will also run for your tests.</p></pre>Brasilikum: <pre><p>Ok, that sounds like idiomatic go :)<br/> Thanks for the reminder about the tests!</p></pre>Brasilikum: <pre><p>Ok, what do you suggest for testing purposes?<br/> Overwriting the Config would be possible, but not very elegant... It would result in something like:</p> <ol> <li>&#34;dev&#34;-config loaded via init()</li> <li>set &#34;ENV&#34; to &#34;test&#34;</li> <li>config.Config = config.LoadConfigByEnv</li> </ol> <p>Better ideas?</p></pre>bkeroack: <pre><p>I usually populate the application config from main() not init(). For my tests, I implement TestMain() somewhere (usually &#34;all_test.go&#34;) which creates the global config.</p></pre>bketelsen: <pre><blockquote> <p>LoadConfigByEnv</p> </blockquote> <p>+1 I create a struct that has all the dependencies, and instantiate it in main(). You can do the same thing in your tests, but replace with your mocks, etc.</p></pre>aikah: <pre><p>s/magic/dynamic</p> <p>There is no &#34;magic&#34; in go, except the make and append functions which are built in and &#34;magically&#34; work.</p> <p>DI is about exchanging compile time safety for run-time percs.</p></pre>jerf: <pre><p>I use something I call the <a href="http://www.jerf.org/iri/post/2929">environment pattern</a>.</p></pre>Brasilikum: <pre><p>Ok, this makes sense. Say I want to register <a href="https://github.com/jinzhu/gorm" rel="nofollow">gorm</a>. Would I have to implement all the functions I am using in an interface? </p></pre>jerf: <pre><p>I&#39;m going to guess you&#39;ve got no realistic chance of swapping out gorm with some sort of test version of it. In that case, if the &#34;environment&#34; interface was:</p> <pre><code>type Environment interface { // whatever goes in here for the environment } </code></pre> <p>I&#39;d make a concrete object that composed that in:</p> <pre><code>type Env struct { db // whatever the gorm DB&#39;s type is Environment } </code></pre> <p>Then you can still swap <code>Environment</code>s in and out, but you have a concrete place for the db. (You also can choose whether to embed the gorm DB object or not.)</p> <p>Composition is so nicely flexible on this front.</p> <p>I&#39;d also consider wrapping all direct invocations of gorm behind a more conceptual API (i.e., instead of directly invoking it to load a user, have a LoadUser method somewhere), and giving that conceptual API an interface. It depends on how passionate you are about unit testing, the probability of ever swapping gorm out, and what the cost/benefit is for this level of abstraction. A lot of my recent Go work has been on infrastructure stuff where it is to my advantage to make it bullet-proof, but not everything has to be up to that standard. I tend to err in favor of testing at the moment, just because of where my head is.</p></pre>3264128256: <pre><p>I like to be explicit, so I would pass a pointer to config as a parameter to functions when required and make it a field of struct (usually) in case of a method.</p> <pre><code>func ListUsers(c *Config, count, page int) ([]*User, error) </code></pre> <p>and </p> <pre><code>type UserEndpoints struct { config *Config } func (ue *UserEndpoints) List(count, page int) ([]*User, error) </code></pre> <p>Sometimes you also need to initialize packages with these configurations, in those cases I have a special Init method that must be called when initializing the main program.</p> <pre><code>package main import ( &#34;db/user&#34; ) func main() { c := &amp;Config{} c.Load() user.Init(c) } </code></pre> <p>I only use init() for things that cannot fail.</p></pre>Blobqx: <pre><p>If &#39;loadConfig&#39; can fail (by that I mean It loads something like a file or connects to something) you shouldn&#39;t use it in init. You will need proper Logs to output what happens if something goes wrong.</p> <p>Also I don&#39;t get the idea behind dependency injection in this case. You want a configuration and a database struct to be accesses from anywhere. So you could just put them as &#39;globals&#39; in your packages and provide a function to setup those which returns an error.</p> <p>What I did I wrote my own app class which basicly initialized a specific context. However you need to remember there you probably need a specific order as the database is probably dependant on the config and so on. </p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

724 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传