8 ms·
I hope to spur conversation on this. I've seen many in the Go community argue against abstractions. Many say, "Pass the database connection in the params." Or "
by virmundi 6y ago
I hope to spur conversation on this. I've seen many in the Go community argue against abstractions. Many say, "Pass the database connection in the params." Or "make the DB pool a global variable". How can you write tests for logic without having to instantiate a DB? Many gophers appear to say I should have essentially a giant transaction script for each handler (https://martinfowler.com/eaaCatalog/transactionScript.html https://martinfowler.com/eaaCatalog/transactionScript.html). The handler opens the DB, makes it at least function scoped, and all my business logic goes in the handler, or some similar method where the DB connection is passed. Now my tests are functionally integration testings. This makes them both slow, and hard to test for proper error handling.
When I write code in most OOP like languages, I follow the Clean Architecture model. The use case is an actual struct with the interfaces defining the repositories/services as members. I am now free to test the use case in isolation. I can test failure cases to since I return an error, which can be easily mocked. I write a factory to create my use cases. The handlers get the factory passed in as an argument to the constructor for server. I can now test handlers in isolation by passing a factor with mocks.
- sagichmal 6y ago> Many say, "Pass the database connection in the params." ... How can you write tests for logic without having to instantiate a DB? You define and use an interface to model the DB, and use that as the mock point. Basic stuff.
- throwaway894345 6y agoTo add to your answer for those who aren't familiar with the basics of mocking, here's a StackOverflow answer that I've shared with others--maybe it will help elucidate things: https://stackoverflow.com/questions/19167970/mock-functions-in-go https://stackoverflow.com/questions/19167970/mock-functions-...
- virmundi 6y agoI aware of making an interface for that. I advocate that. What I see people on r/golang saying, and even in HN, is to just pass the DB connection either explicitly or in the context. I hate this. It makes things bound to DBs.
- sagichmal 6y agoSome people say this, but when they do, other more senior people quickly interject and say it's a bad idea.
- throwaway894345 6y agoI agree. I’ve seen more prominent figures argue against global state often. Using global state is certainly not idiomatic Go.
- morelisp 6y ago> Using global state is certainly not idiomatic Go. Unfortunately it's not this simple (and probably multi-idiomatic). Go definitely adopts more global state than other languages; I don't know any other language that offers a default-global HTTP client and server. Now, part of that is because Go's stdlib goes out of its way to make these appear stateless even though they are not - and this is good, even if you (often rightly) don't use the default ones. But I think a lot of people saw those carefully engineered APIs and instead ran with "globals are OK in Go!" Lots of packages have global-level configuration properties - some of this is a hacky replacement for DI e.g. most logger injection. Well, OK, I can support/tolerate some of that because DI in these cases is usually a hacky replacement for real AOP language support. But some of it just shouldn't be global. e.g. Gin debug vs. release vs. test mode should be a setting on the Engine. And then you get into really bad stuff - I don't know why but it is common to to have a global sql.DB, or sarama.AsyncProducer, or whatnot. A lot of novice Go developers - anecodotally predominately skewed towards previous PHP users, I think because they are not used to have really global variables - use a global for anything concurrency-safe. And this has ended up in a lot of low-quality tutorials/examples/SO questions so I don't see it going away any time soon.
- permille42 6y agoIn recent history I have worked a lot with Golang and also encountered the tendency towards globals being the recommended path. Specifically, I encountered it with logging, frameworks( Gin ), and DBs. It is interesting to me that you called out these three things specifically having encountered them all myself. I agree also that it is a result of having poor DI support from the language. I also like your point about PHP users using Golang. The approach Golang uses towards serving web content is very similar to me to how initial PHP frameworks did it. Go tends to make it easy to setup concurrent things occurring, and so I've found myself spending a fair amount of time speculating on what is thread safe and what isn't. No matter how much I see it I still do a double take when I see globals in Go modules. Seems like a bad practice to me.