<p>I am a go novice so be gentle. I have been messing around with http handlers and I have been researching the best ways to pass things like my db and cache pointers down through the handler stack. Then I read about Context being added in go1.7. My question is, will this allow me to add my db and cache pointer to the the Context stored in the request and have it available in all of my http handlers?</p>
<hr/>**评论:**<br/><br/>bkeroack: <pre><p>KISS principle. Global Config struct with DB session/handle, etc, unless you can demonstrate you need something more complex.</p></pre>daveddev: <pre><p>This is a good reminder. Though, needing something more complex comes up quickly when testing is introduced. It's also a bit unsightly for anything except the smallest projects.</p></pre>danredux: <pre><p>Currently guiding my contractee through this separation of request-scope and handler-scope.. Another great benefit is auto-complete doesn't work when looking for context values, ie, there's no way to know what is available to you in the context.</p></pre>daveddev: <pre><p>That sort of context type is for passing data which pertains to the immediately related scope. So, a request context will hold request-scoped data. If you need server-scoped (in other words, http.Handler-scoped) dependencies, create a type which holds them and define methods on that type which will act as the handler funcs.</p>
<p>For Handler dependencies, see <a href="https://elithrar.github.io/article/map-string-interface/" rel="nofollow">https://elithrar.github.io/article/map-string-interface/</a></p>
<p>For request context, see my library <a href="https://github.com/codemodus/chain" rel="nofollow">https://github.com/codemodus/chain</a> (if possible an upgrade script will be provided for the changes coming in 1.7).</p></pre>nesigma: <pre><blockquote>
<p>That sort of context type is for passing data which pertains to the immediately related scope.</p>
</blockquote>
<p>I am curious, is it a bad technique to pass db down with the context and why?</p>
<blockquote>
<p>If you need server-scoped (in other words, http.Handler-scoped) dependencies, create a type which holds them and define methods on that type which will act as the handler funcs.</p>
</blockquote>
<p>Also what are the advantages of creating type + methods over just passing it down with the context?</p></pre>TheMerovius: <pre><blockquote>
<p>I am curious, is it a bad technique to pass db down with the context and why?</p>
</blockquote>
<p>One disadvantage of the context as implemented is, that it is relatively slow, as it's basically a linked list. If you stuff too much stuff into it, that will become noticeable. So if there is a better place, like a struct-field as suggested, use that.</p>
<blockquote>
<p>Also what are the advantages of creating type + methods over just passing it down with the context?</p>
</blockquote>
<p>An access to a field is O(1), whereas an access to a context value is O(n). It's also cleaner: Instead of having a wrapper which injects your value, the value is just always there and available.</p></pre>nesigma: <pre><blockquote>
<p>One disadvantage of the context as implemented is, that it is relatively slow, as it's basically a linked list.</p>
</blockquote>
<p>Now that makes sense. Though I still have to wonder. If the object that contains db is put in the context first then it should be very fast to take it out as it's the first item of the linked list, no? </p>
<blockquote>
<p>It's also cleaner: Instead of having a wrapper which injects your value, the value is just always there and available.</p>
</blockquote>
<p>Yeah, now that I know the explanation I start to feel that it's clearer as well. Though to be honest there's something "unclear" to me about making a struct and defining method handlers on it. What I mean is that it feels as if I am coupling the "web layer" with the "db layer" if you catch my drift. What are your thoughts on that?</p></pre>daveddev: <pre><p>The documentation (<a href="https://godoc.org/golang.org/x/net/context" rel="nofollow">https://godoc.org/golang.org/x/net/context</a>) states: "Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions."</p>
<p>Regarding coupling; If the handlers require access to info in your database, then there is a coupling by necessity. To minimize the ramifications of the dependency, use interfaces. Instead of having a field in your handler type which carries an instance of a struct, define an interface which provides the methods that returns what is needed. In that way, the database layer can be swapped out or mocked for testing.</p></pre>nesigma: <pre><blockquote>
<p>Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.</p>
</blockquote>
<p>I've read that yes but it's just an assertion with no explanation which does not help me make decisions for my specific cases. But most importantly why would an object that holds db should be defined as optional parameter and not something that transists processes and APIs?</p>
<blockquote>
<p>To minimize the ramifications of the dependency, use interfaces.</p>
</blockquote>
<p>I am already doing that.</p>
<blockquote>
<p>If the handlers require access to info in your database, then there is a coupling by necessity.</p>
</blockquote>
<p>The handlers need db to bring meaningful results but db does not need the handlers at all. So why not just pass db in the handlers through the context?</p></pre>daveddev: <pre><p><a href="/u/TheMerovius" rel="nofollow">/u/TheMerovius</a>'s response answers this. I was providing the quote from the docs to reinforce the justifications.</p>
<p>Beyond that, hiding everything in the context type is disingenuous. Looking up handler-scoped needs from a request-scoped type feels disorganized. A handler which allows it's methods to access types via fields provides for a more clear design.</p>
<p>Maybe looking at it through an analog can help. If one rides their bike to work, they would have their journey-scoped items in a back-pack (laptop, extra coat, etc.). The bike-scoped items would go in a bike bag (bike tool, extra tube, etc.). It would be possible to take the bike bag off and put it in the back-pack, but putting a laptop in a bike bag just doesn't make much sense (nor would it fit in that case). I hope this silly/extended analogy helps.</p></pre>nesigma: <pre><blockquote>
<p>Looking up handler-scoped needs from a request-scoped type feels disorganized. A handler which allows it's methods to access types via fields provides for a more clear design.</p>
</blockquote>
<p>Maybe it's something I am doing wrong in practice that does not allow me to see why it's wrong to throw db in the context so let's get a little more practical. Please bear with me.</p>
<p>We define a package that will handle anything db related, let's call it <code>store</code>. The package <code>store</code> provides an interface with all the db functions we will need so that for example we can write <code>store.GetUsers</code>. And of course <code>store.New</code> will give the implementation.</p>
<p>The package <code>store</code> seems "orthogonal" to me. You can use it on a web application by somehow passing it in the handlers (through the context in my case) or a console application or even a 3rd party application. It also seems easily extensible as you can just add more methods depending on the needs.</p>
<p>I have trouble understanding how defining handlers on that <code>store</code> package would make it more clear. Suddenly the "db layer" is telling you how to present the data to the user. And what if I want to use <code>store</code> on a console application (as well)? Why define the handlers on it?</p>
<p>Maybe this is a bad example but it is what I usually use when writing web applications in Go. If the architecture is bad I wouldn't mind improving it and make it more clear.</p></pre>daveddev: <pre><p>I see where you're confused now. Create a type which contains the store.</p>
<pre><code>type node struct {
DB store
}
func (n *node) testHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
user := n.DB.GetUser()
v, ok := getReqData(ctx)
if !ok {
// handle error
}
user.SetSomething(v)
// etc.
}
</code></pre>
<p>I'm sorry if I made any errors.</p></pre>nesigma: <pre><blockquote>
<p>Create a type which contains the store.</p>
</blockquote>
<p>Hmm okay that makes sense. Would suggest that node (and it's handlers) should live in the main file or in some other package? I am gonna try it next time, thanks.</p></pre>kaeshiwaza: <pre><p>It'll be very interesting to see how the chaining and middlewares libs will look like with coming 1.7 ! It's not that it's so much difficult now but there is too many ways to do it when we begin...</p>
<p>About db, i like to begin and end a transaction in one middleware (that i do in Python). I'm surprise to don't see such examples with Go ?</p></pre>daveddev: <pre><p>Along with <a href="http://github.com/codemodus/chain" rel="nofollow">http://github.com/codemodus/chain</a>, I also setup <a href="http://github.com/codemodus/catena" rel="nofollow">http://github.com/codemodus/catena</a>. catena uses the http library's http.Handler definition, but uses the same API as chain. chain will likely be modified to be identical to catena and will have an upgrade script available to minimize impact when 1.7 is released.</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传