| I really like Mat Ryer's work, and I've applied most of the ideas in the 2018 version of this article to all of my Go projects since then. The one weak spot for me is this aspect: >NewServer is a big constructor that takes in all dependencies as arguments... In test cases that don’t need all of the dependencies, I pass in nil as a signal that it won’t be used. This has always felt wrong to me, but I've never been able to figure out a better solution. It means that a huge chunk of your code has a huge amount of unnecessary shared state. I often end up writing HTTP handlers that only need access to a tiny amount of the shared state. Like the HTTP handler needs to check if the requesting user has access to a resource, and then it needs to call one function on the datastore. I'd love to write tests where I only mock out those two methods, but I can't write simple tests because the handler is part of this giant glob where it has access to all of the datastore and every object the parent server has access to because it's all one giant object. Nothing against Mat Ryer, as his pattern is the best I've found, but I still feel like there's some better solution out there. |
Where possible plugins are a great strategy to lay down these code seam points that don't force all possibilities upon some body of code, because fundamentally with plugin architectures you pick and choose what you want. Plugins are opt out by default, you must explicitly opt into a plugin for it to manifest. I've been calling software that has this quality going as being an "a la carte" style.
But in general you do what you need to do to avoid "doing everything so you can do anything".