what is the go idiomatic way of passing DB instance between modules ?

blov · · 564 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I am practicing with go on writing a REST api for one of my apps that is already written Ruby. I am wondering what is the proper way to pass the DB instance between modules ?</p> <p>What i already have in mind is that i create a package called &#34;db&#34; for example, and inside it i create an instance called DB, so from other modules i can import &#34;db&#34; and use it like this db.DB.SqlSomething .. but that doesn&#39;t seem write.</p> <p>Suggestions?</p> <hr/>**评论:**<br/><br/>peterbourgon: <pre><p>Your modules should look like this.</p> <pre><code>type Foo struct { db *sql.DB // ... } func NewFoo(db *sql.DB, ...) *Foo { return &amp;Foo{ db: db, // ... } } </code></pre> <p>Pass database handles to object constructors explicitly. Don&#39;t use package-global state.</p></pre>emostafa: <pre><p>that&#39;s seems be to be the best way i&#39;ve seen so far, but in that case my http handlers are going to be functions attached to Foo ? e.g:</p> <pre><code>func (f *Foo) ListSomeFoos(w http.ResponseWriter, r *http.Request) { // do some stuff f.DB.Query() } </code></pre></pre>puffybunion: <pre><p>As an extension to this, when possible you should strive to pass an interface of the DB handler.. That way you can mock it. Not always possible but when it is it helps a lot with testing.</p></pre>heraclmene: <pre><p>Alternatively encapsulate your database logic in some sort of servicer/repository and mock that. </p></pre>PsyWolf: <pre><p>Yes, that&#39;s correct. I almost always make my handlers methods on a Env struct as described in <a href="http://www.alexedwards.net/blog/organising-database-access" rel="nofollow">http://www.alexedwards.net/blog/organising-database-access</a></p> <p>That said, you can instead use closures to achieve​ the same effect.</p></pre>joncalhoun: <pre><p>Yes. That is basically what it will be. </p> <p>You can do the Singleton approach you described in the original post for smaller apps but over time it will likely need changed. It isn&#39;t hard to change but just takes some time.</p></pre>LeafGoBap: <pre><p>As an aside, are there downsides to using closures to achieve the same thing?</p> <p>Eg:</p> <pre><code>func InitHandler(db *Db) http.HandlerFunc { return func(*Request, *Response) { // .. fake code, ignore the bad handler signature ... } } </code></pre> <p>I&#39;ve not heard of speed differences / downsides to closures. I imagine/hope they&#39;re not too great, as a lot of people use closures for goroutines. Eg:</p> <pre><code>var db *Db go func(){ // .. db usage }() </code></pre> <p>and <em>(i think this is valid?)</em></p> <pre><code>var db *Db go func(db *Db) { // .. db usage }(db) </code></pre></pre>joncalhoun: <pre><p>The biggest downside to a closure here is when you need 3-4 pieces of data (database objects, already instantiated templates, etc) it can get a bit cumbersome. But both work well enough.</p></pre>PsyWolf: <pre><p>Not really, but methods are more readable imo. Between the struct and method declaration, you can quickly see all the variables the method can access. To do the same thing with closures, you have to read through the whole function that created the closure. </p></pre>LeafGoBap: <pre><p>I&#39;m curious what the comparison would be to passing around some type of custom HttpVars <em>(badly named)</em> struct via closures.</p> <p>The one thing i like about closures is that they can be moved to new packages very easily, to break up larger packages.</p></pre>PsyWolf: <pre><p>It&#39;s really a matter of preference. If you like closures for this, by all means, use them. </p> <p>I don&#39;t follow you on how closures would be easier to break up though. You could just as easily break of your Env into a multiple structs across multiple packages. I don&#39;t see either approach as being better for modularization.</p></pre>mixedCase_: <pre><p>There are speed differences but they are really small for it to ever be a problem for this usecase.</p></pre>earthboundkid: <pre><p>Are there really speed differences in Go? ISTM that behind the scenes the implementation by the compiler would be pretty much identical, you just don&#39;t see the &#34;virtual strict&#34; when you use a closure. </p></pre>mixedCase_: <pre><p>There used to be difference at least, from what I remember seeing in benchmarks. I guess it&#39;s easy to benchmark yourself if you&#39;re curious enough.</p></pre>icholy: <pre><p>Here&#39;s what I do.</p> <pre><code>pkg.FuncThatUsesDB(db) // or thing := pkg.NewThingThatNeedsDB(db) thing.MethodThatUsesDB() </code></pre></pre>chmikes: <pre><p>This is indeed the idiomatic way to do it. But http handlers are not methods. So the question is how to pass the database to these handlers ? Apparently, the db has to be a global variable. That is not so nice. </p></pre>softwaregav: <pre><p>Actually, http handlers can be methods. For example, if you want to break up your handlers into &#34;controllers&#34;, you would have something like:</p> <pre><code>package user type UserController struct { db *sql.DB } func NewController(db *sql.DB) *UserController { return &amp;UserController{db:db} } func (u *UserController) Login(w http.ResponseWriter, r *http.Request) { // validate input u.db.GetUser() // more stuff } </code></pre> <p>Then your main would be something like:</p> <pre><code>func main() { db := db.NewDB() controller := user.NewController(db) http.Handle(&#34;/user/login&#34;, controller.Login) http.ListenAndServe(&#34;:8080&#34;, nil) } </code></pre> <p>The link that <a href="/u/miko5054">/u/miko5054</a> posted provides a great example. It goes more in depth with this concept, like using an interface instead of taking in the concrete db.</p></pre>So_Brave: <pre><p>This is basically how I&#39;ve settled on it. Technically I pass my database instance to my service, which I then inject (the service) into my controller. Define an interface for your service and test your controller against that... bring it all together in the main function. Feels really clean. </p></pre>softwaregav: <pre><p>That&#39;s exactly how I set my projects up as well. It&#39;s the cleanest way I&#39;ve seen so far. It kind of stinks starting up with Go because the online tutorials seem to be really basic or pretty advanced. This feels like a grey area that most beginner Go developers come across where there isn&#39;t much guidance out there on the internet. Those of us still learning get left behind because we don&#39;t have the experience to catch on to things as quickly as people who have been developing for years.</p></pre>So_Brave: <pre><p>Definitely. I was primarily a Node developer, but we decided to use Go for a few internal APIs at my company for a variety of reasons. For a while I was definitely writing my Go programs like Express/Node apps. I had questions just like this and learned mostly through trial and error and looking at a lot of other repos. </p> <p>One of the most helpful projects for me to look at was <a href="https://github.com/go-kit/kit" rel="nofollow">go-kit</a> (thanks <a href="/u/peterbourgon" rel="nofollow">/u/peterbourgon</a>)! It really helped me develop a better mindset of how the pieces fit together in a Go project (e.g. large main func tying dependencies together/satisfying DI interfaces, endpoints vs services, middleware granularity).</p> <p>We&#39;re really satisfied with the result. It&#39;s trivially testable, new team members have been able to jump on and add features, and our complexity is isolated to places where it makes sense (mostly vague business requirements :D). </p> <p>Now when I write Node programs they look like Go programs....</p></pre>softwaregav: <pre><p>That&#39;s awesome. I am actually following that path almost exactly lol. I started learning .NET for my internship, and then I joined a startup company that wanted to use Go. I have been learning through lots of trial and error and exploring lots of other repos, but it was difficult at first just like you said. I was trying to make all my services look like my .NET monolithic code. We&#39;re starting to use a microservice architecture, and I just started looking at go-kit the other day. It looks pretty awesome. I discovered it the other day via a video of Matt Heath speaking at a goto conference about Monzo, the company he works for. The <a href="https://www.youtube.com/watch?v=WiCru2zIWWs" rel="nofollow">talk</a> is pretty interesting.</p></pre>peterbourgon: <pre><p>Yaaay! This is lovely to hear :)</p></pre>kpurdon: <pre><p>lots of good answers here: <a href="https://www.reddit.com/r/golang/comments/5vsz2t/what_is_the_best_way_to_pass_a_db_to_web_handlers/" rel="nofollow">https://www.reddit.com/r/golang/comments/5vsz2t/what_is_the_best_way_to_pass_a_db_to_web_handlers/</a></p></pre>miko5054: <pre><p><a href="http://www.alexedwards.net/blog/organising-database-access" rel="nofollow">http://www.alexedwards.net/blog/organising-database-access</a> Request-scoped context</p> <p>passing the db instance within the context</p></pre>PsyWolf: <pre><p>Direct quote from that article</p> <blockquote> <p>Personally I&#39;m not a fan of storing application-level variables in request-scoped context – it feels clunky and burdensome to me. The x/net/context documentation kinda advises against it too: </p> <blockquote> <p>Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.</p> </blockquote> </blockquote></pre>

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

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