Configuration Engine Rework
hkupty
A few months ago I wrote about the road to 1.0 and one of the key things there was the configuration. Now I'm super thrilled to talk about its refactoring.
I'm going to outline a few concepts that I believe are key to that refactoring that is now landing on the 0.8 version, from a higher perspective, as a Software-engineering analysis more than a library-specific view. For the library details, have a look at Penna's webpage or the Github Repository
Up until 0.7.2
The previous version was very inspired in the Service Loader pattern, but it wasn't too thought through..
In a nutshell, it would try to mount the first available Configuration Manager instance that it could find. This is a problem because it means that in a scenario where two different implementations would exist, one would have to "win" the race and be mounted, while the other would never have the chance to be loaded.
Additionally, it would delegate a lot of the responsibility to either the implementation or the logger storage. This is a bad idea as we'll see further down, but mostly it means that the some of the implementation details would be copy-pasted between the implementations and it was a thin, unnecessary layer.
The data between the layers was also complex, as it was often the cases that the concrete implementation had to pass in a function that the storage would then execute to perform the updates.
Then comes version 0.8
The new Penna version comes with a reworked mechanism that is composed by distinct pieces with clear responsibilities. There are three main components involved now, the Manager
, the Provider
and the Storage
.
While previously the ConfigManager
(now simply the Manager
as the namespace tells the necessary context) had to be implemented all the time, the Manager here is a simple, internal implementation that just ensures that the changes requested by the Provider
s are applied into the Storage
.
The Provider
s themselves now are the ones implementing the ServiceLocator
pattern, but in this case it is a much smaller role they have. They're responsible to inform the manager what has to be applied, not where, not how as they had before.
The manager, intelligently, identifies the Provider
s that are capable and only then initializes them. So, for example, if you have the penna-yaml-config
jar in your classpath but your application doesn't have any penna.yaml
file available, the yaml config Provider
won't advertise itself as available and won't be requested anymore during this runtime.
Although the manager starts the Provider
s, it doesn't keep them. It's the other way around instead. By passing itself to the Provider
instances and they, either through an internal thread (i.e. the File Watcher thread from penna-yaml-config
, for example) or normal application runtime (in the case of a custom Provider), they call the manager for updating the logger configs.
The Manager
then translates the data to an API that the storage can then parse and understand clearly, so no function passing involved.
Key takeaways
- Keep your contracts simple between functions;
- Ensure the boundaries are well established, not blurry lines;
- Simplify. :)
If you liked this article, consider sharing, leaving a feedback, subscribing.
Thank you very much, Henry