Conversation
|
@maghamed hi it is only basic version I think must also implement a bunching but the code is working some can be optimized. Have you a good example how can I test it? |
There was a problem hiding this comment.
For Big Merchants, who have a lot of SKUs and a lot of Sales channels (scopes in which products would be sold). We want to prevent a multiplication of data in the index table :
SKUs Qty * Number of Sales Channels
Because that could introduce performance impact both on Read and Write index operations.
That's why it's proposed to split indexes by the Scope.
Taking into account that we have 1 - 1 relation between Scope (Sales Channel) and Stock.
We could Build independent index table per each Stock (Stock_id).
Stock Id could be used as Dimension which will help us to resolve correct index we need to refer to get Product Qty.
For example, having 5 websites with ids (1 .. 5)
we will end up creating 5 different index tables (with the same structure):
inventory_stock_item_stock_1
inventory_stock_item_stock_2
inventory_stock_item_stock_3
inventory_stock_item_stock_4
inventory_stock_item_stock_5
You can refer for similar implementation in the CatalogSearch Index
Magento\CatalogSearch\Model\Indexer\Fulltext
Also, we have a requirement to have index aliasing during the full reindex happened, so we need to have switchable indexes to make Front-End responsive while Full Reindex happen.
Also, please take into account that when the SourceItem data change -> we need to upgrade Stock Item Indexes for each of the Stock to which given Source was assigned.
If Source was assigned to 3 stocks, for example, we need to refresh data in the 3 StockItem indexes
Looks like, for this operation we need to introduce new service
Magento\InventoryApi\Api\GetAssignedStocksForSourceInterface::execute($sourceId);
| use \Psr\Log\LoggerInterface; | ||
| use \Magento\Framework\App\ResourceConnection; | ||
|
|
||
| abstract class AbstractAction |
There was a problem hiding this comment.
Please don't introduce AbstractAction class, as Magento doesn't recommend to use Inheritance in newly created code.
Create a dedicated indexer model which would be injected via DI into each of the class:
Row
Rows
Fulland make each of these classes to proxy call to that indexer model
| public function __construct(ResourceConnection $resource, LoggerInterface $logger) | ||
| { | ||
| $this->resource = $resource; | ||
| $this->connection = $resource->getConnection(); |
There was a problem hiding this comment.
it's not recommended to make anything except assignment in the constructors.
Like in your case where you do
$this->connection = $resource->getConnection();
| /** | ||
| * Stock item Indexer @todo | ||
| */ | ||
| class StockItem implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface |
There was a problem hiding this comment.
It's wrong to Make indexer implementing both of these interfaces, because these interfaces responsible for different things. Doing so we violate Single Responsibility Principle.
We need to have two different classes, each one implementing own interface.
Like in CatalogSearch module
https://github.com/magento/magento2/blob/develop/app/code/Magento/CatalogSearch/Model/Indexer/Mview/Action.php
implements \Magento\Framework\Mview\ActionInterface
which is declared in - https://github.com/magento/magento2/blob/develop/app/code/Magento/CatalogSearch/etc/mview.xml#L9
and Magento\CatalogSearch\Model\Indexer\Fulltext implements only \Magento\Framework\Indexer\ActionInterface
which is declared in
https://github.com/magento/magento2/blob/develop/app/code/Magento/CatalogSearch/etc/indexer.xml#L9
Let's do the same way
| $indexItems = $this->fetchIndexItems($sourceItems); | ||
| if (is_array($indexItems) && count($indexItems) > 0) { | ||
| $this->cleanIndexTable($sourceItems); | ||
| $this->updateIndexTable($indexItems); |
There was a problem hiding this comment.
It's better to introduce implementation of IndexerInterface
https://github.com/magento/magento2/blob/develop/lib/internal/Magento/Framework/Indexer/SaveHandler/IndexerInterface.php
and use it for the operations of clean and delete index
|
|
||
| use Magento\InventoryApi\Api\Data\StockItemInterface; | ||
|
|
||
| class CreateStockItemIndexTable |
There was a problem hiding this comment.
This should be implemented similarly to what we have in CatalogSearch
where
Magento\CatalogSearch\Model\Indexer\IndexStructure
responsible for creating StockItem index structure.
IndexStructure implements interface
Magento\Framework\Indexer\IndexStructureInterface
we will reuse this class for index structure creation because we will create dedicated index table for each particular Stock (stock_id). To make the system being scalable horizontally.
As each index table will hold StockItems for each particular Scope (SalesChannel).
For example, having 5 websites each of which represents Sales Channel we will end up with 5 index tables:
inventory_stock_item_index_1 - for website_id 1
inventory_stock_item_index_2 - for website_id 2
inventory_stock_item_index_3 - for website_id 3
inventory_stock_item_index_4 - for website_id 4
inventory_stock_item_index_5 - for website_id 5
| try { | ||
| $this->reindexRows($ids); | ||
| } catch (\Exception $e) { | ||
| throw new \Magento\Framework\Exception\LocalizedException(__($e->getMessage()), $e); |
There was a problem hiding this comment.
it's not correct translation,
__($e->getMessage()), $e);
Because \Exception $e ,so it's not a successor of Localized Exception, thus $e->getMessage()
could return a string for which you don't have precise translation
| try { | ||
| $this->reindexRows([$id]); | ||
| } catch (\Exception $e) { | ||
| throw new \Magento\Framework\Exception\LocalizedException(__($e->getMessage()), $e); |
There was a problem hiding this comment.
it's not correct translation,
__($e->getMessage()), $e);
Because \Exception $e ,so it's not a successor of Localized Exception, thus $e->getMessage()
could return a string for which you don't have precise translation
| * | ||
| * @return void | ||
| */ | ||
| public function execute($id = null) |
There was a problem hiding this comment.
why do we allow to accept NULL value if we throw an exception if NULL passed as a parameter?
| * Interface StockItemInterface | ||
| * @api | ||
| */ | ||
| interface StockItemInterface extends ExtensibleDataInterface |
There was a problem hiding this comment.
For now we don't have a Service API to retrieve Stock Items for a given Stock_ID
That's why for now we can't get StockItems in any case
These API should be introduced to InventoryApi\Api\StockItemManagerInterface
| <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Indexer/etc/indexer.xsd"> | ||
| <indexer id="inventory_stock_item" view_id="inventory_stock_item" class="Magento\Inventory\Model\Indexer\StockItem"> | ||
| <title translate="true">StockItem</title> | ||
| <description translate="true">Index stock item</description> |
There was a problem hiding this comment.
Please provide more clear description
maghamed
left a comment
There was a problem hiding this comment.
In general everything good, @larsroettig please keep going
There was a problem hiding this comment.
no need to make these fields as constants, because constants are the contract which could be accessible outside of the class (like public methods)
it's better to make them private static in this case, so, no one from outside could rely on these values.
And we will not duplicate these data for all instantiated classes.
There was a problem hiding this comment.
it's better to use IndexStructureinterface here and specify in DI Type what exact implementation should use here
There was a problem hiding this comment.
use DI Type configuration for batchSize, especially taking into account that you will use Type for IndexStructure
There was a problem hiding this comment.
hm... it's better to keep this constant away from the concrete implementation, but for now I don't see a place where we could move it
There was a problem hiding this comment.
how can we set these data?
There was a problem hiding this comment.
let's introduce
interface StockItemIndexerInterface extends \Magento\Framework\Indexer\ActionInterface
{
/**
* Indexer ID in configuration
*/
const INDEXER_ID = 'inventory_stock_item_index';
}and make this class to implement this interface.
But Ideally to have getName method in ActionInterface which returned value initialized from the configuration, which we can't add because of BackwardCompatibility
There was a problem hiding this comment.
why do we have StockItemManager which behaves like DataInterface,
Manager means to make some actions over some data.
There was a problem hiding this comment.
why do we need this DataInterface?
it duplicated StockItemInterface, but called manager and never used
looks like a mistake
There was a problem hiding this comment.
Pls delete or add more description to @todo annotation
There was a problem hiding this comment.
Need to more clear name?
What is mean data?
There was a problem hiding this comment.
As for me *Manager is not good name for "entity"
Maybe it is better simple "StockItem"
But pay attention we already have \Magento\InventoryApi\Api\Data\StockItemInterface
There was a problem hiding this comment.
visa versa
Get Stocks assigned to Source
There was a problem hiding this comment.
Do we need to process disabling/enabling of products?
There was a problem hiding this comment.
Please update doc block.
There was a problem hiding this comment.
Please describe constants.
More info is available in the devdocs
There was a problem hiding this comment.
Please add a type hint
There was a problem hiding this comment.
Please add a type hint
There was a problem hiding this comment.
It's better to have a separate namespace for Indexer. Not to put all indexers to
Magento\Inventory\Model\*
Let's create dedicated namespace like
Magento\Inventory\Indexer\
There was a problem hiding this comment.
In the documentation https://github.com/magento-engcom/magento2/wiki/New-Indexer-for-StockItems#index-dimensions stated
That's why it's proposed to split indexes by the Scope. Taking into account that we have 1 - 1 relation between Scope (Sales Channel) and Stock
For example, having 5 websites with ids (1 .. 5) we will end up creating 5 different index tables (with the same structure):
inventory_stock_item_stock_1
inventory_stock_item_stock_2
inventory_stock_item_stock_3
inventory_stock_item_stock_4
inventory_stock_item_stock_5
Current code where dimension is StoreId is copy-pasted from Search Indexer
There was a problem hiding this comment.
we already have
\Magento\Framework\Search\Request\Dimension
why do we create own Dimension object?
There was a problem hiding this comment.
Pls provide more clear class name
class name is proxy, class interface is resolver, due to code it is factory... it is not clear what is responsibility of this class
There was a problem hiding this comment.
Do we need to create a new object each time?
There was a problem hiding this comment.
This logic is better to place in State object. In this way, we don't provide possibility to set the wrong state
But if we look code of State object, we can not find possibility to set the wrong state.
In State objectwWe have only useTemporaryIndex and useRegularIndex methods, thus we can not set state different from these two.
So this checking looks like redundant
There was a problem hiding this comment.
agree with @naydav in the scope of UnknownStateException I described the idea of State Machine, so don't think that we need to check the state and have a factory for state creation
There was a problem hiding this comment.
I understand that is taken from search index but I don't like naming (TemporaryResolver, IndexScopeResolver, ScopeProxy)
@maghamed what do you think about this naming?
There was a problem hiding this comment.
@naydav makes sense, naming should be aligned to outline the best practices and make it understandable for external developers what's the responsibility of the class reading its name.
There was a problem hiding this comment.
Need to think about moving this exception to framework lib if we will use it
There was a problem hiding this comment.
@naydav agree, already pointed that out for IndexTableNotExistException.
These should be framework level exceptions, we can't provide them to client because they provide details of how this functionality is implemented (leaky abstractions).
So, these exceptions are not successors of Localized
There was a problem hiding this comment.
Btw, I don't know why we have this Exception in our codebase.
So, from what I see we have a pre-defined finite list of states which could be switched in pre-defined order. (aka State machine design pattern)
- our state object initially being initialized in 'REGULAR' state.
- after that, we've decided to move it to 'TEMPORARY' state while making re-indexation
- after re-indexation complete - we move the object to 'REGULAR' state again.
Is it correct? I suppose so, then why do we have a possibility that State object would be created in a wrong state?
@larsroettig
There was a problem hiding this comment.
Maybe it is better to use one fixture with all preconditions
There was a problem hiding this comment.
Why do you create an instance of repository each time?
There was a problem hiding this comment.
You can reuse fixture from catalog module
There was a problem hiding this comment.
We already have this fixture
app/code/Magento/InventoryApi/Test/_files/source/source_list.php
There was a problem hiding this comment.
app/code/Magento/InventoryApi/Test/_files/source/stock_list.php
- Add Setup for index table - Add new Interface"
- Refactoring to new Setup structure - Implement SourceItem add entry to di.xml - WIP Register Basic Indexer SourceItem
- Impelemnt basic import funktion TODOS: - Add some bunching function for index - Add Mysql Index to inventory_stock_item_index table - Documentation and clean up
- Clean Up and implement basic structure
- Impelemnt basic import function TODOS: - Index switch from tmp to idx table - Documentation and clean up
- Add Setup for index table - Add new Interface I magento-engcom/magento#48: - Add Setup for index table - Add new Interface
- Implement switch for Index TODO: - Clean Up and more Doc - Discuss the missing website to stock mapping
- Impelemnt Basic intigration test fixtures
- Bugfix for di.xml - Add basic call for reindex full
- Basic Indexer with switch for tmp and normal index TODO: - Refactoring to remove the state folder from the module - Doc blocks and clean up - Add assertions to the test case
-- fixes after merge
23864df to
31e5df0
Compare
|
|
maghamed
left a comment
There was a problem hiding this comment.
@naydav please help @larsroettig
and join this story development.
It's very sophisticated Story, and there are no good examples in Magento where re-indexation was implemented totally correct.
So, let's make a good example of introducing New Index to the Magento 2. Outlining all the steps and document them afterwards.
We already agreed that ActiveTableSwitcher would be moved to Framework maybe we need some additional things to move to the framework level?
| * @todo refactoring it copy from catalog search module | ||
| * @api | ||
| */ | ||
| class IndexTableNotExistException extends LocalizedException |
There was a problem hiding this comment.
If we are introducing IndexTableNotExistException, this exception should be low-level (framework level).
Not inherited from LocalizedException.
Because we can't provide this Exception o the client code, even its name is a leaky abstraction of the internal details.
| @@ -0,0 +1,24 @@ | |||
| <?php | |||
There was a problem hiding this comment.
In Magento there is another Switcher mechanism - https://github.com/magento-engcom/magento2/blob/develop/app/code/Magento/Catalog/Model/ResourceModel/Indexer/ActiveTableSwitcher.php
by our request, it would be moved to the Framework soon (in the scope of Magento 2.2.0 regression).
So, we could consider using ActiveTableSwitcher for our purposes.
@naydav please consider this and provide a solution for @larsroettig
maybe we need some additional classes to be moved to the Framework?
There was a problem hiding this comment.
@naydav makes sense, naming should be aligned to outline the best practices and make it understandable for external developers what's the responsibility of the class reading its name.
There was a problem hiding this comment.
@naydav agree, already pointed that out for IndexTableNotExistException.
These should be framework level exceptions, we can't provide them to client because they provide details of how this functionality is implemented (leaky abstractions).
So, these exceptions are not successors of Localized
There was a problem hiding this comment.
Btw, I don't know why we have this Exception in our codebase.
So, from what I see we have a pre-defined finite list of states which could be switched in pre-defined order. (aka State machine design pattern)
- our state object initially being initialized in 'REGULAR' state.
- after that, we've decided to move it to 'TEMPORARY' state while making re-indexation
- after re-indexation complete - we move the object to 'REGULAR' state again.
Is it correct? I suppose so, then why do we have a possibility that State object would be created in a wrong state?
@larsroettig
There was a problem hiding this comment.
agree with @naydav in the scope of UnknownStateException I described the idea of State Machine, so don't think that we need to check the state and have a factory for state creation
- Add test for index row
# Conflicts: # app/code/Magento/Inventory/etc/di.xml
- refactoring
- refactoring
- refactoring
- refactoring
- refactoring
- refactoring
- refactoring
- refactoring
- refactoring
- fixes
- fixes
- fix static tests - update db structure
- fix static tests
|
Internal build
|
|
Approved to be merged into the Develop branch Great job guys @larsroettig @naydav , well done! |
ACP2E-3863: [Bundle 12] Update copyrights for MSI and fix static errors.
No description provided.