A theory of modern Golang
peter.bourgon.org
peter.bourgon.org
I'm writing Python and some C++, and I have been doing this for maybe 5-6 years. It takes a little practice to structure your programs like this, but once you know how, it has a lot of benefits.
It's not really idiomatic in those communities either. Python and C++ Libraries use globals all the time, although I think it's becoming less common.
I guess Java programmers have been doing dependency injection for even longer. It probably became popular more than 10 years ago. But I'm not really sure how "pure" the Java ecosystem is. Singletons are banned under this style. Instead, a singleton is just an object you instantiate once in main(), and pass throughout the rest of your program.
I think the problem with Java is lack of free functions. Static methods confuse things too.
The pure-dependency injection style is basically functional programming, requiring state as an explicit parameter.
I use this style Python all the time with zero frameworks. You just make the objects in main() and make the objects in your test main(). Use helper functions if necessary.
I do think Spring was very influential because I remember other people in the mid-2000's talking about testing, dependency injection, and Spring.
I recall lots of people using Guice at Google but I never used it since I did very little Java. I feel like there is something about Java where you need a DI framework, and in Python you don't.
I don't feel like you need one in C++ either. Or it might not even be possible to write one. I think LLVM is written in C++ in a style with zero globals. It's a library-based design.
I was going to say that I don't use closures in Python all that much, and Python has some issues with closures (e.g. nonlocal is ugly, and it wasn't even there until Python 3).
But after watching a recent PyCon talk [1], I realized that the "bound method" feature of Python is a useful special case of a closure. And he makes the good point that the code is shorter than JavaScript which requires an explicit anonymous function or bind().
I guess, many people did it long before.
I also think language-supported thread locals are superfluous in this style. Instead, just create a single object that runs into a single thread. Then you don't pass the object to any other threads. The members are automatically thread locals.
If you have N threads, then you have N objects confined to those threads. There doesn't need to be a separate concept of thread-local IMO.
Eventually its gets unwieldy when you have lots of classes. I've worked on old code in that style (DI w/o a library), it produces a lot of boilerplate. Piles and piles of "container" classes filled with deps getting passed around gets real old, real fast.
I strongly encourage people to use a framework like guice.
Yep, if application started to grow, it'll require significant refactoring at some stage. But many application won't grow and using framework for something like 5 services looks like overkill for me.
Honestly, this is my favorite argument for static typing. Stupidity should be difficult.
I say this as a Python developer. We waste a lot off time debating this in code review and debugging this junk code when it inevitably leads to bugs or inexplicably broken test cases. Static typing would have precluded these hacks (and others).
https://www.youtube.com/watch?v=DJtef410XaM
http://rhodesmill.org/brandon/slides/2014-07-pyohio/clean-ar...
I don't come from a Java background, but I think that many of these ideas originated in the Java community. They came to Python later (if at all), and it's a little funny to see Go struggling with the same thing.
I think I came at it from more of a functional programming perspective, and the style gradually converged with what people were thinking about OOP. I'm not actually of all the details about "clean architecture" and "onion architecture", but they are definitely related to dependency injection.
Basically all your effects and state can be instantiated on the "outside" in main() and passed INWARD. But I don't believe it necessarily has to be pure. I encapsulate all my state, but I don't encapsulate all my side effects, like printing to the screen.
Some people appeared to like this comment, which is somewhat related:
https://news.ycombinator.com/item?id=11841893
I'm not sure how much this will help, but here is 12K+ lines of code I wrote with zero globals:
https://github.com/oilshell/oil
The main directories to look at are core/ and osh/. The main program imports a lot of stuff and wires it together:
https://github.com/oilshell/oil/blob/master/bin/oil.py
(There is a bunch of surface messiness, but the architecture is sound.) I've also written even larger programs with zero globals.
Also, I believe that web frameworks are some of the worst offenders in terms of preventing you from using this style. I don't think I've seen a single web framework that made the handlers easily testable.
I think you may be fighting against the grain in some parts of the Python world using this style... I can do it in certain projects because I'm writing most of the code from scratch, and forking some dependencies.
I came from Python but have worked a lot in Java (and primarily in Java for the last few years). It took me a while to accept dependency injection but now I see it as fundamental to good Java design. But I don't see the same for Python. My reasoning is that it comes down to the difference in static and dynamic typing and how imports work. Maybe even mostly on how imports work, since DI did take off in Angular for JS, but if JS had a module system it might not have needed to like we see with Python. Ideas that quickly spread and cross quite different language boundaries are the ones I pay more attention to (like the industry shift to more functional designs and patterns, "composition over inheritance" when OOP is still in heavy use, and so on), DI hasn't been one of those.
The canonical example of dependency injection in Java is having a method that constructs a new Date object (or something that is based on the current system time) and does something with it, and asking the question of how you would test it. Well the JVM is pretty dynamic under the hood so if you have to (useful for legacy systems or third party code you can't really modify) you can cheat with something like PowerMock and overwrite the bytecode for the built in Date class with your mock one. But normally, such usage is frowned upon, just change the code under test. So DI comes into play where you bring either the Date class or some common time-related interface up into the method signature argument, and you provide the value in your test. You may or may not have another method for prod code to call that supplies a default value, depending on whether you want the burden of giving a good default (if such a thing makes sense for the use case) to be put on the client or not.
When you have dependencies that your whole class uses, then it's common to put those on the constructor so clients will pass you in the object, instead of you making one yourself. Frameworks like Spring make that slightly easier to work with (especially when you get into cross-module service classes you want to depend on) by letting you just specify a setter() and it will magically make sure it autowires an object for your class methods to use when needed. You can easily resolve circular dependency problems this way too, A <-> B is resolved by having two more modules for IA and IB that each of A and B depend on instead with something behind the scenes to make sure it all wires up correctly. Interfaces are great since now you can depend on much less. If you want a mock, just implement a test class implementing the interface. You can use the less powerful Mockito library to help only mock out the parts of the interface you actually need to test (but this implies access to the source so you can know such things about the implementation). With interfaces though you can in principal even test while treating the function as a blackbox, and if you can prove that you've passed all possible states for the dependency interface you can prove interesting things. Or just fuzz it. Or be allowed to have your IDE helpfully autocomplete types and suggestions.
Python doesn't have interfaces, being duck-typed, nor does it force static typing. This alone makes DI less useful. I can write:
def unix_time(time_dependency):
return time_dependency.time()
and have it live in its own file, nothing else in it. Java would throw a fit, it needs to know what type time_dependency is. Python doesn't care, as long as it can call the .time() function on it. I think this is great, but some programmers are terrified. But anyway it's following DI and assuming time_dependency.time() is pure (it may or may not be) would make unix_time also pure itself. But it's not very helpful if you're looking at the signature or even the full source itself. Something more helpful would be: import time
def unix_time(time_dependency=time):
return time_dependency.time()
Then you at least have an idea what to pass it yourself besides just something that can have .time() called on it, or you can pass it nothing. Something you would see in Java, but with the same method names instead of the different ones that Python requires: import time
def unix_time_internal(time_dependency):
return time_dependency.time()
def unix_time():
return unix_time_internal(time)
But the most common implementation in Python is probably this: import time
def unix_time():
return time.time()
The Pythonista in me also thinks this is in fact the best implementation. It's just as testable as any of the others. As I mentioned in my first comment, in your test code, instead of passing in a time dependency directly, or using a mock/patch library that starts to smell like PowerMock, you can literally just call (assuming the function was in a file called timeutil.py) timeutil.time = myTimeDep and then call timeutil.unix_time() with your substitution used. This is because unlike in Java where imports are definitions for a compiler to lookup and insert things about, in Python they are global (to the file) variables, and they can be redefined, and with Python's dynamicism it looks up the name of the thing each time to get its value instead of doing it once and coding a static reference to it, so your redefinition will take precedence. Of course this pattern isn't even that unusual in non-unit Java tests, frequently the test will save the current value of something and overwrite it in the setup, run the test, and then restore the old value in the teardown. At least in Python you can have a clean decorator for that.In your code I opened util.py at random. Looking at GetHomeDir, I can't test this in the Java sense, it has dependencies in its implementation on os and pwd. But that's ok, because unlike in Java, those dependencies are actually global (to the file) variables that I can mutate. Plus you probably tested it manually in the REPL, another development benefit Python has over languages like Java, you can build and test things bottom up sometimes. Thanks for the code sample though, I'll keep it around for future reference. (Would you say it's common these days in the community to have test files living next to the implementation files? The only place I see that these days are with JS components.)
Probably the initial resistance I had with DI is that taken to the ultimate extreme, DI would take the form of a style of functional programming whose name escapes me at the moment, but essentially one where everything is passed as a parameter, including language built-ins, and only at the top level where you define a main() can you specify all of these things that the language will inject when it runs your program.
When I work with Python, I don't see a problem with not having the same DI toolkits or seeing projects not following it very well. No more so than not having privates or a culture in love with a sea of getter and setter methods. But I can see why some might disagree, like your sibling comment, and cry out for static types as the answer. Well, there's Python 3, not sure how much it gives you for this, and there's a ton of other static languages out there, I'd suggest working with those and leaving the Python community to do its thing. Functional programming in general, though, that's the popular thing in pretty much every language in modest use at this point, it's good stuff. A lot of bugs in old Python code I wrote could have been avoided if I had written in a more functional style, probably not as many if I had followed DI more rigorously.
GetHomeDir() is a great example that proves that point... if I were trying to be very pure, I would have reified the pwd dependency and environment dependency.
One reason to avoid the complication is that I'm using shell scripts for tests. So in shell you already have a test environment. You can set $HOME to whatever, and in theory /etc/passwd, although that's a little trickier.
Actually that was one of my motivations for writing a shell :) You can test "one level out" at the OS level. Instead of the "seam" being the language, the seam is the OS.
I prefer to test against STABLE INTERFACES, not against things that I made up internally. You don't want your tests to calcify the structure of your code. I've seen that happen a lot with fine-grained testing, and it's a big pitfall.
I would say at the beginning, I write unit tests for tricky parts, but don't aim for 100% unit test coverage. I aim more for high integration test coverage. And then at the end of the project, when you are fixing bugs, that is when you can do fine-grained testing without worrying about making a mess of your code.
Here I did parameterize random() (irr_rand), because it's very important to the function of the code and needs to be tested:
https://github.com/google/rappor/blob/master/client/python/r...
So it all depends on the context. That's why I say it takes some practice. It takes practice to:
- not end up with more than 3-4 parameters for each class.
- not end up with too many classes. Java code seems to fall into this. A lot of things are just functions with dependencies. Actually this style I think reduces the need for classes -- they are your parameters rather than being your context!
- not structuring your code as a deep tree of calls. Instead it should be a relatively flat object graph.
A relatively static "Object graph" is really the idea that distinguishes OOP from "structured programming" (i.e. a pyramid).
I think I would differ with you in that I don't think DI and functional programming are that different. I think they are trying to get at the same core idea.
Things I ALMOST NEVER use in Python:
- setters and getters.
- Especially, setters for dependency injection. I always pass params through constructors.
- decorators. This can always be accomplished with composition of objects. I find that style a lot more readable. decorators are non-trivial code at the global level, when it really should be in main().
- classmethod and staticmethod. Static methods should just be functions. Class method is kind of a hack for singleton-like behavior.
- As mentioned, the singleton pattern is banned. Singletons are just classes you instantiate once.
I like that this style gets rid of a lot of concepts: thread local (as mentioned above), explicit singleton pattern, and "static/class methods".
Also I think it's true that Java's static type system might get in the way a little bit, but I don't have a strong conclusion on that. However, I also wish Python were a little more strict. I don't use 90% of the dynamism of classes.
But I guess I have stopped doing that, in favor of testing against my own interfaces, using integration tests with shell scripts, or the odd param that is only for testing. I think I just like to have some indication in the code of where I tested something, and it doesn't cost very much and doesn't come up too often.
public void doStuff() {
// ...
}
Everyone loves those!If on the other hand your "app" object is quite lean, and follows the behavior of maybe Context in Android, then ignore my above comment, and I would agree with you.
It is non-interactive side-effect and irrelevant to the function of your code. It's just for diagnostics, like a debugger.
If that is not true, I wouldn't call it "logging" for the proposes here.
You could inject a logger, but I don't see a reason to.
https://softwareengineering.stackexchange.com/questions/2759...
I don't see any hard in gloably DI'ed system observability.
I agree it would be completely silly to change the signatures for a bunch of methods just to print something.
But sometimes you absolutely DO want to inject dependencies for things. The Lua VM is parameterized by the allocator it uses, which is useful in practice.
And I would distinguish between logging and program output. Logging can be an exception, because it doesn't need to tested. You probably don't want your unit tests to fail if your debug logs change.
But maybe in some situations you have some really critical logging, and you want to test that part. Then you can parameterize that code by the logger.
Although honestly I would usually just write a shell scripts and invoke "diff".
So yeah I don't agree with absolutely pure DI or pure functional programming. But I definitely think the ideas there will help you structure better programs.
And it's madness.
With this style, all the functions expose their implementation details, every single thing they use to accomplish their task. It totally destroys encapsulation.
Then you have the problem that if one of these functions decides to add one more thing, say log a message, then all its callers need to pass it a logger. And all the callers of its callers also need to pass a logger.
And of course, they need to find a way to materialize such a logger in the first place.
Insane advice.
Dependency injection is popular for a reason.
Not at all. Under the author's guidelines, a function can encapsulate whatever it wants, so long as it doesn't modify state or have side effects. If it wants to do either of those, it should declare its dependency explicitly. I don't see anything so insane about that.
Not at all. Only the initializer needs to know about the needs. Then in the object world you can use a combination of dependency injection and factories to abstract over all the details you don't want callers to know about, and you can do the same more elegantly in the functional world with partial application (AKA currying).
And then you made Go a complicated and awful to read language
You would use the "New" constructor function pattern in this case, using a higher order function called at injection time to keep it reusable and hide everything. I also prefer keeping the struct it returns as unexported so you get full encapsulation, but golint doesn't like it. I guess this is a personal issue.
Spot on. I love the decorator pattern for dependency injection.
type Server struct {
logger *logrus.Logger // optional
store databaste.Store // required
}
type ServerOption func(Server) Server
func WithLogger(logger *logrus.Logger) ServerOption {
return func(s Server) Server {
s.logger = logger
return s
}
}
func NewServer(store database.Store, options ...ServerOption) *Server {
s := Server{store: store}
for _, option := range options {
s = option(s)
}
return &s
}
func main() {
myServer := NewServer(myStore, WithLogger(myLogger))
}Factories are just functions or objects that can create new instances of a thing of the number or configuration of that thing can't be known at compile time.
I don't think it has to an all-or-nothing thing. I generally encapsulate all my state, but not all side effects, like printing to the screen.
And yes I believe logging should just be a free function. I think configuration of loggers is overrated. But if you need to use a global for that configuration, so be it.
Functions don't expose any implementation details -- they expose the state they modify and the side effects they produce.
I don't quite understand your last sentence. Dependency injection IS popular and that is what I am advocating and what this post on Go is advocating. It's perhaps not as popular in Go and Python as it is in Java, and perhaps even some corners of the C++ world.
Dependency injection - is just making all the dependencies of a class/function explicit on construction, with a secondary affect that an implied ordering is needed to creating complicated nested objects.
Avoiding hidden state-channels (globals) that can't be easily modified is valuable for being able decipher/reason about behavior, and for mocking test instances.
Since Java doesn't allow for first-class functions, it necessitated design patterns in spite of a more straight forward constructs in more imperative or function languages.
But this is likely just me and my biases. (Note: I don't hate Java, but it does feel constraining at times.)
Well, yeah! Those are great principles in any language - I find it concerning (and maybe indicative of something) that the Go community has voted this up. These are fundamental requirements to building testable, scalable systems in teams. Having global variables has been bad practice for what, 20 years? One of the first lines in Javascript, the Good Parts (closest book handy) is:
JavaScript is built on some very good ideas and a few very bad ones. ... The bad ideas include a programming >model based on global variables.It's a winning combo because you can recycle old and simple ideas for a whole new context.
[edit] felt bad because rereading this it implies the author has bad intentions. Don't mean that. Just mean it's cute to watch the golang community learn Java.
I do enjoy that Go gives a bit more obvious control over memory layout than Java did, and I enjoy using it as a small projects language. I've never used it in full anger/production, so I haven't seen the worst of it.
One funny thing about Guice(mentioned elsewhere): I've heard more than one Googler say that dislike the framework quite a bit, due to how heavily it can be abused.
One bit of advice that seems to have gone either missing in Java/JVM, or common knowledge, is the idea that only main/binary packages should have config options via flags/env vars. Many, many JVM libraries do not follow that convention, and I've had to hunt down a log4j XML file for Spark in order to turn down the logging levels before. Would much rather have that be controlled via a flag.
At Google pretty much all server configuration is done with flags. Binaries can and do have thousands of flags. And I view that as a much better solution than a config file that is searched for.
The idea is that modular units like processes and functions don't "search for configuration", they just accept parameters, and led the higher level application configure them. Then the code becomes very straightforward to read and debug.
Plus the lack of runtime control means you are essentially forced to do explicit DI, or you have an untestable mess. Hopefully the DI libs will catch up to e.g. Java's sophistication within a year or two.
And they have virtually nothing to do with DI?
If you are going to make an argument for more natural support for DI in go than other "strongly" typed languages it starts and ends with structural typing.
I don't even understand the basic premise of that argument. It seems nonsensical.
This isn't a killer feature, it jjust means Go requires less boilerplate when doing explicit DI, so the ROI offered by a DI framework is low. If you don't agree that initialization in Go requires less boilerplate, maybe you should be asking yourself what you gain from your DI framework?
They were originally intended to move the behavior changes provided by DI from compile time to configuration time.
This was especially valuable when you are delivering enterprise software to sites you don't control and you need to support a wide array of integrations.
They also happen to reduce boilerplate & DI can help with test-ability so they were adopted for that as well, but if you are only doing DI for tests and only using a framework to reduce lines of code it's a fairly widely accepted anti pattern.
I believe your basic assumptions about DI are wrong. The reason it hasn't taken off in the golang world is that the kind of software it was used for is less common & if you are writing it you'd not use golang for a host of reasons, none related to initializer syntax.
DI is also great for creating modular code you compose, instead of monoliths. It encourages the use of interfaces and information hiding - you should look into it, it's a very powerful technique.
Instead of asking how it would improve an arbitrary library, ask yourself "how screwed am I if 100% of the API surface for this library changes tomorrow?" Dependency injection would help you out of that faster that rewriting everything.
I interpret these upvotes as more of a "Yes, we get this right" than a "Wow, what a great idea!"
In particular, Go programs are much more likely in my experience to be SOLID/DI than JS, Python, Java, C#, etc, mostly because the language was designed to support it.
I think making the dependency explicit is good when it is likely that it is going to be desired to change the implementation on a per object basis. If it's unlikely or meaningless to have multiple different implementations within the same process, there's nothing wrong with making the configuration global.
Imagine how painful it would be to use the http module if it required the injection of its clock, compression suite, network interfaces, address resolvers, loggers, etc. And that's just one module of many.
One difference is that it's reasonable to add logging to nearly any function anywhere, which means that you have to either make the logger global, preemptively provide a logger parameter to almost everything, or else change a whole bunch of method signatures at once every time you need to add logging to somewhere that didn't previously have any log statements. Global variables are unfortunate, but the other options are much worse.
The second difference is that you pretty much only ever have one logger object in a process, but processes often use multiple database connections and it may matter which one is being used. For example, a caller may want to make your function's database accesses part of its transaction. Or use a unit test database instead of a real database. Or a number of other things. Omitting the parameter hides the fact that a function uses the database, which is an important thing to know about it.
Also it's a lot easier to test logging, or anything that you would us a global object for, if it's DI based. That said, you can always pass a config object that holds what would normally be global variables but allows you to override.
This boils down to functions which follow the template.Must pattern, i.e.: either the function can be evaluated or the program should panic.
The other case is read-only lookup tables.
What are other ways to execute logic when a package is imported? init() is the only one I'm aware of.
var ( X = blah() )
func blah() int { return 123 }
The language was first released in 2007 :). Man...
I'm always wary about advice that says "you must never use {this tool provided by a language}". Never say never ;)
Globals can be and are useful in some circumstances. Having a blanket ban on globals because "globals are bad mmkay" is not useful.
I draw this line at "do I need to mock this for testing?". If I need to mock it to run tests, then it shouldn't be a global and should be DI'd (so I can DI the mock). If I'm not going to mock it, then it's probably fine to leave it as a global.
The Db and Logger are perfect examples: I frequently mock the Db connection, so I can test sql output. I almost never mock the logger, because I only use it to output meta information. So having a global logger is OK.
YMMV...
When these new levels of granularity are desired in your project, switching from globals is no fun.
Although "Go" is the name of the language, "Golang" can also be a submission title hack so as not to confuse those who "Expected to see an article about the development of modern Go the board game theory".
Why not use "Golang"?