Skip to content

286 product type attributes enum actions#295

Merged
jortizsao merged 14 commits intomasterfrom
286-product-type-attributes-enum-actions-b
Aug 7, 2018
Merged

286 product type attributes enum actions#295
jortizsao merged 14 commits intomasterfrom
286-product-type-attributes-enum-actions-b

Conversation

@jortizsao
Copy link
Copy Markdown
Contributor

Summary

This PR adds the attribute definition update actions for plain enum values and localized enum values

Description

  • AttributeDefinitionUpdateActionUtils
    • Add PlainEnumValue to attribute definition update action
    • Change order of PlainEnumValue update action
    • Remove PlainEnumValues from attribute definition update action
    • Change plain enum value label update action
    • Add LocalizedEnumValue to attribute definition update action
    • Change order of LocalizedEnumValue update action
    • Remove LocalizedEnumValues from attribute definition update action
    • Change localized enum value label update action

Todo

  • Tests
    • Unit

* Add PlainEnumValue to attribute definition update action
* Change order of PlainEnumValue update action
* Remove PlainEnumValues from attribute definition update action
* Change plain enum value label update action
* Add LocalizedEnumValue to attribute definition update action
* Change order of LocalizedEnumValue update action
* Remove LocalizedEnumValues from attribute definition update action
* Change localized enum value label update action
# Conflicts:
#	src/main/java/com/commercetools/sync/producttypes/utils/AttributeDefinitionUpdateActionUtils.java
#	src/main/java/com/commercetools/sync/producttypes/utils/ProductTypeUpdateActionUtils.java
@codecov-io
Copy link
Copy Markdown

codecov-io commented Jul 30, 2018

Codecov Report

Merging #295 into master will increase coverage by 0.13%.
The diff coverage is 98.1%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #295      +/-   ##
============================================
+ Coverage     95.15%   95.28%   +0.13%     
- Complexity      967     1011      +44     
============================================
  Files            88       94       +6     
  Lines          2415     2546     +131     
  Branches        120      123       +3     
============================================
+ Hits           2298     2426     +128     
- Misses           99      102       +3     
  Partials         18       18
Impacted Files Coverage Δ Complexity Δ
...cttypes/utils/LocalizedEnumUpdateActionsUtils.java 100% <100%> (ø) 3 <3> (?)
...ync/commons/exceptions/DifferentTypeException.java 100% <100%> (ø) 3 <3> (?)
...ils/ProductTypeUpdateLocalizedEnumActionUtils.java 100% <100%> (ø) 4 <4> (?)
...s/utils/ProductTypeUpdatePlainEnumActionUtils.java 100% <100%> (ø) 4 <4> (?)
...oductTypeUpdateAttributeDefinitionActionUtils.java 100% <100%> (ø) 17 <1> (ø) ⬇️
...roducttypes/utils/PlainEnumUpdateActionsUtils.java 100% <100%> (ø) 3 <3> (?)
...es/utils/AttributeDefinitionUpdateActionUtils.java 100% <100%> (ø) 20 <10> (+9) ⬆️
...oducttypes/utils/ProductTypeUpdateActionUtils.java 100% <100%> (ø) 5 <0> (ø) ⬇️
...types/utils/ProductTypeUpdateEnumActionsUtils.java 93.75% <93.75%> (ø) 18 <18> (?)
... and 5 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3e18555...14ea923. Read the comment docs.

@jortizsao jortizsao changed the title 286 product type attributes enum actions b 286 product type attributes enum actions Jul 31, 2018
Copy link
Copy Markdown
Contributor

@lojzatran lojzatran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot finish the review now because there are some other urgent tasks. So here's what I saw so far.


import javax.annotation.Nonnull;

