If you have too many dependencies being passed around, the general technique is to eliminate those dependencies higher up in the call stack, by changing the order decisions are made. This is easiest to explain with an example:
getPath(config, user) {
if (config.isB2B())
return b2bpath(config, user);
else
return b2cpath(config, user);
}
b2bpath(config, user) {
if (!config.allowedToAccessPath(user))
return accessDeniedPage();
else
return "My fancy b2b page";
}
b2cpath(config, user) {
if (!config.allowedToAccessPath(user))
return accessDeniedPage();
else
return "My fancy b2c page";
}
You are repeating the authorization check down at the lowest levels of the call stack, so move it up:
getPath(config, user) {
if (!config.allowedToAccessPath(user))
return accessDeniedPage();
if (config.isB2B())
return b2bpath();
else
return b2cpath();
}
b2bpath() {
return "My fancy b2b page";
}
b2cpath() {
return "My fancy b2c page";
}
Then repeat to see if you can move some of the decisions into the code that calls getPath
. This is a simple example, but I see the former kind of code all over the place with more layers. Start with decisions at your lowest layers, and try to figure out ways to move them up. Sometimes this requires judicious use of inheritance, like:
getPath(config, user) {
module = config.isB2B() ? B2BModule() : B2CModule()
if (!config.allowedToAccessPath(user))
return module.accessDeniedPage();
return module.getPath();
}
It's very rare not to be able to simplify dependencies this way. It's a matter of trying different arrangements until you find one that works.
If you have workflow-type dependencies, not just data dependencies, as in your first example, you can separate them out using something like this:
step1 = new ValidateHeaderId(inputHeaderId);
step2 = new FindShipment(retShipmentRepository);
step3 = new ValidateUserPermission(user, inputPostCode, inputEmail);
step4 = new SaveReturnOrder(inputLines, inputReference);
step5 = new CheckSaveStatus();
notSet = new NotSetPage(templateEngine, searchPage);
notPermitted = new NotPermittedPage(templateEngine, searchPage);
saveErrors = new SaveErrorsPage();
success = new SuccessPage();
requestTokenConfirmation = new RequestTokenConfirmation();
steps = [step1, [notSet, step2], [notSet, step3], [notPermitted, step4],
[saveErrors, step5], [success, requestTokenConfirmation]];
executeSteps(steps);
This recognizes you have a series of steps which each produce some sort of result and choose the next step. executeSteps
abstracts away the repetition of calling run()
on each step, and passing the output from the previous step into the next step. This allows the steps to be stored in a data structure instead of a function, which can then be built up in several different ways, including by some sort of registration process or config file. Once each step object has been created, its dependencies no longer need to be tracked outside it. I believe the rules engines from BobDalgleish's answer are basically pre-existing libraries to help you do this more easily.
Using a thread local variable. I could store the IP address in the
Controller into a thread local variable and access it in the
EventService. I'm leaning towards this one, but I don't know.
I've used this approach before, for storing contextual information about requests. I would perhaps expand from the above to store a thread-local request context object (one attribute of which would be the IP address, you could also store the requesting user id etc. etc.)
It's a clean and simple means to achieving a solution which otherwise will impact all your code paths. I think it's clean because the contextual information is unrelated to the individual method calls in your program logic. It does rely on the server giving each request it's own thread, but so long as you're aware of that then I think it's a useful approach.
I've seen this done to an elaborate degree where the contextual information is built up as the request touches different entities - the entity ids are recorded along with significant events - and the resultant contextual object is available for future diagnosis (by being persisted separately). It provides a powerful alternative to searching through log files of multi-threaded servers.
Best Answer
It seems like you're viewing the problem as one of re-configuring your application based on the subdomain it is accessed from.
Given that your university specific data is basically user data - i.e. it will change as users sign up for and use your application, I think a better approach is to store all of that stuff in your database and then create a service for accessing it when you need to, rather than as part of the application startup.
I would write a simple and minimal service that inspects the subdomain and returns the university ID. (You'd need to inject the @request_stack into it (see https://stackoverflow.com/a/20502945/86780), access the hostname of the current request and perform some sort of lookup.)
As for creating subdomains dynamically - I'd suggest that you configure your webserver to work with any subdomain (i.e. *.yourdomain.tld) and have your application figure out whether it's valid or not.
Basically, avoid writing server config dynamically. This approach is likely give you problems when it comes to scaling and tuning your application.