if in a monolith MVC application (i.e rails) you have other entry-points than just the Controller? I.e background jobs, or the model interaction?
Consider workers (jobs executors) as actors of the system. Provide them with identity in the security service so that they can engage with it as any other actor and play with the same rules as the rest. Grant them with special privileges if necessary. I think you are half-way to that - This task normally have to impersonated by a specific user, to avoid to have the wrong access level. comments
Make the input ports of the application accesible to workers. These ports are the gate through which outer-boundaries (contexts) get access to the application. You already have these ports; the controllers. They fit perfectly with the purpose. However, they probably are very specialized in handling web inputs, so it might take some technical adaptations. Perhaps, with a little luck, a couple of basic proxyies for a couple of controllers is enough. Perhaps you have to make available some more, then a gateway should do the job too. Both are good components to engage with security and other tasks.
public interface SalesController {}
public final class WebSalesController implements SalesController {}
public final class ProxySalesController implements SalesController {
private final SalesController salesController;
private final SecurityService securityService;
public ProxySalesController(
SalesController salesController,
SecurityService securityService){
this.salesController = salesController;
this.securityService = securityService;
}
public void aSalesMethod(InputData data){
//TODO : Replace with env vars or properties
securityService.doLogin("jobUser","jobuserCredentialsOrToken");
salesController.aSalesMethod(InputData data);
}
}
Regarding audit, you are right. The audit, the logging, the security are cross-cutting concerns that usually bloat our source code making the reading of the business a little bit harder. You can decouple these features (to a point) with Aspect-oriented programming or with proxy|gateways as I commented.
public final class ProxySalesController implements SalesController {
// the previous code ...
private final Auditor auditor;
private final Logger Logger;
public void aSalesMethod(InputData data){
...
doLog(data);
...
doAudit(data;
}
// rest of the code ...
public void doLog(InputData data){
if(logger ! = null){
logger.debug("Useful messages for developers goes here");
}
}
public void doAudit(InputData data){
if(logger ! = null){
logger.trace("Useful messages for monitoring and traceability goes here");
}
}
}
Proxy pattern can be implemented in any layer. You can declare an interface for Product and have 2 implementations: one for the business and other for the audit. Their respective instantiations are delegated to factories or, if you prefer, to builders.
With this approach I prupose:
- Small changes: I would expect some refactors alongside with new classes, but not huge redesigns.
- Code reuse: There's already working code, I would turn this into my stable dependency for the solution to implement.
- Testability : the patterns mentioned plus DI and some more good practices should result in code easy to test.
- Concern segregation : move audit, logging and security away (as much as possible) to the business. Business should not be affected by these details.