Skip to content
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

Introduce single unified UpdateProductCommand #30031

Merged
merged 21 commits into from Oct 26, 2022

Conversation

zuk3975
Copy link
Contributor

@zuk3975 zuk3975 commented Oct 17, 2022

Questions Answers
Branch? develop
Description? related POC's #29999, #30009, #30025. Current approach having multiple CQRS commands for product update (commands like UpdateProductBasicInformationCommand, UpdateProductPricesCommand, UpdateProductOptionsCommand, UpdateProductDetailsCommand, UpdateProductSeoCommand) has some flaws - user saves product once but it is actually fetched and saved couple times and the product update hooks fires multiple times too. This PR introduces single command for all these general properties update (except for all the association commands like setting categories, suppliers, features etc.). Integrated 2 commands for now: basic information and options. The new product_commands_builder (which builds the new command) tags are commented out, so it doesn't affect the product page yet. This approach should eventually replace the currently existing commands, but for now we are still keeping the old multi-command approach working in product page until this new one is fully implemented.
Type? refacto
Category? BO
BC breaks? no
Deprecations? no
Fixed ticket?
Related PRs
How to test? see bellow
Possible impacts?

How to test
Locally it can be tested by uncomenting tags of product_commands_builder (in Resources/config/services/core/form/command_builder.yml prestashop.core.form.command_builder.product.update_product_commands_builder) and making sure that the UpdateProductCommand appears in the list of generated commands in Core/Form/IdentifiableObject/DataHandler/ProductFormDataHandler::update(). Also related behat scenarios can be run using these commands in terminal:

  • ./vendor/bin/behat -c tests/Integration/Behaviour/behat.yml -s update_product --tags update-basic-information
  • ./vendor/bin/behat -c tests/Integration/Behaviour/behat.yml -s update_product --tags update-multi-shop-basic-information
  • ./vendor/bin/behat -c tests/Integration/Behaviour/behat.yml -s update_product --tags update-options
  • ./vendor/bin/behat -c tests/Integration/Behaviour/behat.yml -s update_product --tags update-multi-shop-options

Copy link
Contributor

@jolelievre jolelievre left a comment

Choose a reason for hiding this comment

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

Thanks @zuk3975

A few comments but most of them are about naming and namespaces (and suggestions for additional tests ^^), but the generic principle of the whole PR seems good to me!

use PrestaShop\PrestaShop\Core\Domain\Product\Command\UpdateProductCommand;
use Product;

class ProductUpdatablePropertyFiller implements ProductUpdatablePropertyFillerInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

You should add a description for this class explaining how it handles all the sub fillers

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm thinking the naming is a bit too long maybe (same for the interface and related variables), maybe ProductFiller and ProductFillerInterface::fillProperties would be enough?

Copy link
Contributor Author

@zuk3975 zuk3975 Oct 18, 2022

Choose a reason for hiding this comment

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

I would still like to emphasize that it returns the properties for update 🤔
So either a class or a method could mention the updatable props. (maybe the method?). Or you think thats not necessary?

use PrestaShop\PrestaShop\Core\Domain\Product\Command\UpdateProductCommand;
use Product;

class ProductOptionsPropertyFiller implements ProductUpdatablePropertyFillerInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add descriptions on all the new class, even a simple one at least explaining which sub domain this filler handles

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, I think these classes should be unit tested, their responsibility is quite simple and scope:

  • fill the fields
  • return array indicating which fields have been update

This makes them perfect candidates for unit testing, the two you handled don't have that many specific rules (although link_rewrite is not that intuitive) but other like SEO and Prices sub domains have some twisted rules sometimes which would be worth testing to avoid regressions.

It may be a bit in doublons with the behat tests, but it's not the same thing. Behat tests will validate the behaviour, while unit tests here would test our implementation.

