Skip to content

Instantly share code, notes, and snippets.

@corlaez
Last active September 23, 2021 13:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save corlaez/aa2c1a657438f5c998af9340a3339051 to your computer and use it in GitHub Desktop.
Save corlaez/aa2c1a657438f5c998af9340a3339051 to your computer and use it in GitHub Desktop.
Manual, Provider Based and Spring Configuration for Modules

First, we should limit the creation of the module to a single site in our system, in this case, the newInstance method within the module configuration. This ensures we always assemble the module with the same logic.

After that, we can create a similar instantiation method but that allows us to inject mocks. Use fakes whenever possible (like InMemory Hashmap implementations of repositories). Mocks should be limited to references to other modules to test the interaction between them. Or to an eventPublisher to test that the expected events where sent.

In this gist you will find examples of how to write these configuration files.

// PseudoCode for Manual DI
public class ModuleAbcConfiguration {
private static ModuleAbcApi moduleAbcApi;
// Test Use (allows to inject mocks)
ModuleAbcApi newTestInstance(externalModule) {
return newInstance(externalModule);
}
// Production Use
ModuleAbcApi instance() {
if (moduleAbcApi == null) {
moduleAbcApi = newInstance(
new ExternalModuleConfiguration.newInstance()
);
}
return moduleAbcApi;
}
// Module Assembly Logic
private ModuleAbcApi newInstance(externalModule) {
return new ModuleAbc(
new AdapterRealImplementation(),
new Helper(),
externalModule,
...
);
}
}
// (Manual) Provider Based DI
// The purpose of this class is to avoid the overhead of DI libraries while letting you encapsulate the internal implementation
// of a module/package and allow you to test with mocks.
// Assume ModuleApi is a public class that exposes an api to interact with your module/package
// Assume ExternalDepConfig is a 'config' class similar to the one presented here (but it belongs to another module/package)
// This pattern works really well with Hexagonal Arquitecture, package encapsulation and BDD mock testing.
// The Subject Under Test being the ModuleApi, usually with mocked external dependencies.
// All other dependencies (Dependency1, 2, ...) are package-private classes that are never referenced in testing.
// This allows us to refactor the internal implementation without changing tests (as long as we don't modify public apis or dtos)
public class ModuleApiConfig {
/** Used in the application */
static ModuleApi newModuleApi() { new ModuleApiMainProvider().newModuleApi(ExternalDepConfig.newExternalDep()); }
/** Used in testing */
static ModuleApi newTestModuleApi(MockableDep mockableDep) { new ModuleApiTestProvider().newModuleApi(mockableDep); }
private ModuleApiConfig {}// Not meant to be instanciated
private static class ModuleApiMainProvider {
/** Not meant to be overriden, centralizes the instantiation of ModuleApi */
final newModuleApi(MockableDep mockableDep) {
return new ModuleApi(
dependency1(),
dependency2(
mockableDep,
random()
)
);
}
Dependency1 dependency1() {
return new Dependency1()
}
// ...
Random random() {
return new Random()
}
}
/** This class overrides the other provider with fakes (like in memory repos), seeded randoms, now() and similar */
private static class ModuleApiTestProvider extends ModuleApiMainProvider {
@Override
Random random() {
return new Random(42);//seeded random
}
}
}
// PseudoCode for Spring DI
class ModuleAbcConfiguration {
// Test Use (allows to inject mocks)
ModuleAbcApi newTestInstance(externalModuleMock) {
return newInstance(externalModuleMock);
}
@Bean // Module Assembly Logic and Production Use
ModuleAbcApi newInstance(externalModule) {
return new ModuleAbc(
new AdapterRealImplementation(),
new Helper(),
externalModule,
...
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment