<p>Just getting a quick temp check from the community. I'm declaring a struct in <code>main.go</code> which currently holds an interface representing my Repository-type object (interfaces everywhere for loose coupling). This struct gets passed into my router declaration, as I'm using the handler wrapping technique:</p>
<pre><code>func GetManagementProperty(ctx *Ctx) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
... boring stuff like checking the parameters, etc.
//Attempt to get a single property from the store
c, err := ctx.store.RetrieveManagementProperty(propertyID);
</code></pre>
<p>To recap:</p>
<p>Main.go creates a new <code>Store</code> object which implements the <code>Storer</code> interface. We add this object to the <code>Ctx</code> struct and then pass the entire thing into the router, where I'm using the handler wrapping technique.</p>
<p>I'm doing all of this to prevent creating a concrete dependency to my Store/Repository in the handlers. The thought is, I can just wire up everything in Main.go and <em>inject</em> these dependencies wrapped as a single struct. Testing will be pretty easy. </p>
<p>So, what code smells do you see here? And how are you doing it? I feel like <code>main.go</code> would be at danger of getting bloated, but these are very small APIs that are being composed behind AWS API Gateway. </p>
<p>Edit: Any experience or thoughts with gorilla/context?</p>
<hr/>**评论:**<br/><br/>tv64738: <pre><p>Make your handlers be methods on the state they need.</p>
<pre><code>type App struct {
db *sql.DB
...
}
func (app *App) getManagementProperty(w http.ResponseWriter, r *http.Request) { ... }
...
mux.HandleFunc("/blah", app.getManagementProperty)
</code></pre></pre>very-little-gravitas: <pre><p>Since every method in a web app requires access to the db, are you suggesting one app struct with all the handlers on it?</p></pre>quiI: <pre><p>I would be thinking about one level of abstraction to prevent this and have a cleaner separation.</p>
<p>Handlers (unless very simple) should be concerned with</p>
<ul>
<li>Parse & validate request</li>
<li>Call a service layer which does your useful domain logic and maybe returns some kind of model</li>
<li>If the model is simple respond with it, otherwise delegate to some kind of view model templating thing</li>
</ul>
<p>This ensures your handlers keep their areas of concern simple and makes it easy to test.</p>
<p>It also means testing your "service" layer (i.e your important domain stuff) doesn't have to worry about HTTP anymore.</p></pre>peterbourgon: <pre><p>This is incredibly good advice.</p></pre>no_coats: <pre><p>This is excellent advice, the fact that you can pass in a function to a handler makes all of this startlingly clear; however, would you mind sharing a small code snippet on how you would abstract this out a level? </p>
<p>This sounds like a great pattern. </p>
<p>Edit: I pretty much implemented <a href="http://www.alexedwards.net/blog/organising-database-access#using-an-interface" rel="nofollow">this pattern</a> without realizing it. I'm using a struct that holds dependencies, ensuring that an interface is used for the data access object, and then closing over the entire thing for each handler. I need to now follow your advice and utilize a service layer so my handlers aren't concerned with business logic. </p></pre>very-little-gravitas: <pre><p>Thanks. Do you know of any good examples of this approach?</p></pre>quiI: <pre><p>There you go <a href="https://play.golang.org/p/yP4qDWx-3y" rel="nofollow">https://play.golang.org/p/yP4qDWx-3y</a></p></pre>tv64738: <pre><p>I said "state they need". That might not be <code>type App struct { DB *sql.DB }</code>, that's just a quick example. If you can make things more modular without drowning in abstractions, do it.</p>
<p>For example, <code>http.Client</code> contains the state it needs to make outgoing requests (<code>Transport</code>). Same thing.</p></pre>danredux: <pre><p>I do the same thing, except, a long time ago I tried using a "Dependencies" struct that holds ALL dependencies for the handlers and passing that in. This was because Context's aren't statically typed... To test I just create a new Dependencies struct and pass it in to the handlers.</p>
<p>You could pass this Dependencies struct in through the context if you want, I'm just suggesting against having loads of different context values for each dependency because they are references by a string which makes it very easy to make weird subtle bugs.</p></pre>RenThraysk: <pre><p>Currently toying with having each module of the website define an interface for it's dependencies.</p>
<pre><code>package user
type Ctx interface {
DB() *sql.DB
PasswordHash() PasswordHash
}
func Register(c Ctx, m *http.ServeMux) {
m.HandleFunc("/user/register", func(w http.ResponseWriter, r *http.Request) { register(c, w, r) } )
m.HandleFunc("/user/login", func(w http.ResponseWriter, r *http.Request) { login(c, w, r) } )
}
</code></pre>
<p>Simplest case is that can then implement all the Ctx interfaces from all modules with a single instance in main() and calling user.Register(ctx, mux) get things wired.</p>
<p>Still experimenting though.</p></pre>kromem: <pre><p>I put all my http functions as methods on a "controller" container that I construct with the dependencies during startup, and from that container pass the dependencies as arguments to other packages, which expect more restrictive interfaces.</p>
<p>I also adopted an architecture similar to the onion architecture. This way, there's actually very little of the application that requires the dependencies to test, and for the very small parts that do need the dependencies, I eventually just got rid of my mocks and now just test against the dependencies directly.</p>
<p>This inside-out approach has been a wonderful experience, and I can't recommend it highly enough.</p>
<p>Edit: The one big difference between my approach and <a href="/u/tv64738" rel="nofollow">/u/tv64738</a>'s is that I embed a dedicated injection container as an anonymous struct to my controller struct that uses methods to access the dependencies, and those methods return interfaces for the dependencies. This isolates the dependency management away from my controllers or main.go.</p></pre>no_coats: <pre><p>I would actually be very interested in a small account of code if you have anything you could throw in a gist! This sounds interesting</p></pre>gofort_: <pre><p>Look at this project. I've passing dependencies through context (env in this project): <a href="https://github.com/deployithq/deployit" rel="nofollow">https://github.com/deployithq/deployit</a></p>
<p>Here you can read great article about what you want: <a href="http://elithrar.github.io/article/http-handler-error-handling-revisited/" rel="nofollow">http://elithrar.github.io/article/http-handler-error-handling-revisited/</a> It is about errors, but you can see pattern for passing dependencies to handlers. You can also read this: <a href="http://elithrar.github.io/article/custom-handlers-avoiding-globals/" rel="nofollow">http://elithrar.github.io/article/custom-handlers-avoiding-globals/</a></p></pre>interactiv_: <pre><p>I use dependency injection through reflection like Martini in my own framework. So I can pass anything to my handler functions instead of these complicated structs that are tedious to test. If I have a function like </p>
<pre><code>func MyHandler(db *DB,session *Session,rw ResponseWriter){
// do stuff
rw.Write([]byte("foo"))
}
</code></pre>
<p>It's obviously easier to test or mock. The hell with the bloat.</p></pre>quiI: <pre><p>"Complicated structs" ? I cant say I agree with your approach. If you have to make complicated structs then you are probably not structuring your code very well, perhaps your handlers are having to do too much.</p>
<blockquote>
<p>So I can pass anything to my handler functions </p>
</blockquote>
<p>This is not a <em>good</em> thing.</p>
<p>By using reflection you are throwing away type safety. Why use Go if you dont want the compiler to help you?</p></pre>interactiv_: <pre><blockquote>
<p>If you have to make complicated structs then you are probably not structuring your code very well, perhaps your handlers are having to do too much.</p>
</blockquote>
<p>My code is well structured thanks you. I just do not like or want to work that way. </p>
<blockquote>
<p>By using reflection you are throwing away type safety. Why use Go if you dont want the compiler to help you?</p>
</blockquote>
<p>Because Go is fast enough even with reflection everywhere. Furthermore my code inside my handler IS type safe. I use reflection on both hands : the router which handles request and an Hibernate style ORM which handles database communication and rows to structs marshalling. Something goes wrong ? I get an error that's all, my code is perfectly type safe and fast enough for the kind of applications I write (classic web apps with a lot of routes and html forms).</p></pre>quiI: <pre><p>It's hard to understand how it could be typesafe if you're using reflection. </p>
<p>I can pass anything into your handler and the best you'd be able to do is do a runtime error. That is not type safety. </p>
<p>I've not once talked about speed, just type safety.</p></pre>interactiv_: <pre><blockquote>
<p>I can pass anything into your handler and the best you'd be able to do is do a runtime error. That is not type safety.</p>
</blockquote>
<p>You questioned my use of Go and talk like you never marshaled or unmarshaled data in Go. If you are doing that you are not writing typesafe code either according to yourself. Why are you using Go then ? </p></pre>quiI: <pre><p>I meant by "passing anything in" your dependencies to your handlers. You mentioned you used DI to handle dependencies with reflection. Not talking about unmarshalling data</p></pre>interactiv_: <pre><p>No difference, you're using an API that as an interface{} argument in both cases, same for database/sql . You're passing anything to row.Scan. But you're not complaining when the Go maintainers are doing it. </p></pre>quiI: <pre><p>There is a big difference between casting arbitrary data into datatypes and using the type system to your advantage when structuring your application. </p>
<p>Seriously though, why use Go if you are so unfond of types. Just use a dynamic language and it would be more convenient. </p></pre>interactiv_: <pre><blockquote>
<p>There is a big difference between casting arbitrary data into datatypes and using the type system to your advantage when structuring your application.</p>
</blockquote>
<p>There is no difference between casting arbitrary data into datatypes and using the type system to my advantage if the language allows it.</p>
<blockquote>
<p>Seriously though, why use Go if you are so unfond of types. Just use a dynamic language and it would be more convenient.</p>
</blockquote>
<p>Seriously though why use Go when Go has feature that you deem not to be used ? Just use a language that doesn't allow what you don't like so you wont have this kind of debate with people you disagree with. You'll all be chanting the same gospel. God speed.</p>
<p>Go 1.7 just got reflect.StructOf method, but Nooo! Go designers absolutely don't want people to touch these apis /s</p></pre>tv64738: <pre><p>Who says reflection is the only way to marshal/unmarshal data. It's merely a way that'll work with any data type you throw at it, with no extra work needed -- that's not always even a good thing!</p></pre>random314: <pre><p>Yes. Me too. The way I see it, if that's the only speed bottleneck in my code then I'm probably using the wrong language.</p></pre>tmornini: <pre><p>Exactly right.</p>
<p>Put EVERYTHING into that struct that downstream items need, including the things you discover along the way.</p>
<p>This makes component a/b testing super easy -- and lots of other great stuff, such as fully mocked testing...</p></pre>no_coats: <pre><p>Are you being sarcastic? </p></pre>tmornini: <pre><p>No. Why did you think so?</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传