How do you pass dependencies / variables to your handlers?

xuanbao · · 472 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Just wondering what the preferred method of passing dependencies to your handlers is, and why.</p> <p><strong>Return http.HandlerFunc</strong></p> <pre><code>func PostHandler(postStore *PostStore) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { postStore.GetPosts() } } </code></pre> <p><strong>Using a struct</strong></p> <pre><code>type PostHandler struct { postStore *PostStore } func (h PostHandler) Index(w http.ResponseWriter, r *http.Request) { h.postStore.GetPosts() } </code></pre> <p><strong>Globals</strong></p> <pre><code>func PostHandler(w http.ResponseWriter, r *http.Request) { postStore.GetPosts() } </code></pre> <p><strong>Something else</strong></p> <p>If so, how :)?</p> <hr/>**评论:**<br/><br/>danhardman: <pre><p>I use the struct method, where all dependencies are interfaces so they can easily be mocked.</p></pre>TheMerovius: <pre><p>I&#39;m usually doing the second. The first I&#39;d find acceptable too. I&#39;d discourage the third, as it makes testing and everything much harder.</p></pre>fortytw2: <pre><p>I return a custom <code>type ErrorHandler func(w http.ResponseWriter, r *http.Request) error</code> with a ServeHTTP method on the type - much in the style of returning a http.HandlerFunc, but if there are many common dependencies / groupings, structs can be just as good :)</p> <p>It also helps to define a custom <code>type HTTPError</code> or similar, so you can pass a response code alongside an error message, and then pull it apart in the <code>ServeHTTP</code> function for the <code>ErrorHandler</code></p></pre>supertopher: <pre><p>May I make the suggestion to checkout Goa [0] and look at it&#39;s generated code? I like the way it handles this and it does it similar to the first 2 methods you describe, but takes it to a different level in a good way if you ask me. One I never thought of.</p> <p>[0] <a href="https://github.com/goadesign/goa" rel="nofollow">https://github.com/goadesign/goa</a></p></pre>luckyleprechaun98: <pre><p>I do something like the first one using closures, except I pass a service interface with methods that do whatever DB or data fetching that needs to be done to return the response. This allows you to easily mock the service when you are testing your handlers. </p></pre>karnd01: <pre><p>I was always struggling on the best way, so I built <a href="https://github.com/go-playground/lars" rel="nofollow">lars</a> so you can use your own struct as the context, hope it can help.</p></pre>theothertomelliott: <pre><p>I&#39;d tend to use the second method, mainly because I find that easier to follow later on. Very much a personal preference.</p></pre>Yorirou: <pre><p>I use the first one, but there are cases when I store certain dependencies in context when the dependency either stores per-request data, or when the middlewares should be able to override the dependency for certain handlers / on certain conditions. It is easier to add something (e.g. a db connection) globally, and then being able to override it on a per-handler basis.</p></pre>k_u_r_o_k_u_s_e: <pre><p>I&#39;m going to say something people don&#39;t like but I don&#39;t care.</p> <p>You should only have 1 http.Handler or HandlerFunc in the whole web app. The default server with &#34;routing&#34; is useless, you could replace it by a switch statement yourself you&#39;d have the exact same behavior</p> <pre><code>switch urlString { case &#34;/foo&#34;: // do something case &#34;/&#34;: // do something else ... default : // 404 } </code></pre> <p>use something like that :</p> <pre><code>func MyHandler(dp DependencyProvider){ name := dp.GetRequest().Url.Query.Get(&#34;name&#34;) dp.GetResponseWriter().Write([]byte(&#34;hello &#34;+name+&#34; !&#34;)) } </code></pre> <p>your DependencyProvider is an interface and container of factories, which will wire your dependencies explicitly</p> <pre><code>type DefaultDependencyProvider struct{ db *sql.DB session *Session } // GetSession creates a session in a lazy fashion func(dp DependencyProvider)GetSession()(*Session,error){ var err error if dp.session == nil{ sessionStore,err = dp.GetSessionStore() if err!=nil{ return nil,err } dp.session,err = CreateSession() } return dp.session,err } // GetDB returns the connection func(dp DependencyProvider) GetDB()(*sql.DB,error){ // return the db or an error } </code></pre> <p>A dependency provider is created with each request in your handler BUT dependencies, aside for the ones that manage their own pools, like sql.DB, are created on demand , i.e. lazily. Of course Dependencies can depend on each other.</p> <p>Now it&#39;s easy to write a middleware queue, provide template functionalities,you can even extend ResponseWriter or Request for various purposes ( logging, putting the response in a buffer instead of sending it directly , this is useful for templates ... ) </p> <pre><code>func(dp DependencyProvider) GetRequest() *http.Request { // } func(dp DependencyProvider) GetResponseWriter() http.ResponseWriter{ // return rw } </code></pre> <p>for routing you can use <a href="http://www.gorillatoolkit.org/pkg/reverse" rel="nofollow">http://www.gorillatoolkit.org/pkg/reverse</a> , and write your own router using gorilla router syntax.</p> <p>tldr; don&#39;t stick to http.Handler , use it once since you need it to pass a handler to Go http.Server ,then the rest should be your code, don&#39;t try to stick to something that doesn&#39;t suit your problem just because everybody else is doing it. Write the code you need, not the code somebody else in the community expects you to write.</p></pre>ChristophBerger: <pre><blockquote> <p>don&#39;t try to stick to something that doesn&#39;t suit your problem just because everybody else is doing it.</p> </blockquote> <p>This is worth keeping in mind. Not only for http.Handler but for anything.</p> <blockquote> <p>The default server with &#34;routing&#34; is useless,</p> </blockquote> <p>...unless you want to integrate http.Handlers from external packages. The beauty of routing is that http.Handlers of any origin can be daisy-chained together.</p></pre>k_u_r_o_k_u_s_e: <pre><p>It doesn&#39;t change anything:</p> <pre><code>func MyHandler(dp DependencyProvider){ someHandler(db.GetResponseWriter(),dp.GetRequest()) } </code></pre> <p>Even with some &#34;middleware&#34; : </p> <pre><code>func MyHandler(dp DependencyProvider){ SomeGoMiddleware(func(rw ReponseWriter,r *Request){ session := dp.GetSession() })(db.GetResponseWriter(),dp.GetRequest()) } </code></pre> <p>The goal here is to make it easy to inject dependencies, in a lazy fashion, on each request so no race condition, with a clear API and not using map[interface{}]interface{} to shove values in the context. If session is not needed in an handler, the session value is never initialized during request processing. It is a IoC container, it&#39;s just not generalized like the usual IoC container found in other languages that is reflection based. Context is still useful of course to handle things like request time outs and work with legacy &#34;middlewares&#34; like gorilla/sessions .</p></pre>sh41: <pre><p>I don&#39;t think this applies in all situations, but it&#39;s a great idea. If careful, I think it&#39;s actually a great way to start out if you want to learn more about routers and understand best practices.</p> <p>A switch on <code>req.URL.Path</code> is the simplest thing that works [0], you can always upgrade to something more heavyweight once you want/need/feel ready. But it&#39;s much harder to start out by using a really heavyweight, complex thing and simplify that.</p> <p>Anyway, it&#39;s definitely worth experimenting with and iterating on. I&#39;m doing that here [1].</p> <p>One weakness I&#39;ve discovered is having to also take the method into account. Ideally, I want a mux that can automatically detect when a route is registered but doesn&#39;t handle given method, set &#34;Allow&#34; header and return http.StatusMethodNotAllowed.</p> <p>[0] <a href="https://github.com/shurcooL/play/blob/a2441b05dadc2698cc34fe87ffb1169cab3d085c/170/usercontent.go#L14-L15" rel="nofollow">https://github.com/shurcooL/play/blob/a2441b05dadc2698cc34fe87ffb1169cab3d085c/170/usercontent.go#L14-L15</a></p> <p>[1] <a href="https://github.com/shurcooL/home/blob/a2aefd82ef2b5e52c7431dff99525994557bb8f5/sessions.go#L261-L264" rel="nofollow">https://github.com/shurcooL/home/blob/a2aefd82ef2b5e52c7431dff99525994557bb8f5/sessions.go#L261-L264</a></p></pre>xargon7: <pre><p>Contrary to a lot of the popular opinion I see, I really liked martini and was sad when it was declared unmaintained. It had some quirks and some things I thought could be refined, so I&#39;ve finally gotten around to making <a href="https://github.com/augustoroman/sandwich" rel="nofollow">sandwich</a>. It lets me write the handlers the way I want and allows consistent, simple error handling and logging.</p></pre>

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

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