public class DifferentTypeException extends RuntimeException {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you write a short comment when is this exception used?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throws DuplicateKeyException, DifferentTypeException {

return Stream
final List<UpdateAction<ProductType>> updateActions = Stream
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be only inside if (haveSameAttributeType(oldAttributeDefinition, newAttributeDefinitionDraft)) {.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.collect(toList());

if (haveSameAttributeType(oldAttributeDefinition, newAttributeDefinitionDraft)) {
if (isPlainEnumAttribute(oldAttributeDefinition)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other attribute types will be done later?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only PlainEnumValues and LocalizedEnum values because you can't apply update actions on the other attribute types according to the commercetools API: https://docs.commercetools.com/http-api-projects-productTypes.html

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also update of isSearchable etc., but I understand now.

*
* @return true if both attribute definitions have the same attribute type, false otherwise.
*/
private static final boolean haveSameAttributeType(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for final as method is private.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @param oldAttributeDefinitions the old list of attribute definitions.
* @param newAttributeDefinitionsDrafts the new list of attribute definitions drafts.
*
* @param oldAttributeDefinitions the old list of attribute definitions.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you should sync the formatting rules with @heshamMassoud so you don't have these formatting changes here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already did it :). That's why the changes on the format in this file. At the beginning I had the default formatting rules.

final List<UpdateAction<ProductType>> updateActions =
buildRemoveAttributeDefinitionOrAttributeDefinitionUpdateActions(
oldAttributeDefinitions,
removedAttributesDefinitionsNames,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable got me confused. It's not used afterwards at all. Can you check?

Copy link
Copy Markdown
Contributor Author

@jortizsao jortizsao Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm... there is in line 118 and

updateActions.addAll(
               buildAddAttributeDefinitionUpdateActions(
                   newAttributeDefinitionsDrafts,
                   oldAttributesDefinitionsNameMap
               )
           );

it is also used in line 125, by finally returning the updateActions in line 131:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I was not clear, this attribute is not used: removedAttributesDefinitionsNames

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's used in line 112 in

final List<UpdateAction<ProductType>> updateActions =
                buildRemoveAttributeDefinitionOrAttributeDefinitionUpdateActions(
                    oldAttributeDefinitions,
                    removedAttributesDefinitionsNames,
                    newAttributesDefinitionsDraftsNameMap
                );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I see, but what do you do with it in the method? You have this in the method:

removedAttributeDefinitionNames.add(oldAttributeDefinitionName);

but then you don't use it anymore. So I still don't understand why do you need it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right @lojzatran :)

Removed!

return updateActions;
return updateActions;

} catch (final DuplicateNameException dne) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use } catch (final DuplicateNameException | DuplicateKeyException | DifferentTypeException dne) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

@lojzatran lojzatran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added my second part of review. Now I reviewed everything.

* Otherwise, if the enum values are identical, an empty optional is returned.
*/
@Nonnull
public static <T extends WithKey> Optional<UpdateAction<ProductType>> buildRemoveEnumValuesUpdateActions(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this:

@Nonnull
    public static <T extends WithKey> Optional<UpdateAction<ProductType>> buildRemoveEnumValuesUpdateActions(
        @Nonnull final String attributeDefinitionName,
        @Nonnull final List<T> oldEnumValues,
        @Nullable final List<T> newEnumValues) {

        final Map<String, T> newEnumValuesKeyMap = getEnumValuesKeyMapWithKeyValidation(
                attributeDefinitionName,
                Optional.ofNullable(newEnumValues).orElse(Collections.emptyList())
        );

        final List<String> keysToRemove = oldEnumValues
            .stream()
            .map(WithKey::getKey)
            .filter(oldEnumValueKey -> !newEnumValuesKeyMap.containsKey(oldEnumValueKey))
            .collect(Collectors.toList());

        if (!keysToRemove.isEmpty()) {
            return of(RemoveEnumValues.of(attributeDefinitionName, keysToRemove));
        } else {
            return empty();
        }
    }

You don't have to do if/else because you will receive newEnumValuesKeyMap empty anyway. And return of(RemoveEnumValues.of(attributeDefinitionName, keysToRemove)); is better because it can never be null.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better use specific type RemoveEnumValues instead of Optional<UpdateAction<ProductType>>?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better use specific type RemoveEnumValues instead of Optional<UpdateAction>?

I can treat all update action as a more generic UpdateAction type, so then all update actions applied to the product type are the same and easier to manipulate in more abstract methods used at higher levels of the product type sync

* Otherwise, if the enum values order is identical, an empty optional is returned.
*/
@Nonnull
public static <T extends WithKey> Optional<UpdateAction<ProductType>> buildChangeEnumValuesOrderUpdateAction(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the order comparision in this method. I see only existence check. Can you clarify?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array of new enum values drafts are either existing enum values or new enum values that are added and didn’t exist before.

The addition of new enum values are added at the end of the array (commercetools doesn’t allow to add a new enum value in a particular position of the array).

So concatenating the sorted keys of the existing enum values with the keys of the new ones, we would have the “natural” order if there wasn’t a change on the order.

Thus, if we compare this concatenated array containing the keys of the “natural” order against the array of keys of the enum values coming as a parameter, we can know that if the order is different it means that has been a change in the order.

Note: the method buildUpdateAction compares the first two parameters and returns the function applied to both if they are different

@Nonnull final List<T> newEnumValues,
@Nonnull final BiFunction<String, T, UpdateAction<ProductType>> addEnumCallback) {

final Map<String, T> oldEnumValuesKeyMap = getEnumValuesKeyMap(oldEnumValues);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not calling getEnumValuesKeyMapWithKeyValidation() also in this case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


return oldEnumValues
.stream()
.map(oldEnumValue ->
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit more consistent with the previous method calls:

return oldEnumValues
            .stream()
            .filter(oldEnumValue -> newEnumValuesKeyMap.containsKey(oldEnumValue.getKey()))
            .map(oldEnumValue -> matchingEnumCallback.apply(attributeDefinitionName, oldEnumValue, newEnumValuesKeyMap.get(oldEnumValue.getKey())))
            .flatMap(Collection::stream)
            .collect(Collectors.toList());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nonnull final String attributeDefinitionName,
@Nonnull final List<T> oldEnumValues,
@Nonnull final List<T> newEnumValues,
@Nonnull final BiFunction<String, T, UpdateAction<ProductType>> addEnumCallback) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better use specific type AddEnumValue instead of UpdateAction<ProductType>?

Copy link
Copy Markdown
Contributor Author

@jortizsao jortizsao Aug 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has to be the parent class because the action can be AddEnumValue or AddLocalizedEnumValue depending on the addEnumCallback will return one or the other.

Another thing is that I can treat all update action as a more generic UpdateAction<ProductType> type, so then all update actions applied to the product type are the same and easier to manipulate in more abstract methods used at higher levels of the product type sync

@Nonnull final String attributeDefinitionName,
@Nonnull final List<T> oldEnumValues,
@Nonnull final List<T> newEnumValues,
@Nonnull final BiFunction<String, List<T>, UpdateAction<ProductType>> changeOrderEnumCallback) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better use specific type ChangeEnumValueOrder instead of UpdateAction<ProductType>?

Copy link
Copy Markdown
Contributor Author

@jortizsao jortizsao Aug 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has to be the parent class because the action can be ChangeEnumValueOrder or ChangeLocalizedEnumValueOrder depending on the changeOrderEnumCallback will return one or the other

Another thing is that I can treat all update action as a more generic UpdateAction<ProductType> type, so then all update actions applied to the product type are the same and easier to manipulate in more abstract methods used at higher levels of the product type sync

import static com.commercetools.sync.producttypes.utils.ProductTypeUpdateEnumActionsUtils.buildChangeEnumValuesOrderUpdateAction;
import static java.util.Collections.emptyList;

public final class ProductTypeUpdateLocalizedEnumActionUtils {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is almost identical with class ProductTypeUpdatePlainEnumActionUtils. What about creating a new abstract parent class for both of them and put the common methods there?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ProductTypeUpdateLocalizedEnumActionUtils and ProductTypeUpdatePlainEnumActionUtils classes are similar in the buildUpdateAction method and someone could argue that I could also reuse the code in the ProductTypeUpdateEnumActionsUtils class, the same as it's done with other methods.

I did it that way first but the code ended up more tight and difficult to understand since all three particular methods for adding, removing and change order of the enums have to be passed as parameters in the common method placed at ProductTypeUpdateEnumActionsUtils, making this method to have 6 parameters where some of them are BiFunction, TriFunction... (long and "complex" signature)

So I finally decided to repeat some code in ProductTypeUpdateLocalizedEnumActionUtils and ProductTypeUpdateLocalizedEnumActionUtils in order to clarify and decouple the buildUpdateAction method of both classes.

}

@Test
public void buildActions_WithNewDifferentValues_ShouldReturnAction() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildActions_WithNewDifferentValues_ShouldReturnAction -> buildActions_WithNewDifferentValues_ShouldReturnActions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jortizsao jortizsao merged commit 5e973a5 into master Aug 7, 2018
Copy link
Copy Markdown
Contributor

@heshamMassoud heshamMassoud left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good but please check the comments @jortizsao :)

@Nonnull final String attributeDefinitionName,
@Nonnull final List<EnumValue> oldEnumValues,
@Nullable final List<EnumValue> newEnumValues)
throws DuplicateKeyException {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The throws DuplicateKeyException here is redundant since it's a runtime exception

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.map(Optional::get)
.collect(toList());
@Nonnull final AttributeDefinitionDraft newAttributeDefinitionDraft)
throws DuplicateKeyException, DifferentTypeException {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The throws DuplicateKeyException here is redundant since it's a runtime exception

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

));
}
} else {
throw new DifferentTypeException(format("The attribute type of the attribute definitions are different. "
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the attributeDefinitions with the same name but of different types we shouldn't just throw an exception but rather we should remove the old attributeDefinition and create a new one with the new type.

Copy link
Copy Markdown
Contributor Author

@jortizsao jortizsao Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't you think is more safer to throw an error, and if the user actually wants to change the type, he can follow a two step process of deleting the attribute in a first run and add the new one with the new type in a second run?

Removing is always dangerous but if you think that we should't notify the user and remove the attribute right away, it is ok :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed my mind :). let's help the user and avoid unnecessary steps ;)

*
* @return true if the attribute definition is a plain enum value, false otherwise.
*/
private static boolean isPlainEnumAttribute(@Nonnull final AttributeDefinition attributeDefiniton) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attributeDefiniton -> attributeDefinition in the variable name and in the javadoc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*
* @return true if the attribute definition is a localized enum value, false otherwise.
*/
private static boolean isLocalizedEnumAttribute(@Nonnull final AttributeDefinition attributeDefiniton) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here: attributeDefiniton -> attributeDefinition

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.collect(Collectors.toList());

if (!keysToRemove.isEmpty()) {
return ofNullable(RemoveEnumValues.of(attributeDefinitionName, keysToRemove));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RemoveEnumValues.of(attributeDefinitionName, keysToRemove) can never be null, so we should use Optional#of instead of Optional#ofNullable.

Also instead of if else, you can use conditional operator like that:

return keysToRemove.isEmpty() ? of(RemoveEnumValues.of(attributeDefinitionName, keysToRemove)) : empty();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


final Map<String, T> newEnumValuesKeyMap = getEnumValuesKeyMapWithKeyValidation(
attributeDefinitionName,
Optional.ofNullable(newEnumValues).orElse(Collections.emptyList())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see down there you already are statically importing the Optional#ofNullable, so would be good if here too for consistent style

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could also statically import Collections#emptyList() ;)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nonnull
public static <T extends WithKey> Map<String, T> getEnumValuesKeyMapWithKeyValidation(
@Nonnull final String attributeDefinitionName,
@Nonnull final List<T> enumValues) throws DuplicateKeyException {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The throws DuplicateKeyException here is redundant since it's a runtime exception

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nonnull final EnumValue oldEnumValue,
@Nonnull final EnumValue newEnumValue) {

return Stream
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(just a question) don't you think it looks better this way:

return Stream.of(buildChangeLabelAction(attributeDefinitionName, oldEnumValue, newEnumValue))
             .filter(Optional::isPresent)
             .map(Optional::get)
             .collect(toList());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)

).collect(Collectors.toList());
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method looks exactly the same as ProductTypeUpdateLocalizedEnumActionUtils#buildActions. Why don't you make a generic implementation and reuse. Like this:

    @Nonnull
    private static <T extends WithKey> List<UpdateAction<ProductType>> buildUpdateActions(
        @Nonnull final String attributeDefinitionName,
        @Nonnull final List<T> oldEnumValues,
        @Nonnull final List<T> newEnumValues,
        @Nonnull final TriFunction<String, T, T, List<UpdateAction<ProductType>>> matchingEnumActionBuilder,
        @Nonnull final BiFunction<String, T, UpdateAction<ProductType>> addEnumActionBuilder,
        @Nonnull final BiFunction<String, List<T>, UpdateAction<ProductType>> changeOrderActionBuilder){

        final List<UpdateAction<ProductType>> removeEnumValuesUpdateActions = buildRemoveEnumValuesUpdateActions(
            attributeDefinitionName, oldEnumValues, newEnumValues)
            .map(Collections::singletonList)
            .orElse(emptyList());

        final List<UpdateAction<ProductType>> matchingEnumValuesUpdateActions =
            buildMatchingEnumValuesUpdateActions(attributeDefinitionName, oldEnumValues, newEnumValues,
                matchingEnumActionBuilder);

        final List<UpdateAction<ProductType>> addEnumValuesUpdateActions = buildAddEnumValuesUpdateActions(
            attributeDefinitionName, oldEnumValues, newEnumValues, addEnumActionBuilder);

        final List<UpdateAction<ProductType>> changeEnumValuesOrderUpdateActions =
            buildChangeEnumValuesOrderUpdateAction(attributeDefinitionName, oldEnumValues, newEnumValues,
                changeOrderActionBuilder)
                .map(Collections::singletonList)
                .orElse(emptyList());

        return Stream.concat(
            Stream.concat(removeEnumValuesUpdateActions.stream(), matchingEnumValuesUpdateActions.stream()),
            Stream.concat(addEnumValuesUpdateActions.stream(), changeEnumValuesOrderUpdateActions.stream()))
                     .collect(Collectors.toList());
    }

and then reuse it for both by doing:
for ProductTypeUpdatePlainEnumActionUtils

buildUpdateActions(attributeDefinitionName, oldEnumValues, newEnumValues, PlainEnumUpdateActionsUtils::buildActions, AddEnumValue::of, ChangeEnumValueOrder::of);

and for ProductTypeUpdateLocalizedEnumActionUtils:

buildUpdateActions(attributeDefinitionName, oldEnumValues, newEnumValues, LocalizedEnumUpdateActionsUtils::buildActions, AddLocalizedEnumValue::of, ChangeLocalizedEnumValueOrder::of);

Copy link
Copy Markdown
Contributor Author

@jortizsao jortizsao Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@heshamMassoud I tried your way first and I explained the reason of "copying" to Lam. This is my previous comment

"The ProductTypeUpdateLocalizedEnumActionUtils and ProductTypeUpdatePlainEnumActionUtils classes are similar in the buildUpdateAction method and someone could argue that I could also reuse the code in the ProductTypeUpdateEnumActionsUtils class, the same as it's done with other methods.

I did it that way first but the code ended up more tight and difficult to understand since all three particular methods for adding, removing and change order of the enums have to be passed as parameters in the common method placed at ProductTypeUpdateEnumActionsUtils, making this method to have 6 parameters where some of them are BiFunction, TriFunction... (long and "complex" signature)

So I finally decided to repeat some code in ProductTypeUpdateLocalizedEnumActionUtils and ProductTypeUpdateLocalizedEnumActionUtils in order to clarify and decouple the buildUpdateAction method of both classes."

In my opinion, that function is too tight and coupled (it's ad-hoc).

First: Passing 6 parameters looks too many parameters for a function (it is a coupling indicator)
Second: It's going to be difficult to extend or modify in the future if a particular change only applies to one of the enum values (either plain or localized).
Third: In some cases is better to "copy" and explicitly show what's going on behind scenes rather than hide and add too much "magic"

What do you think? if you find your way more understandable for future maintainers, I change it :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ok to keep them copied also, I agree with your arguments. It was just an indicator for reuse right away and taking advantage of generics when they look the same. So we don't have to have the exact same code twice. But your arguments are also valid enough, It's fine for now, since it's only going to be reused by only these two. From experience we had some generic methods which maybe had many params but were very helpful to use by any sync process implementation and saved a lot of time (and also the dev didn't need to know about how it's working inside), this was the custom update action builder util method - it was used easily by almost every sync process implementation for example latley in the price custom type sync.

We can leave it like this for now, and see what happens in the future, if we see that we are changing always in both the same things, then we can generify the method.

@heshamMassoud heshamMassoud deleted the 286-product-type-attributes-enum-actions-b branch September 6, 2018 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants