<p>Hey <a href="/r/golang">/r/golang</a>,</p>
<p>I've been racking my head on the best way to seed my DB, Cache, and other third party services throughout my application.</p>
<p>For sake of speed, I initially just used globally accessable objects within packages dedicated to those issues, but I'm now at a point where we can refactor a better (and more testable) solution into the code.</p>
<p>I've been researching quite a bit on the best way to do this though, and seen a lot of non-consensus on the topic. The most popular answer seems to be:</p>
<blockquote>
<p>Define an interface for the dependency and pass it as an argument into each function or method that requires it.</p>
</blockquote>
<p>This works great in the small examples posted on SO and elsewhere, but I feel like littering my code with a ton of arguments goes against the reasons for DRY - let's say I decide to start caching my DB reads - now I need to go back through everything that accepts a DB dependency, and add in the dependency for the cache that's going to be used. It makes things very clear, but it's not very encapsulated, and given the proclivity of dependencies to change, I feel like that might be purity at the cost of adaptability.</p>
<p>I came up with the following solution for dealing with this problem, and want to see if anyone else has thoughts on why this would be a huge mistake, or ways the approach could be improved:</p>
<p><a href="http://play.golang.org/p/NOVKZCxpca">Play Example</a></p>
<p>I feel like this is a fairly flexible solution. In my case, since all the services I need currently operate like singletons (I don't need multiple DB backends, caching backends, etc), I can continue using global instances of these services across my code, but it becomes trivial to test my own implimentations by overriding the interfaces. If I ever needed to have multiple instances of a dependency in the future, I could always adapt the code at a later point of time with a service factory and pass it as an argument localized to only the area where it differs from the global dependency.</p>
<p>The other big area of concern would be race conditions, but as I don't plan on changing the implimentations during runtime often (only within tests), I think a mutex would solve that issue.</p>
<p>Has anyone else found a more elegant way of peppering multiple dependencies into their code across controllers/models/middlewares/etc? Or see ways I can make this better/other concerns I haven't anticipated?</p>
<p>TL;DR: To solve the issue of dependency injection for third party services and to make my life easier during testing without adding multiple arguments everywhere, I devised an approach of composing a container object with the dependency as an interface and a setter method to overwrite that interface, optionally fulfilling the interface itself, and using that object in my codebase instead. I'm looking for thoughts and/or criticisms from the community.</p>
<hr/>**评论:**<br/><br/>printf_hello_world: <pre><p>Absolutely love this pattern. Check <a href="http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/">this</a> out; I think you'll find it relevant.</p></pre>kromem: <pre><p>Ohh... that's neat! Thank you for sharing.</p></pre>breerly: <pre><p>Just use plain-ol DI:</p>
<ol>
<li>Inject dependencies onto structs where the funcs that need access to the deps are methods on the struct.</li>
<li>Wire up the dependency graph by hand in the main function.</li>
<li>Eventually, where needed, create factories.</li>
<li>Once there is significant wiring and factory logic, consider a DI framework to centralize and streamline initialization logic.</li>
</ol></pre>kromem: <pre><p>I think you're probably right. </p>
<p>I also think an advantage of the basic approach would be discouraging the direct access of dependencies beyond the bare minimum, which would be evident in step #2 becoming a nightmare.</p></pre>breerly: <pre><p>Yup. </p>
<p>As much as possible you want to avoid a global dependency registry or service locator approach, which is what the example you posted appears to be. </p>
<p>Once #2 gets big/complex enough, you'll want to use a service container (#4).</p></pre>mnsota: <pre><p>This is a slightly cleaner approach and the type of your global will be Commenter rather than Container. <a href="http://play.golang.org/p/-aE6FUmrGU" rel="nofollow">http://play.golang.org/p/-aE6FUmrGU</a></p>
<p>A more elegant approach would be to use the factory and driver patterns. That way you can change the type of Commenter that is constructed based on a build tag, a test file, or even a string.</p></pre>jerf: <pre><p>I wrote up something I call the <a href="http://www.jerf.org/iri/post/2929" rel="nofollow">environment object pattern</a>, which I still prefer to anything I see linked in this discussion as of this writing.</p>
<p>Also, note that if all your setter does is set something, you might as well just have a public member. You're doing more paperwork than you need to. (Heck, all these other suggestions are doing more paperwork than they need to.)</p></pre>R2A2: <pre><p>have a look at facebook's <a href="https://godoc.org/github.com/facebookgo/inject" rel="nofollow">inject</a>. I'm planning to use it to replace a big ugly chunk of wiring code (about 50 of these objects, all wired together in a repetitive way)</p></pre>egonelbre: <pre><p>Could you say/show what the actual example is. It's very hard to see what the best solution would be from a trivialized example.</p>
<p>Regarding your proposed solution - you can just use a global variable. There's no point in putting a container around it. e.g. <a href="http://play.golang.org/p/RgWeJY7GaN" rel="nofollow">http://play.golang.org/p/RgWeJY7GaN</a>.</p>
<p>After a lot of experimenting, using interface(s) as a separation layer seems to work the best. e.g. <a href="https://github.com/raintreeinc/knowledgebase/blob/master/kb/database.go#L22" rel="nofollow">https://github.com/raintreeinc/knowledgebase/blob/master/kb/database.go#L22</a>. <em>Note: WIP, not ready for general consumption.</em> That completely defines my database dependency,although it's not "database" probably "Services" would be more precise. But it defines most dependencies that the Server has. And it becomes a dependecy to the server <a href="https://github.com/raintreeinc/knowledgebase/blob/master/kb/server.go#L64" rel="nofollow">https://github.com/raintreeinc/knowledgebase/blob/master/kb/server.go#L64</a>.</p>
<p>If I need caching I could do it two ways - either put it inside the database and make it transparent or create a separate interface for cache and use that inside. One is better because it's transparent to the rest of code, of course the separate cache interface might help with integrating with other things.</p>
<p>Of course how you design your interfaces is not that clear, but mostly can be designed via constantly pushing code down. I.e. asking the question "Does the current package need to care how exactly X is done? No --> move it to an interface."</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传