5 ms·
I've probably misunderstood something, but re: DB::get() Is the proposal that we use global singletons for the things that would normally be found in a service
by lbayes 5y ago
I've probably misunderstood something, but re: DB::get()
Is the proposal that we use global singletons for the things that would normally be found in a service locator?
How does one overcome the issues with the Singleton pattern (e.g., how to unit test various features that manipulate global state)?
- downsplat 5y agoDB::get() is a static method that figures out from its environment what kind of DB connection to give you. DB connections themselves belong to another class (say, DbConn), which is not a singleton, and does not keep global state. But user code never instantiates that directly, and only ever calls DB::get(), which does know about global state, and figures out whether an existing connection should be reused. If you need to set up things in a unit test so that DB::get() will return some kind of mock object, then you call some kind of DB::setupMock() method first. Basically, I'm taking the kind of code that would go into the DB section of a DI configuration, and putting it in a separate class of its own, which is fully in charge of that particular bit of functionality: providing DB connections to the application. And since it's functionally a global, I'm embracing that and making its method(s) and state static. Application startup calls DB::init() once, and then it's ready to go.
- hvidgaard 5y agoThat is all well and good until the day you need to it to return two different results depending on which context its running in. Relying on fixed global state is asking for trouble down the line if it's something that will be evolved over time. DI makes such changes possible because you're injecting it with the constructors, while still allowing you to have a singleton DB managing class today. Another major benefit for long term maintainability is the ability to see _all_ dependencies in the single constructor. It certainly makes writing tests simpler from unit tests to in memory integration tests.
- lbayes 5y agoGot it. One problem I've run into, is that these things tend to proliferate into a dozen or so global things that people need to remember to deal with (clean up and/or configure) in the test environment. Since my brain doesn't do memory well, I start getting intermittent test failures from interacting tests that can be painful to debug. Another problem is that they tend to make the test environment really slow because the fix is often to add a pre or post handler to every single test globally. Also, the feature might be designed for environment A, but later need to run in environment B. Global state patterns tend to blow up when that happens. The DB::SetupMock() thing tends to work okay for direct consumers, it's bad when the transitive dependencies need to start doing that for 6-10 different services. FWIW, I commented elsewhere with an alternative structure that's been working well for me with a different set of trade-offs. Also FWIW, I've been fighting against global state for so many years, it makes me really sad to see it getting promoted again. Best of intentions here, not trying to be critical, just trying to share some hardwon experience.
- downsplat 5y agoWhat you say makes lots of sense, I think the best design depends on many other factors, like the size of the codebase, the size of the team, and how modularity is handled within the whole system. What made sense for my project might not make sense for another. And yes, I agree that if something needs to be a module that can be used in unrelated projects, it should not depend on global state... unless those projects are explicitly defined to share a common programming environment, in which case they are not pure modules anymore. My point, maybe going a bit meta, is that no matter how you organize it in terms of which language features you are using, application code effectively does depend on its running environment, and the information from those dependencies needs to get there somehow.