JAVA-1310: Implementation of MapperConfiguration and basic behavior#771
Conversation
…ate set[unordered] to list[ordered]) -fix an issue with initializing tests
|
Hi again. I had all tests pass from intellij, but i had to tweak them to also pass through mvn directly. Additionally i wanted to supply an example of usage of the new |
|
So I've taken some time to take a look at this PR. This is very extended work, thank you @avivcarmis for your involvement. As an overall remark: I believe that while this is very extensive and detailed, there are a lot of new configuration classes introduced, and while reviewing I found it pretty hard to follow. Remembering all of these classes' purposes and relationships and names may be extra complications for users. ( So I agree with the need of a
Overall I agree that having all the separate fine-grained config classes kind of gives a more extensible approach, but it's quite hard to understand the meaning of all the classes. If I resume the configurations we want to expose, there are:
Then I think simpler options directly on For 1) and 3), are these settings that make sense on the global Thoughts? |
|
@newkek Hi Kevin. Okay so there's a lot to cover. For starters i'll say that i agree with all your inputs. I do have some thoughts that may make you change your mind regarding some of the comments. Let's decide which direction to go with each one. As a general overview, i gotta say i kind of took it one step forward and instead of simply creating an interface for defining field mapping config, i tried to lay the ground for defining config for the entire mapper behavior, so that in the future we would be able to add more parameters on top of that config object instead of passing multiple config objects. Since i'm not that creative with examples, i can give you a real one, in the next PR i've pushed i've added an interface for defining naming conversion strategy (auto mapping camelCase to snake_case) - so then instead of passing (or not) one object for setting the mapping config and then passing (or not) an object for defining naming strategy, you can just pass the one global mapper config object, with just the settings you want to tweak changed, the rest left with their default values. Third point (
This is not entirely true, you may have (not that i can think of a good reason for that) some fields with getters and then some others without. You may want to give the opportunity to scan both. For example you may want to map both Again, this is not a classic case so we can skip it if it's over complicating things. Also there's the last option of no fields and no getters allowed meaning no columns are mapped at all which is obviously redundant.
Regarding the
Regarding the last point, i think that this settings (hierarchy scanning and whitelist vs blacklist) may absolutely be shared between classes. I actually wanted it for a project i worked on myself. I use annotate every field i want to persist with So again i want to say that i do not disagree with any of the comments i just want you to know what i meant to achieve and maybe with popper documentation it may be convenient, but i think you're in a better position to decide on every one of those. Cheers! |
Well, looking at the other PR (the naming strategy) isn't it a way to define "How property are going to be mapped when scanning them"? Why isn't it part of the About scanning for (getters && fields), I see your point. Although yeah I am not sure either if there is a need but I understand it would be a possible feature. Still, the extra class seems superfluous and I'd rather opt for a Enum config. So I'd personally leave a "?" on whether to choose option 2 or 3 for now. For After thinking more about it yeah I think the White and black listing config could be also a MappingManager level setting (thinking about JAVA-1310 so yes let's keep the In general I've had my fair share of struggles when dealing with libraries that have too much configuration classes (taking the example of Astyanax), I believe the driver has a lot already, and creating a single class for just One option each time here seems too much. The more classes there are, the more potential opportunities of breaking changes, and for these reasons as you may have guessed I'm a big supporter of the KISS principle when possible. Maybe we can have the other driver devs weigh in on these points and see their points of view. |
|
So to sum up what we have so far:
Let me know how you feel about it and i'll get right on it! |
|
Sorry I closed this by mistake, I removed my old branch and I didn't realize it was based on it. Will reopen shortly. |
olim7t
left a comment
There was a problem hiding this comment.
Great contribution @avivcarmis !
To chime in on the conversation with Kevin:
I would slightly prefer MappingConfiguration as well (you can pass it to MappingManager so it's not specific to mapper instances).
PropertyScanConfiguration class should be removed and it's properties should be transferred to MapperConfiguration.
Actually I'm not convinced. I think we should think about what future options might come, and if it makes sense to group them all together.
PropertyScanScope class will be changed to an enum containing {ONLY_GETTERS, ONLY_FIELDS, FIELDS_AND_GETTERS}.
+1
HierarchyScanStrategy class will be migrated to a single field of type Class<?> indicating the deepest ancestor to scan.
+1
| <method>java.lang.String[] transientProperties()</method> | ||
| <justification>False positive, it's an annotation and the new method has a default value</justification> | ||
| </difference> | ||
|
|
| // try to register an updated version of the previous codec | ||
| try { | ||
| getUDTCodec(udtClass); | ||
| getUDTCodec(udtClass, defaultConfiguration); // TODO - this will update with default configuration even though previously loaded with a custom one. Solution may be to pass the MappedUDTCodec it's configuration on creation time, and then at this point we are able to get it from the previous instance |
There was a problem hiding this comment.
I agree. Instead of udtClasses, we can store the whole codecs in a deletedCodecs set, and add a way to access the configuration in MappedUDTCodec (package-private getter or direct field access).
| * hierarchyScanEnabled set to false - parent classes will not be scanned at all. | ||
| * if scanOnlyAnnotatedClasses set to true, only parents annotated with table classes | ||
| * ({@code Table}, {@code UDT}, {@code Accessor}) will be scanned. And lastly - deepestAllowedAncestor | ||
| * will define the deepest parent class to scan - which default to {@code Object.class}</li> |
There was a problem hiding this comment.
These detailed explanations are great. I think they would be better located on each corresponding setter's javadocs in PropertyScanConfiguration (this is likely the first thing a developer will use in their IDE).
| */ | ||
| public class MapperConfiguration { | ||
|
|
||
| private PropertyScanConfiguration propertyScanConfiguration; |
There was a problem hiding this comment.
The configuration is shared across multiple threads, so every mutable field (here and inside PropertyScanConfiguration itself) should be volatile.
There was a problem hiding this comment.
Yeah i'll will.
Maybe it's worth mentioning this case:
MapperConfiguration config = ...
Mapper<User> userMapper1 = manager.mapper(User.class, config);
config.changeSomething();
Mapper<User> userMapper2 = manager.mapper(User.class, config);
// at this point we should make sure that userMapper1 has no direct references to config fields,
// otherwise it would make it change it's config from outside and after creation
So for this end each configuration class implements a copy constructor and on mapper creation we clone the given config instance.
I think if we are to extend the usage of the config class in the future it may be a bit hard to maintain since the developer must remember it or fields will not be copied. If we want to avoid it at the price of a bit efficiency loss we can use some 3rd party lib to deep copy the object only on mapper creation and then we can remove those constructors.
Thoughts?
There was a problem hiding this comment.
On the other hand, if I set a global configuration and then change something on it, I kind of expect the change to be reflected on every mapper, currently with the copy this would not be the case.
Maybe we're overthinking this. All the configuration we have so far is stuff you set once at startup, and don't change at runtime. We could make the configuration classes immutable and avoid the copies.
we can use some 3rd party lib to deep copy the object
We avoid dependencies as much as possible. It leads to dependency hell if users already use another version of the library in their application. Guava is a recent example (JAVA-1328).
There was a problem hiding this comment.
👍 on making the configuration immutable. I think changing the config at runtime doesn't offer a lot of value and also adds uncertainty.
| return this; | ||
| } | ||
|
|
||
| public static class PropertyScanConfiguration { |
There was a problem hiding this comment.
Do you mind moving this to a top-level class? Historically the driver codebase has been using inner classes a lot, but I must say I'm not a fan of it, it creates very long source files.
| @SuppressWarnings("unchecked") | ||
| private <T> Mapper<T> getMapper(Class<T> klass) { | ||
| private <T> Mapper<T> getMapper(Class<T> klass, MapperConfiguration configuration) { | ||
| configuration = new MapperConfiguration(configuration); |
There was a problem hiding this comment.
The configuration is not used in the map key. So if you do this:
Mapper<User> userMapper1 = manager.mapper(User.class, config1);
Mapper<User> userMapper2 = manager.mapper(User.class, config2);Then you get the same object and userMapper2 uses config1.
Granted, this is a weird use case, so I'm not sure we want to go through the trouble of modifying the map key. But this should at least be documented in the javadoc, and maybe we should log a warning or throw an error on the second call (that forces us to store the configuration in the mapper, and implement equals on all configuration classes).
There was a problem hiding this comment.
I agree.
We can easily implement a key containing both class and config with equals method and make sure it won't happen.
Whichever we like better.
There was a problem hiding this comment.
We can easily implement a key containing both class and config with equals method and make sure it won't happen.
+1 (a bit tedious for sure).
There was a problem hiding this comment.
This means all configuration classes must implement equals and hashcode. In hindsight I would be fine with just documenting it.
+1
Yes, I'm +0 here so let's keep it the way it is now imo.
+1, but with some minor changes:
+1 |
adutra
left a comment
There was a problem hiding this comment.
Looks very good in overall! Please take a look at my (very minor) suggestions. Thanks @avivcarmis !
| } | ||
|
|
||
| /** | ||
| * Returns a collection of properties to exclude from mapping |
There was a problem hiding this comment.
- I would suggest to complete the documentation by stating that properties defined here will be transient on a per-mapper basis; if the user wants a more fine-grained tuning, there is also the
transientPropertiesattribute in@Tableand@UDT. - I would suggest to normalize the namings here. Since it's already called
transientPropertiesin the annotations and we cannot change that, why not call this methodgetTransientProperties()instead? Same for some local variables inAnnotationParser, these are currently calledclassLevelTransients, which is a bit confusing.
|
|
||
| } | ||
|
|
||
| public enum PropertyMappingStrategy {BLACK_LIST, WHITE_LIST} |
There was a problem hiding this comment.
I would prefer OPT_IN and OPT_OUT, but if I'm the only one to ask that, it's fine to keep it the way it is.
| // (should properties be transient by default or not) | ||
| switch (mapperConfiguration.getPropertyScanConfiguration().getPropertyMappingStrategy()) { | ||
| case BLACK_LIST: | ||
| return hasAnnotation(Transient.class) || |
There was a problem hiding this comment.
There is a pull request ( #750 ) that suggests to also consider fields annotated with the Java keyword transient – as they are probably not meant to be serialized nor persisted in any form. I think it's a valid point but the author has not accepted the CLA, so if you want to consider that here, I wouldn't be against it.
| @SuppressWarnings("unchecked") | ||
| private <T> Mapper<T> getMapper(Class<T> klass) { | ||
| private <T> Mapper<T> getMapper(Class<T> klass, MapperConfiguration configuration) { | ||
| configuration = new MapperConfiguration(configuration); |
There was a problem hiding this comment.
We can easily implement a key containing both class and config with equals method and make sure it won't happen.
+1 (a bit tedious for sure).
| */ | ||
| class PropertyMapper { | ||
|
|
||
| private static final Set<Class<? extends Annotation>> COLUMN_ANNOTATIONS = ImmutableSet.of( |
There was a problem hiding this comment.
Instead of defining this here, I'd suggest you to reuse AnnotationParser.VALID_COLUMN_ANNOTATIONS and AnnotationParser.VALID_FIELD_ANNOTATIONS by making them package-private and combining them together.
|
OK, i'm summing up everything we went through so far. Anyone objecting to anything comment so i know to hold that up. Personally i'm good with everything we have. The last point should be decided on since it was left to later discussion.
|
|
Thanks for summing it up @avivcarmis ! I am 👍 for everything but the following: Item 4) I am still a bit uncomfortable with it. If only the first configuration object is taken into account, there is a code smell here: it means that the configuration is meant to be manager-wide, not mapper-wide. If you agree with my diagnostic, how about creating a setter for Item 13) I would rather keep this class for the reasons Olivier stated. |
| * @param excludedProperties a collection of properties to exclude from mapping | ||
| * @return the PropertyScanConfiguration to enable builder pattern | ||
| */ | ||
| public PropertyScanConfiguration setExcludedProperties(Collection<String> excludedProperties) { |
There was a problem hiding this comment.
Related to Olivier's comment about immutability, Unless I missed a previous discussion rather than setters, I would prefer we use a builder strategy for PropertyScanConfiguration and MappingConfiguration. Once intialized it would be nice if the configuration would not change and we use a builder strategy for a lot of other modes of configuration.
| } | ||
|
|
||
| @Test(groups = "short") | ||
| public void ignore_non_annotated_classes() { |
There was a problem hiding this comment.
Small nit: should be should_ignored_non_annotated_classes()
I think the final consensus was rather to make all configuration objects immutable, ie make the fields
The configuration object taken into account depends on the first call to |
|
@avivcarmis we plan to wrap up 3.2.0 development soon, do you have bandwidth to complete those pull requests? The roadmap looks pretty clear now, so we can help you with some of the remaining tasks (we generally squash before merging, so the commits will remain attributed to you no matter what). |
|
Argh, got caught again by the fact that this PR was opened against the driver's existing |
|
Nope, that doesn't work. This time I accidentally pushed your |
|
Resurrected as #814. |
Is this really useful? Can't we assume that one configuration is enough for the whole |
Hi all!
As discussed with Alexandre on the Jira ticket, i took some effort to try and implement the mapper configuration the way i believe to be the most convenient.
So iv'e implemented the following abilities:
Take a look and let me have some feedback if you have any. I think once the general concept of MapperConfiguration is implemented, we can build more features on top of it (for example solving JAVA-1316).
Note: I added two elements to clirr.xml that prevented me from compiling the source, let me know if i made a mistake.