-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable structured types as config values #77
Conversation
This commit enables structured types as config values. That is, something like ``` @BaiganConfig public interface SomeComplexConfiguration { SomeConfigObject someConfig(); } ``` can now be deserialized, e.g. from a config file with content ``` [{ "alias": "some.complex.configuration.some.config", "defaultValue": {"config_key":"a value"}}] ``` How it works is that the repositories have to be constructed with a class `BaiganConfigClasses`, a container for all interfaces annotated with `@BaiganConfig`. The repository then deserializes first to `JsonNode`, then deserializes this to the correct class for each config key. The `BaiganConfigClasses` is provided as a bean. It is constructed by the `ContextAwareConfigurationMethodInvocationHandler` when creating the proxies for the `BaiganConfig` interfaces. Beyond this bean, users of the library do not have to add any extra configuration to enable structured config types. They only have to make sure that the `ObjectMapper` can deserialize the classes correctly. To this end, the `ObjectMapper` can now be injected into the repositories. The change is backwards compatible with respect to config files. All old config files can be processes as before. This is covered in an End2End test. It is not generally backwards compatible regarding the code as deprecated constructors for the `S3ConfigurationRepository` were removed and all constructors for repositories now require `BaiganConfigClasses` to be passed.
As the repositories now return the type checking and constructor logic can now simplified. Primitive types would in principle work. However, if no config can be found, then trying to return null for a primitive return type of a configuration would cause a NullPointerException. To make sure users do not declare primitives as return types, the BaiganConfigClasses throws and exception if there are primitive return types, causing the starting of the ApplicationContext to fail.
… inheritance The inheritance implied by the AbstractConfigurationRepository makes it harder to test its logic and that of the repositories that use it. This commit changes it to composition, turning the abstract class into a service that is used by the repositories. The EtcdConfigurationRepository did not actually use any of the functionality of the abstract class, so it does not have the new service as component. Also, this commit increases the test coverage.
@lukasniemeier-zalando do we want to add a component test for the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the change as it moves deserialization to the pre-caching layer, which I believe is a crucial change once we allow complex type (deserialization). It's a considerably large change that I expect us to iterate a bit on, we may have to communicate this to library users (and find an early adopter application for validation).
I am interested in the thoughts of other baigan maintainers (if there are any left 😢) regarding if one want to support complex types with baigan. I know that the original intention was to only support simple feature flags by design (true
/false
or 0
/100
).
src/main/java/org/zalando/baigan/service/EtcdConfigurationRepository.java
Outdated
Show resolved
Hide resolved
src/main/java/org/zalando/baigan/service/ConfigurationParser.java
Outdated
Show resolved
Hide resolved
src/main/java/org/zalando/baigan/service/FileSystemConfigurationRepository.java
Outdated
Show resolved
Hide resolved
My vote goes for removing it (in a separate, maybe earlier) PR. We have discourage from usage for many years |
Done in #78 |
Good call out. I'd appreciate input by other maintainers as well. The reasoning is that I see Baigan applied not just as a feature flag library, but as a general library for business configuration, where each file contains the configuration for a particular use case. This is a lot nicer if structured configuration types are possible. |
This commit adds a builder for the FileSystemConfigurationRepository to simplify future refactorings of the FileSystemConfigurationRepository and hiding potential future complexity of the construction of this class. This was triggered by the discussion #77 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, first, I'd like to note that I love the change, and it's something that is truly useful to have supported by the library out of the box. In my team, we used complex configs for a long time, but we're applying workarounds. I've added questions related to the backward compatibility of such workaround with this feature release.
src/main/java/org/zalando/baigan/proxy/ConfigurationBeanDefinitionRegistrar.java
Outdated
Show resolved
Hide resolved
.../java/org/zalando/baigan/proxy/handler/ContextAwareConfigurationMethodInvocationHandler.java
Show resolved
Hide resolved
src/main/java/org/zalando/baigan/service/ChainedConfigurationRepository.java
Outdated
Show resolved
Hide resolved
This commit adds support for handling generics in the retunr type of a config method. Instead of parsing `Map<UUID, List<SomeConfigObject>>` to a `Map<String, List<Map<...>>>`, it is now correctly processed to the expected type. This works by using `Type` instead of `Class<?>` in `BaiganConfigClasses` and actually using the `AnnotatedType` instead of the plain type when constructing the bean in `ConfigurationBeanRegistrar`.
Repositories no longer require a `BaiganConfigClasses` instance upon construction. Instead, this class is hidden by a `ConfigTypeProvider` and a `ConfigurationParser` bean is provided to a `ConfigurationRepository` by the latter implementing `ApplicationContextAware`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, many thanks! In terms of open tasks I see the "documentation one", for which I feel a light refresh of the README
can also be a dedicated PR (we also still have #78 (comment)). Besides that I guess it's just resolving the merge conflicts now, then we take it in.
final ConfigTypeProvider configTypeProvider; | ||
|
||
@Autowired | ||
public ConfigurationParser(final ConfigTypeProvider configTypeProvider, @Qualifier("baiganObjectMapper") final Optional<ObjectMapper> objectMapper) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a test (or shall we add one) that verifies that an application context offering a specifically qualified ObjectMapper
bean is handing it to the ConfigurationParser
(plus the negative variants of that validation)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a test that does it, but since it does not special configuration of the object mapper, it does not actually validate it properly. I'll extend this to cover the case.
Can you also check the change proposal #82 The current PR makes using the |
While relatively silently accepting failed parsing of configuration is problematic, this is how the EtcdConfigurationRepository behaved in the past. So it makes sense to not change the behavior here as the main change of the whole PR is not concerned with this.
👍 |
This commit enables structured types as config values. That is, something like
can now be deserialized, e.g. from a config file with content
Users of the library do not have to add any extra configuration to enable structured config types.
It works like this:
BaiganConfigClasses
that provides a mapping of config keys to their types. It is provided as a bean that is constructed by theContextAwareConfigurationMethodInvocationHandler
when creating the proxies for theBaiganConfig
interfaces.ConfigurationParser
.ConfigurationParser
does the actual deserialization of the config values. It first deserializes the data toConfiguration<JsonNode>
, then deserializes theJsonNode
config value to the correct class for each config key, using a bean of typeBaiganConfigClasses
to determine the right type.ConfigurationParser
is passed to the repositories by theRepositoryFactory
. Library users do not need to be aware of these details.ObjectMapper
instance that will be used for the deserialization of the configuration.The change is backwards compatible with respect to config files. All old config files can be processes as before. This is ensured in End2End tests.
TODOs