$product,
$command,
[
'available_for_order',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ping @jolelievre Idk if we should care to check if new value from command changes the previous one?
bcuz thats why this test case seems abit strange (by default $product->show_price and $product->available_for_order is true. So when we set AvailableForOrder to true, we are still updating the field (even though we are updating it with the same value). Same happens with other fields, its just more visible with show_price and available_for_order, because the $show_price value is the only one that is not updated if its not changed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I understand everything 😅 we should discuss this live

Copy link
Contributor

@jolelievre jolelievre left a comment

Choose a reason for hiding this comment

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

Thanks, @zuk3975

The whole architecture is fine for me, the only comments are about naming, we should discuss this live after next standup it'll be more efficient than via review comments

@@ -397,7 +397,7 @@ public static function initialize()
);

$isAllShop = 'all' === $id_shop;
$isApiInUse = defined('_PS_API_IN_USE_') && _PS_API_IN_USE_;
$isApiInUse = defined('_PS_API_IN_USE_') && _PS_API_IN_USE_ === true;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure why this was needed since it's only use for Webservice context and the only value it can have so far true anyway

define('_PS_API_IN_USE_', true);

Is it really in your PR's scope?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh, no I fixed it in another one. Forgot here, thanks 👍

/**
* Fills product properties which can be considered as a basic product information
*/
class ProductBasicInformationPropertyFiller implements ProductUpdatablePropertyFillerInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
class ProductBasicInformationPropertyFiller implements ProductUpdatablePropertyFillerInterface
class BasicInformationFiller implements ProductFillerInterface

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm thinking of ways to reduce all the namings which are quite long:

  • the method name in the interface is already fillUpdatableProperties so the UpdateableProperty part can be removed in class names and interface
  • since we already are in product namespace having product prefix everywhere is a bit redundant (although I kept it in ProductFillterInterface to avoid confusion with future potential FillerInterface used for other entites

/**
* Fills product properties which can be considered as product options
*/
class ProductOptionsPropertyFiller implements ProductUpdatablePropertyFillerInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
class ProductOptionsPropertyFiller implements ProductUpdatablePropertyFillerInterface
class OptionsFiller implements ProductFillerInterface

*
* All the internal property fillers are split just to contain less code and be more readable (because the Product contains many of properties).
*/
class ProductUpdatablePropertyFiller implements ProductUpdatablePropertyFillerInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
class ProductUpdatablePropertyFiller implements ProductUpdatablePropertyFillerInterface
class ProductFiller implements ProductFillerInterface

/**
* Responsible for filling up the Product with the properties which have to be updated
*/
interface ProductUpdatablePropertyFillerInterface
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
interface ProductUpdatablePropertyFillerInterface
interface ProductFillerInterface

prestashop.adapter.product.update.product_updatable_property_filler:
class: PrestaShop\PrestaShop\Adapter\Product\Update\ProductUpdatablePropertyFiller
arguments:
- !tagged core.product_updatable_property_filler
Copy link
Contributor

Choose a reason for hiding this comment

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

I made some other comments regarding the interface naming, this tag renaming would make even more sense if the interface is renamed

use PrestaShop\PrestaShop\Adapter\Product\Update\Filler\ProductUpdatablePropertyFillerInterface;
use PrestaShop\PrestaShop\Adapter\Tools;

class ProductBasicInformationPropertyFillerTest extends PropertyFillerTestCase
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
class ProductBasicInformationPropertyFillerTest extends PropertyFillerTestCase
class BasicInformationFillerTest extends ProductFillerTestCase

use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopConstraint;
use Product;

abstract class PropertyFillerTestCase extends TestCase
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
abstract class PropertyFillerTestCase extends TestCase
abstract class ProductFillerTestCase extends TestCase

Comment on lines 67 to 69
abstract public function getDataForTestFillsUpdatableProperties(): iterable;

abstract public function getFiller(): ProductUpdatablePropertyFillerInterface;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
abstract public function getDataForTestFillsUpdatableProperties(): iterable;
abstract public function getFiller(): ProductUpdatablePropertyFillerInterface;
abstract protected function getDataForTestFillsUpdatableProperties(): iterable;
abstract protected function getFiller(): ProductUpdatablePropertyFillerInterface;

These two could and should be protected no? Maybe getDataForTestFillsUpdatableProperties is a bit different since a data provider so I understood we may be forced to keep it public But for getFiller it seems we don't need to have it public, or am I missing something?

$product,
$command,
[
'available_for_order',
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I understand everything 😅 we should discuss this live

Copy link
Contributor

@jolelievre jolelievre left a comment

Choose a reason for hiding this comment

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

Thanks @zuk3975

prestashop.adapter.product.update.filler.product_filler:
class: PrestaShop\PrestaShop\Adapter\Product\Update\Filler\ProductFiller
arguments:
- !tagged core.product_filler
Copy link
Member

Choose a reason for hiding this comment

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

I Think the tag name should start by prestashop (like prestashop.core.product_filler)

Copy link
Contributor Author

@zuk3975 zuk3975 Oct 25, 2022

Choose a reason for hiding this comment

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

I was insipired by core.product_command_builder so I didn't think too much.
I think its a question to @jolelievre (I see other older services using prestashop prefix like prestashop.core.command_bus) maybe indeed we shoul rename these 2 new concepts with the prestahop prefix too?

Copy link
Contributor

Choose a reason for hiding this comment

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

When we use core it implicitly relates to prestashop core, but I guess we can be more explicit on this one I don't mind you can replace it if you prefer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't care either, so as its not a blocker I prefer not to change anything now. But we can still do it when migrating other commands if decided

/**
* Fills product properties which can be considered as a basic product information
*/
class BasicInformationFiller implements ProductFillerInterface
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand the whole point of having *FillerInterface files and classes.
Why don't we fetch the product and map data inside ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

purely just to keep them small I guess. Cuz we will have like 5-6 of those classes (and we already had all the logic taken from dedicated command handlers that used it before).
Do you think its an overengineering?

Copy link
Contributor

Choose a reason for hiding this comment

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

The idea was indeed to avoid having one mega filler with 4000 lines of code, and to be able to work on remaining pieces more easily in multiple PRs. But maybe at the end this should be all done inside one, we could refacto this and merge them all into one We can discuss this tomorrow I guess

@jolelievre
Copy link
Contributor

Thanks @zuk3975

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
develop Branch migration symfony migration project Refactoring Type: Refactoring
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants