Configuration Engine Rework

hkupty

hkupty

image.png

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 Providers are applied into the Storage.

The Providers 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 Providers 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 Providers, 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

Photo by Kai Dahms on Unsplash