diff --git a/SUMMARY.md b/SUMMARY.md index 8624f3bb9..33cba4fad 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -452,6 +452,7 @@ * [Tax providers](resources/references/adr/2022-04-28-tax-providers.md) * [Remove static analysis with psalm](resources/references/adr/2022-05-12-remove-static-analysis-with-psalm.md) * [Rule condition field abstraction](resources/references/adr/2022-05-23-rule-condition-field-abstraction.md) + * [Integrate app into flow event](resources/references/adr/2022-06-17-integrate-app-into-flow-event.md) * [Add typescript support for storefront js](resources/references/adr/2022-06-24-add-typescript-support-for-storefront-js.md) * [Providing the admin extension sdk](resources/references/adr/2022-06-27-providing-the-admin-extension-sdk.md) * [Blog concept](resources/references/adr/2022-07-19-blog-concept.md) @@ -478,7 +479,30 @@ * [Admin text editor evaluation](resources/references/adr/2023-03-27-admin-text-editor-evaluation.md) * [Mocking repositories](resources/references/adr/2023-04-01-mocking-repositories.md) * [Disable css autoprefixer](resources/references/adr/2023-04-03-disable-css-autoprefixer.md) + * [New language inheritance mechanism for opensearch](resources/references/adr/2023-04-11-new-language-inheritance-mechanism-for-opensearch.md) * [Jest test files should be javascript only](resources/references/adr/2023-04-14-jest-test-files-should-be-javascript-only.md) + * [Optimise cart cleanup](resources/references/adr/2023-05-09-optimise-cart-cleanup.md) + * [Experimental features](resources/references/adr/2023-05-10-experimental-features.md) + * [Stock API](resources/references/adr/2023-05-15-stock-api.md) + * [Php enums](resources/references/adr/2023-05-16-php-enums.md) + * [Symfony dependency management](resources/references/adr/2023-05-16-symfony-dependency-management.md) + * [Switch to uuidv7](resources/references/adr/2023-05-22-switch-to-uuidv7.md) + * [Exception log levels](resources/references/adr/2023-05-25-exception-log-levels.md) + * [Store API to app server](resources/references/adr/2023-06-27-store-api-to-app-server.md) + * [Default handle for non specified salutations](resources/references/adr/2023-06-28-default-handle-for-non-specified-salutations.md) + * [Flow builder preview](resources/references/adr/2023-07-13-flow-builder-preview.md) + * [Collecting entity data](resources/references/adr/2023-08-03-collecting-entity-data.md) + * [Media path](resources/references/adr/2023-08-17-media-path.md) + * [Post updater](resources/references/adr/2023-08-27-post-updater.md) + * [Feature property for experimental anotation](resources/references/adr/2023-09-06-feature-property-for-experimental-anotation.md) + * [Catalog import API](resources/references/adr/2023-09-22-catalog-import-api.md) + * [Add unique identifiers for checkout methods](resources/references/adr/2023-10-17-add-unique-identifiers-for-checkout-methods.md) + * [Bootstrap css utils](resources/references/adr/2023-10-19-bootstrap-css-utils.md) + * [Toggle feature flag on demand](resources/references/adr/2023-11-29-toggle-feature-flag-on-demand.md) + * [Acceptance test suite](resources/references/adr/2023-12-12-acceptance-test-suite.md) + * [Transactional flow actions](resources/references/adr/2024-02-11-transactional-flow-actions.md) + * [Disable vue compat mode per component level](resources/references/adr/2024-03-11-disable-vue-compat-mode-per-component-level.md) + * [Implementation of meteor component library](resources/references/adr/2024-03-21-implementation-of-meteor-component-library.md) * [YYYY MM DD template](resources/references/adr/YYYY-MM-DD-template.md) * [App Reference](resources/references/app-reference/README.md) diff --git a/resources/references/adr/2020-06-25-implement-architecture-decision-records.md b/resources/references/adr/2020-06-25-implement-architecture-decision-records.md index 0e0855661..10cb39261 100644 --- a/resources/references/adr/2020-06-25-implement-architecture-decision-records.md +++ b/resources/references/adr/2020-06-25-implement-architecture-decision-records.md @@ -3,7 +3,7 @@ title: Implement architecture decision records date: 2020-06-25 area: core tags: [adr, workflow] ---- +--- # Implement architecture decision records @@ -53,7 +53,7 @@ The ADRs are markdown files inside the platform repository, located in the "adr" * One developer must be a member of the core development team * One developer must be a member of a team, other than the team of the creator * One product owner or higher role has to approve an ADR -** This part of the decision is superseded by [2021-11-05 - Adjust ADR approval rules for the new org structure](2021-11-05-adjust-ADR-approval-rules.md), but the rest of this ADR is untouched.** +** This part of the decision is superseded by [2021-11-05 - Adjust ADR approval rules for the new org structure](2021-11-05-adjust-adr-approval-rules.md), but the rest of this ADR is untouched.** **Should counter decisions also be documented?** Not specific, but if there is more than one possible solution, all options should be outlined. @@ -73,4 +73,3 @@ The status of an ADR is symbolized by the directory. All ADR located in the main **Can an ADR be changed?** When an ADR is accepted and merged in to the code, it can no longer be changed. If a decision is outdated or has to be changed, the ADR has to be superseded by a new ADR. Superseded ADRs have to be moved to the `/adr/_superseded` directory. - diff --git a/resources/references/adr/2020-07-02-control-clone-behavior.md b/resources/references/adr/2020-07-02-control-clone-behavior.md index 3c06a512b..c7c20303e 100644 --- a/resources/references/adr/2020-07-02-control-clone-behavior.md +++ b/resources/references/adr/2020-07-02-control-clone-behavior.md @@ -3,7 +3,7 @@ title: Get control of association clone behavior as developer date: 2020-07-02 area: core tags: [repository, entity, clone, flag, association] ---- +--- # Get control of association clone behavior as developer diff --git a/resources/references/adr/2020-07-02-implement-sales-channel-context-token-requirement.md b/resources/references/adr/2020-07-02-implement-sales-channel-context-token-requirement.md index 11b03ec62..15af05577 100644 --- a/resources/references/adr/2020-07-02-implement-sales-channel-context-token-requirement.md +++ b/resources/references/adr/2020-07-02-implement-sales-channel-context-token-requirement.md @@ -3,7 +3,7 @@ title: Implement sales channel context token requirement for store-api and sales date: 2020-07-02 area: core tags: [context, token, sales-channel, store-api, sales-channel-api, api] ---- +--- # Implement sales channel context token requirement for store-api and sales-channel-api diff --git a/resources/references/adr/2020-08-03-implement-new-changelog.md b/resources/references/adr/2020-08-03-implement-new-changelog.md index 290086b29..7569d6379 100644 --- a/resources/references/adr/2020-08-03-implement-new-changelog.md +++ b/resources/references/adr/2020-08-03-implement-new-changelog.md @@ -3,7 +3,7 @@ title: Implement new changelog date: 2020-08-03 area: core tags: [changelog] ---- +--- # Implement new changelog @@ -118,4 +118,3 @@ If you just want to get the changes of a specific version or even just a special `bin/console changelog:change [] [options]` Use the `--help` flag of the command to see all possible options. - diff --git a/resources/references/adr/2020-08-12-document-template-refactoring.md b/resources/references/adr/2020-08-12-document-template-refactoring.md index 0b27a6989..7445119dc 100644 --- a/resources/references/adr/2020-08-12-document-template-refactoring.md +++ b/resources/references/adr/2020-08-12-document-template-refactoring.md @@ -3,7 +3,7 @@ title: Document template refactoring date: 2020-08-12 area: customer-order tags: [document, template, twig] ---- +--- # Document template refactoring diff --git a/resources/references/adr/2020-08-12-implement-app-system-inside-platform.md b/resources/references/adr/2020-08-12-implement-app-system-inside-platform.md index 58be64133..ff08c51df 100644 --- a/resources/references/adr/2020-08-12-implement-app-system-inside-platform.md +++ b/resources/references/adr/2020-08-12-implement-app-system-inside-platform.md @@ -3,7 +3,7 @@ title: Implement app system inside platform date: 2020-08-12 area: core tags: [app, cloud] ---- +--- # Implement app system inside platform @@ -56,7 +56,7 @@ Additionally, the storefront theme files (JS and CSS) only need to be accessed d * [Custom Fields](https://docs.shopware.com/en/shopware-platform-dev-en/app-system-guide/app-base-guide?category=shopware-platform-dev-en/app-system-guide#custom-fields): An app can register it's own custom fields sets, that are displayed along the other custom fields inside the administration. -* [Storefront Customizations](https://docs.shopware.com/en/shopware-platform-dev-en/app-system-guide/app-examples-and-tutorials/create-own-theme?category=shopware-platform-dev-en/app-system-guide/app-examples-and-tutorials): An app should be able to customize the storefront in the same way a plugin does. This includes the theme system, custom twig templates and custom JS and CSS. +* [Storefront Customizations](https://developer.shopware.com/docs/guides/plugins/apps/storefront/): An app should be able to customize the storefront in the same way a plugin does. This includes the theme system, custom twig templates and custom JS and CSS. In regard to the theme system apps are treated the same way as plugins are, especially regarding the theme inheritance. Apps can be explicitly set in the inheritance chain via `@TechnicalAppName`, if they are not referenced directly they are part of the fallback `@Plugins` namespace. Extension points may be added as new features of the app system, but we have to make sure that it does not violate one of the limitations mentioned above. Additionally, it needs to be taken into account that it's possible to deploy and run that feature in the cloud environment. diff --git a/resources/references/adr/2020-08-14-implement-individual-sorting.md b/resources/references/adr/2020-08-14-implement-individual-sorting.md index 4b5387ba8..4e47cffc4 100644 --- a/resources/references/adr/2020-08-14-implement-individual-sorting.md +++ b/resources/references/adr/2020-08-14-implement-individual-sorting.md @@ -3,7 +3,7 @@ title: Implement individual sorting date: 2020-08-14 area: core tags: [repository, dal, entity, sort, product] ---- +--- # Implement individual sorting diff --git a/resources/references/adr/2020-08-14-merchant-registration.md b/resources/references/adr/2020-08-14-merchant-registration.md index 2250cd7ac..e887996b9 100644 --- a/resources/references/adr/2020-08-14-merchant-registration.md +++ b/resources/references/adr/2020-08-14-merchant-registration.md @@ -3,7 +3,7 @@ title: Merchant registration date: 2020-08-14 area: customer-order tags: [merchant, registration, customer-group] ---- +--- # Merchant registration diff --git a/resources/references/adr/2020-08-21-unified-notification-titles.md b/resources/references/adr/2020-08-21-unified-notification-titles.md index 7346b8df5..5d95af2f3 100644 --- a/resources/references/adr/2020-08-21-unified-notification-titles.md +++ b/resources/references/adr/2020-08-21-unified-notification-titles.md @@ -3,7 +3,7 @@ title: Notification titles are pre-defined and make use of the global namespace date: 2020-08-21 area: administration tags: [administration, notification] ---- +--- # Notification titles are pre-defined and make use of the global namespace @@ -14,8 +14,8 @@ You can find the original version [here](https://github.com/shopware/platform/bl ## Context -* Creating notifications messages in the administration caused the effort of making up not only a title but a message too. -This has led to inconsistent notification appearances. In some cases the notification message simply duplicated the title, +* Creating notification messages in the administration caused the effort of making up not only a title but a message too. +This has led to inconsistent notification appearances. In some cases, the notification message simply duplicated the title; others wore the module's name as a title and so on. * Now, since it is a set design decision to use the following four types of notification as titles at the same time, @@ -39,7 +39,7 @@ it is just logical to make use of the global namespace and manage notification t * By introducing the global namespace as early as in the `notification.mixin.js` it is now unnecessary to define individual titles when implementing notifications within a module. * Notifications from now on only require a "notification message" and thus the creation of only snippet within each snippet file (en-GB and de-DE). -* Consequently a whole bunch of unnused snippets have been removed. +* Consequently, a bunch of unused snippets have been removed. For more information on snippets deleted in this course see CHANGELOG-6.3.md ### Examples @@ -66,7 +66,7 @@ this.createNotificationError({ * Messages should be translatable, precise and not redundant. An error notification's title literally says: "Error" - no need in repeating that. Better find and present information on what exactly went wrong. -* Make use of success notifications, but make them carry useful information, by e.g. including counters. +* Make use of success notifications, but make them carry useful information, by e.g., including counters. * Make use of info and warning notifications to keep users informed about things that are ongoing or need a closer look! @@ -78,6 +78,3 @@ It would cross the design idea of unified titles though and should only be consi > *When creating notifications, just decide on the correct type of notification, add a meaningful message, don't waste even a thought on creating a title... And you're done!* - - - diff --git a/resources/references/adr/2020-08-28-import-acl-privileges-from-other-roles.md b/resources/references/adr/2020-08-28-import-acl-privileges-from-other-roles.md index bd943fe4e..de34dd306 100644 --- a/resources/references/adr/2020-08-28-import-acl-privileges-from-other-roles.md +++ b/resources/references/adr/2020-08-28-import-acl-privileges-from-other-roles.md @@ -3,7 +3,7 @@ title: Import ACL privileges from other roles date: 2020-08-28 area: administration tags: [acl, privileges, dependency, administration] ---- +--- # Import ACL privileges from other roles diff --git a/resources/references/adr/2020-09-08-custom-field-label-loading-in-storefront.md b/resources/references/adr/2020-09-08-custom-field-label-loading-in-storefront.md index f62c8b5f8..9ed2bfd32 100644 --- a/resources/references/adr/2020-09-08-custom-field-label-loading-in-storefront.md +++ b/resources/references/adr/2020-09-08-custom-field-label-loading-in-storefront.md @@ -3,7 +3,7 @@ title: CustomField label loading in storefront date: 2020-09-08 area: storefront tags: [custom-fields, storefront, snippets] ---- +--- # CustomField label loading in storefront diff --git a/resources/references/adr/2020-09-17-the-best-practice-to-always-re-fetch-the-data-after-saving.md b/resources/references/adr/2020-09-17-the-best-practice-to-always-re-fetch-the-data-after-saving.md index d6beb5ed2..c6fe77631 100644 --- a/resources/references/adr/2020-09-17-the-best-practice-to-always-re-fetch-the-data-after-saving.md +++ b/resources/references/adr/2020-09-17-the-best-practice-to-always-re-fetch-the-data-after-saving.md @@ -3,7 +3,7 @@ title: The best-practice to always re-fetch the data after saving date: 2020-09-17 area: administration tags: [administration, data-handling] ---- +--- # The best-practice to always re-fetch the data after saving diff --git a/resources/references/adr/2020-11-19-dal-join-filter.md b/resources/references/adr/2020-11-19-dal-join-filter.md index 230ac57d5..fb80f7f9c 100644 --- a/resources/references/adr/2020-11-19-dal-join-filter.md +++ b/resources/references/adr/2020-11-19-dal-join-filter.md @@ -3,7 +3,7 @@ title: DAL join filter date: 2020-11-19 area: core tags: [dal, join-filter, negated-filter, criteria] ---- +--- # DAL join filter diff --git a/resources/references/adr/2020-11-20-add-login-required-annotation.md b/resources/references/adr/2020-11-20-add-login-required-annotation.md index 7c06aa4e2..8ef89f8b9 100644 --- a/resources/references/adr/2020-11-20-add-login-required-annotation.md +++ b/resources/references/adr/2020-11-20-add-login-required-annotation.md @@ -3,7 +3,7 @@ title: Add the login required annotation date: 2020-11-20 area: core tags: [annotation, login, customer, api, store-api] ---- +--- # Add the login required annotation diff --git a/resources/references/adr/2020-11-25-decoration-pattern.md b/resources/references/adr/2020-11-25-decoration-pattern.md index 2033a5681..ae571c127 100644 --- a/resources/references/adr/2020-11-25-decoration-pattern.md +++ b/resources/references/adr/2020-11-25-decoration-pattern.md @@ -3,7 +3,7 @@ title: Decoration pattern date: 2020-11-25 area: core tags: [decoration, plugin, interface, abstraction] ---- +--- # Decoration pattern diff --git a/resources/references/adr/2020-12-02-removing-api-version.md b/resources/references/adr/2020-12-02-removing-api-version.md index 587b58263..9b5e2c33b 100644 --- a/resources/references/adr/2020-12-02-removing-api-version.md +++ b/resources/references/adr/2020-12-02-removing-api-version.md @@ -3,7 +3,7 @@ title: API version removal date: 2020-12-02 area: core tags: [api, versioning, deprecation] ---- +--- # API version removal diff --git a/resources/references/adr/2021-01-21-deprecation-strategy.md b/resources/references/adr/2021-01-21-deprecation-strategy.md index 9d9fa8413..03ff26474 100644 --- a/resources/references/adr/2021-01-21-deprecation-strategy.md +++ b/resources/references/adr/2021-01-21-deprecation-strategy.md @@ -21,7 +21,7 @@ Define a strategy for deprecations. ## Decision ### Dogma -* Don't do changes without feature-flags (only exception are bugfixes) +* Don't do changes without feature-flags (only exception is a bugfix) * Don't break things without an alternative * Don't break things in a minor release * Annotate upcoming breaks as soon as possible @@ -31,31 +31,31 @@ Define a strategy for deprecations. ### Synopsys -As we decided to work in the trunk based development from now on, there are different kinds of cases we need to consider while implementing changes to not cause any breaks while developing for future features. -The main difference we have to take in account, is if we break currently behaviour with our changes or not. -For this difference we have 4 different cases: +As we decided to work on the trunk-based development from now on, there are different kinds of cases we need to consider while implementing changes to not cause any breaks while developing for future features. +The main difference we have to take in account is if we break current behaviour with our changes or not. +For this difference, we have 4 different cases: * Minor Changes which don't cause any breaks or deprecations * Minor Changes which cause deprecations * Minor Changes as part of a major feature which don't cause any breaks * Major changes which cause breaks -For a quick overview this is how you have to deal with the different cases. +For a quick overview, this is how you have to deal with the different cases. Concrete Examples and further explanation follow below. #### Only Minor Changes (no breaks) -Feature and changes tend to be released in a minor release. Don't cause breaks. Simple additions, refactorings, etc +Features and changes tend to be released in a minor release. Don't cause breaks. Simple additions, refactorings, etc * Put all your changes behind a feature flag, to be sure that nothing you have changed is called while developing is in progress. * When Development is completed, remove the feature flag and all the old code that is not used anymore -* Detailed description here [Detailed Rules](DetailedRules) +* Detailed description here [Detailed Rules](#detailed-rules) #### Only Minor Changes (with deprecating code) -Feature and Changes tend to be released in a minor release and are developed in a backward compatible manner, but deprecate old code. For example a class is replaced by a new one. +Features and Changes tend to be released in a minor release and are developed in a backward compatible manner, but deprecate old code. For example, a class is replaced by a new one. * Put all your changes behind a feature flag, to be sure that nothing you have changed is called while developing is in progress. * When Development is completed, remove the feature flag and all the old code that is not used anymore * Mark old code as deprecated and make sure it is not called anywhere else * Make sure everything you removed has a working alternative implemented. * Annotate everything in a manner that the removal of the deprecated code will be a no-brainer on the next major release -* Detailed description here [Detailed Rules](DetailedRules) +* Detailed description here [Detailed Rules](#detailed-rules) #### Major Changes (Breaks) Parts of a major feature or refactoring which breaks current behaviour. Removal of classes, methods or properties, change of signatures, business logic changes... @@ -64,14 +64,14 @@ Parts of a major feature or refactoring which breaks current behaviour. Removal * Mark old code as deprecated and make sure it is not called anywhere else * Make sure everything you removed has a working alternative implemented. * Annotate everything in a manner that the removal of the deprecated code will be a no-brainer on the next major release -* only difference between the case above is, that you have to take care about the fact, that the whole old behaviour needs to be fully functional until the next major. +* The only difference between the case above is that you have to take care of the fact that the whole old behaviour needs to be fully functional until the next major. * Write specific tests for the major flag which tests the new behaviour. -* Detailed description here [Detailed Rules](DetailedRules) +* Detailed description here [Detailed Rules](#detailed-rules) ## Summary Deprecations: * The old code [^3] (class/method/property/event...) will be annotated with @feature-deprecated or @major-deprecated depending on the fact if this is a breaking change or not. - * There will be parts of the changes which cause breaks and parts which don't cause breaks. Following we will call these parts "breaking code" [^4] and "non breaking code" [^5] + * There will be parts of the changes which cause breaks and parts which don't cause breaks. Following, we will call these parts "breaking code" [^4] and "non breaking code" [^5] * The breaking code has to be hidden behind a feature flag (major-flag [^6]) until the next major release. * The non breaking code only needs to be hidden behind a feature flag (minor-flag [^7]), while developing. As soon as the feature or change is tested, quality ensured and approved the flag can be removed. * The breaking code will only be used, if the corresponding major-flag is active. @@ -87,7 +87,7 @@ Deprecated code and major-deprecated code will not be called anywhere in the cod As an Example the removal of the class `MySampleClass` and the replacement with `MyNewSampleClass` -Version 6.3.3.0 - While developing +Version 6.3.3.0 - While developing The new class `MyNewSampleClass` is implemented and marked as `@internal (flag:FEATURE_NEXT_22222)`. The old class `MySampleClass` is annotated as `@feature-deprecated tag:v6.4.0 (flag:FEATURE_NEXT_22222)`. The code which calls the old class `MySampleClass` gets a feature switch which is annotated as `@major-deprecated tag:v6.4.0 (flag:FEATURE_NEXT_22222)` @@ -106,7 +106,7 @@ In these examples we assume the minor flag is called `FEATURE_NEXT_11111` and th #### Startup examples These are some Startup examples. We have some more complex examples below [Complex examples](Complex examples) if you want to know more for a special case. -In general it is recommended to read the startup examples and come back to the complex examples, if you face a specific question. +In general, it is recommended to read the startup examples and come back to the complex examples if you face a specific question. ##### Changelog Often there are changes in a major feature which will be released immediately after the removal of the minor flag, because they don't break anything, and other changes which will not be used until the next major release. @@ -138,12 +138,12 @@ flag: FEATURE_NEXT_22222 ``` ##### Services -Services are somehow special, because they are always initiated in the container. So it is ok to change anything in the constructor of a service because it always has to be used from the container instance. +Services are somehow special because they are always initiated in the container. So it is ok to change anything in the constructor of a service because it always has to be used from the container instance. _If someone initiate a service directly with `new ServiceController()` we don't provide compatibility_. ###### Declare a new Service When a new service is implemented, the tag `shopware.feature` with the minor-flag has to be added in the dependencyInjection.xml. -The new service should be usable, as soon as the development is finished to allow plugin developers to implement new services early. +The new service should be usable as soon as the development is finished to allow plugin developers to implement new services early. If it is not intended to allow the use beforehand, you can also use the major flag to make the service unavailable until the next major. service.xml ```xml @@ -156,7 +156,7 @@ service.xml ###### Exchange a service with a new one If you want to exchange an old service with a new one, you act for the new service like above. If the old service is not used anywhere right now, you can deprecate it with the symfony tag. -On feature release the service will be deprecated with the symfony tag: +On feature release, the service will be deprecated with the symfony tag: ```xml @@ -179,7 +179,7 @@ If you are implementing a new Feature with many new Services, you could decide t This way you don't have to mess with every single new service. * Create a new xml. As an Example `src/Core/Framework/DependencyInjection/nextLevelShopping.xml` -* Add the new xml to the relevant bundle.php. In this case it would be the Framework.php +* Add the new xml to the relevant bundle.php. In this case, it would be the Framework.php ```php class Framework extends Bundle @@ -202,7 +202,7 @@ class Framework extends Bundle ##### Unused classes Classes which should not be used anymore should marked as deprecated with FEATURE:triggerDeprecated. -We can not simply remove it directly because a class can always be initiated in a plugin. +We cannot simply remove it directly because a class can always be initiated in a plugin. PHP ```php @@ -243,7 +243,7 @@ public function testNewWorkflow(): void ``` ##### Add an argument to a public method -If you want to add an argument to a public method you have to add the new argument as a comment and use func_get_args to get this argument if provided. +If you want to add an argument to a public method, you have to add the new argument as a comment and use func_get_args to get this argument if provided. ```php /** @@ -488,9 +488,9 @@ class ProductRoute ``` ### Complex examples -You don't have to read all these examples in one take. It's most likely they will confuse you more than help you, if you don't have a concrete case in mind. -Come to this section, if you stumble over a case where you don't exactly know that to do. -If you don't find your answer here, don't be silent. Call us out in slack for that we aware of cases that have to be stated here. +You don't have to read all these examples in one take. It's most likely they will confuse you more than help you if you don't have a concrete case in mind. +Come to this section if you stumble over a case where you don't exactly know that to do. +If you don't find your answer here, don't be silent. Call us out in slack for that we are aware of cases that have to be stated here. #### Rename or removal of a property Should a property of a class be removed, it will be annotated as deprecated and will only be used in code which is also deprecated. Should a property be renamed, it will be deprecated instead and we implement a new property with the new name. @@ -853,9 +853,9 @@ class MyTestService ##### Change return value of method There are to ways to do this. If you can be sure that no caller of the method typehinted the return value, you could remove the return type. -If you can not be sure (and with public methods you bearly can be sure) you have to add a new method. +If you cannot be sure (and with public methods, you barely can be sure), you have to add a new method. -###### New Method - while develoment: +###### New Method - while development: ```php class MyTestClass { @@ -903,7 +903,7 @@ class MyTestClass } ``` -###### Remove Typehint - while develoment: +###### Remove Typehint - while development: You have to be really sure that there is no call that will break if the return type is not defined ```php @@ -954,7 +954,7 @@ class MyTestCallerClass If an argument of a class constructor or service should be exchanged, the new argument is added and the old one is set to "nullable" if necessary. If an argument of a service is removed or exchanged, it has to be annotated in the dependency-xml as well. If an argument is added, which is not available without the feature, it has to be marked as `on-invalid="null"`. -Also it has to be annotated as @internal in the dependency-xml as well and the comment should explicitly explain that the `on-invalid="null"` should be removed. +Also, it has to be annotated as @internal in the dependency-xml as well and the comment should explicitly explain that the `on-invalid="null"` should be removed. PHP class ```php @@ -987,9 +987,9 @@ service.xml If an interface should be changed in any compatible way, instead you should implement an abstract class. the abstract class implements the interface until the next major. -This only applies in compatible interface changes. If you have to break the interface see below. +This only applies to compatible interface changes. If you have to break the interface, see below. -In this example the MyTestClass implements MyTestInterface and other classes typehinted MyTestClass with this interface, so you have to make sure that the interface is still present. +In this example, the MyTestClass implements MyTestInterface and other classes typehinted MyTestClass with this interface, so you have to make sure that the interface is still present. ###### Original Class ```php class MyTestClass implements MyTestInterface @@ -1083,15 +1083,15 @@ class MyTestClass extends MyTestAbstractClass ### Detailed Rules #### Only Minor Changes (no breaks) -Feature and changes tend to be released in a minor release. Don't cause breaks. Simple additions, refactorings, etc +Features and changes tend to be released in a minor release. Don't cause breaks. Simple additions, refactorings, etc * While developing * New Code * The new code should be hidden behind a feature flag and be annotated with @internal (flag:FEATURE_NEXT_11111) for new code public API [^1]. - * Add new tests for the new code. Put this tests behind the feature flag (FEATURE:skipTestIfInActive('FEATURE_NEXT_11111')). + * Add new tests for the new code. Put these tests behind the feature flag (FEATURE:skipTestIfInActive('FEATURE_NEXT_11111')). * Changed Code - * The changed code should be hidden behind a feature flag. In most cases this will be an if-else clause with FEATURE:isActive('FEATURE_NEXT_11111') with an additional expressive comment on what have to be done on feature release. + * The changed code should be hidden behind a feature flag. In most cases, this will be an if-else clause with FEATURE:isActive('FEATURE_NEXT_11111') with an additional expressive comment on what has to be done on feature release. * Removed Code - * On this kind of changes there should barely remove code, except for private code. The private code which will be removed should be annotated with @feature-deprecated (flag:FEATURE_NEXT_11111) with an additional expressive comment on what have to be done on feature release. + * On this kind of changes there should barely remove code, except for private code. The private code which will be removed should be annotated with @feature-deprecated (flag:FEATURE_NEXT_11111) with an additional expressive comment on what has to be done on feature release. * On Feature release * New Code * Remove the feature flag and @internal annotation. @@ -1106,9 +1106,9 @@ Feature and Changes tend to be released in a minor release and are developed in * While developing * New Code * The new code should be hidden behind a feature flag and be annotated with @internal (flag:FEATURE_NEXT_11111) for new code public api [^1]. - * Add new tests for the new code. Put this tests behind the feature flag (FEATURE:skipTestIfInActive('FEATURE_NEXT_11111')). + * Add new tests for the new code. Put these tests behind the feature flag (FEATURE:skipTestIfInActive('FEATURE_NEXT_11111')). * Changed Code - * The changed code should be hidden behind a feature flag. In most cases this will be an if-else clause with FEATURE:isActive('FEATURE_NEXT_11111') with an additional expressive comment on what have to be done on feature release. + * The changed code should be hidden behind a feature flag. In most cases, this will be an if-else clause with FEATURE:isActive('FEATURE_NEXT_11111') with an additional expressive comment on what have to be done on feature release. * Removed Code / Deprecated code * The obsolete code has to hidden behind the minor-flag and annotated as @feature-deprecated. * The call of the deprecated code has to be hidden behind an extra feature flag, especially for the major version. Also the code has to be annotated with @major-deprecated tag:v6.4.0 (flag:FEATURE_NEXT_22222) [^2]. @@ -1130,7 +1130,7 @@ Feature and Changes tend to be released in a minor release and are developed in #### Minor Changes as part of Major Feature (No Breaks) Parts of a major feature, which can be changed without breaking anything. New classes, private method changes with unchanged output. -* Depending on the value of the new changes, the developers should decide, if it is an advantage to release the backward-compatible parts early with a minor version. In that case the previous workflow (Only Minor Changes (with deprecating code)) has to be used. Otherwise the changes stay behind the major feature flag with the other breaking changes and follow the guid below (Major Changes as part of Major Feature (Breaks)) +* Depending on the value of the new changes, the developers should decide if it is an advantage to release the backward-compatible parts early with a minor version. In that case the previous workflow (Only Minor Changes (with deprecating code)) has to be used. Otherwise, the changes stay behind the major feature flag with the other breaking changes and follow the guid below (Major Changes as part of Major Feature (Breaks)) #### Major Changes as part of Major Feature (Breaks) Parts of a major feature or refactoring which breaks current behaviour. Removal of classes, methods or properties, change of signatures, business logic changes... @@ -1140,7 +1140,7 @@ Parts of a major feature or refactoring which breaks current behaviour. Removal * The new code should be hidden behind a major feature flag and be annotated with @internal (flag:FEATURE_NEXT_11111) for new code public api [^1]. * Add new tests for the new code. Put this tests behind the major feature flag (FEATURE:skipTestIfInActive('FEATURE_NEXT_22222')). * Changed Code - * The changed code should be hidden behind a major feature flag. In most cases this will be an if-else clause with FEATURE:isActive('FEATURE_NEXT_22222') with an additional expressive comment on what have to be done on major release. + * The changed code should be hidden behind a major feature flag. In most cases, this will be an if-else clause with FEATURE:isActive('FEATURE_NEXT_22222') with an additional expressive comment on what have to be done on major release. * Declare old tests as legacy. https://symfony.com/doc/current/components/phpunit_bridge.html#mark-tests-as-legacy * Removed Code / Deprecated code * The call of the deprecated code has to be hidden behind the major feature flag. Also the code has to be annotated with @major-deprecated tag:v6.4.0 (flag:FEATURE_NEXT_22222) [^2]. @@ -1151,8 +1151,8 @@ Parts of a major feature or refactoring which breaks current behaviour. Removal * Remove the feature flag and the @internal annotation. * Remove the feature flag from the tests. * Changed Code - * Remove feature flag and keep new solution. In case you had an if-else clause, you will keep the new code and remove the old, according to the comment made before. - * Remove parts of the changed code which aren't be called anymore. + * Remove the feature flag and keep the new solution. In case you had an if-else clause, you will keep the new code and remove the old, according to the comment made before. + * Remove parts of the changed code which aren't called anymore. * Removed Code / Deprecated code * Remove major-deprecated annotation, and the unused code according to the comment made before. * Remove old tests @@ -1161,12 +1161,12 @@ Parts of a major feature or refactoring which breaks current behaviour. Removal [^2]: The version number of the **upcoming** major version, and the feature flag extra created for this issue for the major release. -Glossar: +Glossary: [^3]: **old code**: Code which will be obsolete with the new feature or refactoring. [^4]: **breaking code**: Code which is implemented with the change and will break current behaviour. -[^5]: **non breaking code**: Code which is implemented with the change but will not break any previously behaviour. +[^5]: **non breaking code**: Code which is implemented with the change but will not break any previous behaviour. [^6]: **major-flag**: A feature flag, which hides breaking code, that can only be released with the next major version. -[^7]: **minor-flag**: A feature flag, which will be used while developing, to secure unfinished code changes. This flag will be removed as soon as the festure or change is completed and approved. +[^7]: **minor-flag**: A feature flag, which will be used while developing, to secure unfinished code changes. This flag will be removed as soon as the feature or change is completed and approved. [^8]: **code-basis**: The whole code of shopware platform, development and production. -[^9]: **feature flags**: A feature flag is a toggle, which switches code behaviour. Basically there are two kinds of feature flags: minor-flags and major-flags. The flag is build from an epic- or issue key. If a major-flag is needed because of deprecations in a minor feature, a special ticket should be created for the task to remove the deprecations on major release. ("NEXT-22222 - Remove feature flag for better order view") +[^9]: **feature flags**: A feature flag is a toggle, which switches code behaviour. Basically, there are two kinds of feature flags: minor-flags and major-flags. The flag is build from an epic- or issue key. If a major-flag is needed because of deprecations in a minor feature, a special ticket should be created for the task to remove the deprecations on major release. ("NEXT-22222 - Remove feature flag for better order view") [^10]: **soft-break**: A soft-break is a break, which doesn't cause any errors, but leads to changes and/or inconsistencies in the business logic. E.g. it would be a soft-break if the calculation of prices gets a rounding that was not there before. This will lead to changing prices. diff --git a/resources/references/adr/2021-03-24-nested-line-items.md b/resources/references/adr/2021-03-24-nested-line-items.md index 2042f1a2a..4cb157072 100644 --- a/resources/references/adr/2021-03-24-nested-line-items.md +++ b/resources/references/adr/2021-03-24-nested-line-items.md @@ -3,7 +3,7 @@ title: Processing of nested line items date: 2020-03-24 area: checkout tags: [checkout, cart, line-items] ---- +--- # Processing of nested line items diff --git a/resources/references/adr/2021-05-14-when-to-use-plain-sql-or-dal.md b/resources/references/adr/2021-05-14-when-to-use-plain-sql-or-dal.md index ebd2f3219..f6def33d9 100644 --- a/resources/references/adr/2021-05-14-when-to-use-plain-sql-or-dal.md +++ b/resources/references/adr/2021-05-14-when-to-use-plain-sql-or-dal.md @@ -3,7 +3,7 @@ title: When to use plain SQL or the DAL date: 2021-05-14 area: core tags: [sql, dal, store-api, storefront, admin-api, entity-indexer] ---- +--- # When to use plain SQL or the DAL @@ -48,4 +48,3 @@ In the following application layers you should work with plain SQL because of th * In Core Components * Core components like the theme compiler, request transformer, etc. are not places where a third party developer should be able to load additional data. The data loaded here is for pure processing only and should never be rewritten. * Deep processes like theme compiling should not be affected by plugin entity schemas, because plugins are an optional part of the system and might be in an unstable state during an update process. - diff --git a/resources/references/adr/2021-05-28-introduce-eslint-on-vue-admin.md b/resources/references/adr/2021-05-28-introduce-eslint-on-vue-admin.md index 3d75b2f4d..28b30d17c 100644 --- a/resources/references/adr/2021-05-28-introduce-eslint-on-vue-admin.md +++ b/resources/references/adr/2021-05-28-introduce-eslint-on-vue-admin.md @@ -3,7 +3,7 @@ title: Vue administration app has ESLint support date: 2021-05-28 area: administration tags: [administration, eslint, vue, linting] ---- +--- # Vue administration app has ESLint support diff --git a/resources/references/adr/2021-06-14-introduce-jest-fail-on-console.md b/resources/references/adr/2021-06-14-introduce-jest-fail-on-console.md index 563c9c2da..ddb54d3f6 100644 --- a/resources/references/adr/2021-06-14-introduce-jest-fail-on-console.md +++ b/resources/references/adr/2021-06-14-introduce-jest-fail-on-console.md @@ -3,7 +3,7 @@ title: Introduce jest-fail-on-console date: 2021-06-14 area: administration tags: [jest, test, console, error, warning] ---- +--- # Introduce jest-fail-on-console @@ -16,9 +16,9 @@ You can find the original version [here](https://github.com/shopware/platform/bl A jest pipeline run produced previously hundreds of errors and warnings, which made it hard to see why a test failed and if a passing test isn’t just a false positive. ## Decision -To combat this we decided to introduce the npm package [jest-fail-on-console](https://github.com/ricardo-ch/jest-fail-on-console#readme), which causes individual unit tests to fail, if they log an error or a warning to the console. +To combat this, we decided to introduce the npm package [jest-fail-on-console](https://github.com/ricardo-ch/jest-fail-on-console#readme), which causes individual unit tests to fail if they log an error or a warning to the console. ## Consequences -Jest-fail-on-console makes unit tests a lot more expressive, because it prevents easy mistakes, which would previously lead to an error that is hard to find and notice. Like an incorrect key in a `v-for` loop, which could potentially lead to vue update errors, but would have not caused the test to fail. +Jest-fail-on-console makes unit tests a lot more expressive because it prevents easy mistakes, which would previously lead to an error that is hard to find and notice. Like an incorrect key in a `v-for` loop, which could potentially lead to vue update errors, but would have not caused the test to fail. -Jest tests might be a little harder to write, because errors cannot simply be ignored anymore. All needed components have to be provided to the component being tested either by being mocked or built, all API requests need to be mocked and all needed mixns have to be provided. Although it is a little more work, it makes the jest tests, as previously mentioned, more expressive and as a neat side benefit it keeps the console clean. +Jest tests might be a little harder to write, because errors cannot simply be ignored anymore. All needed components have to be provided to the component being tested either by being mocked or built, all API requests need to be mocked, and all needed mixins have to be provided. Although it is a little more work, it makes the jest tests, as previously mentioned, more expressive and as a neat side benefit it keeps the console clean. diff --git a/resources/references/adr/2021-07-22-move-storefront-scripts-to-head.md b/resources/references/adr/2021-07-22-move-storefront-scripts-to-head.md index ff3e5544d..a0e377e84 100644 --- a/resources/references/adr/2021-07-22-move-storefront-scripts-to-head.md +++ b/resources/references/adr/2021-07-22-move-storefront-scripts-to-head.md @@ -3,7 +3,7 @@ title: Move storefront script to head with defer date: 2021-07-22 area: storefront tags: [storefront, javascript, performance] ---- +--- # Move storefront script to head with defer diff --git a/resources/references/adr/2021-08-10-storefront-coding-standards.md b/resources/references/adr/2021-08-10-storefront-coding-standards.md index f685ade76..642e3d9c4 100644 --- a/resources/references/adr/2021-08-10-storefront-coding-standards.md +++ b/resources/references/adr/2021-08-10-storefront-coding-standards.md @@ -3,7 +3,7 @@ title: Storefront coding standards date: 2021-08-10 area: storefront tags: [storefront, coding-standards, architecture] ---- +--- # Storefront coding standards @@ -63,7 +63,7 @@ A storefront controller should never use a repository directly, It should be inj Routes which should load a full storefront page, should use a PageLoader class to load all corresponding data that returns a Page-Object. -Pages which contains data which are the same for all customers, should have the _httpCache=true defaults parameter in the Routes annoation. +Pages which contains data which are the same for all customers, should have the _httpCache=true defaults parameter in the Routes annotation. #### Write operations inside Storefront controllers Write operations should create their response with the createActionResponse function to allow different forwards and redirects. @@ -84,4 +84,3 @@ The pageloader always returns a page-object. All dependencies in the controllers for routes which render a page have to be moved to the `Loaders` and if still missing, the `Loader` and `Page` has to be created. All direct DAL-dependencies inside the storefront have to be moved to Store-Api routes and respective calls. All other dependencies which are not allowed have to be checked for individual alternatives - diff --git a/resources/references/adr/2021-08-11-make-platform-stand-alone.md b/resources/references/adr/2021-08-11-make-platform-stand-alone.md index 1b0570489..a0b1c386a 100644 --- a/resources/references/adr/2021-08-11-make-platform-stand-alone.md +++ b/resources/references/adr/2021-08-11-make-platform-stand-alone.md @@ -3,7 +3,7 @@ title: Make shopware/platform stand-alone for development and testing date: 2021-08-11 area: product-operations tags: [shopware, platform, development, testing] ---- +--- # Make shopware/platform stand-alone for development and testing @@ -18,15 +18,15 @@ The platform requires some additional config, a console and web entrypoint and a running the application. In practice this is provided by one of the templates: `shopware/development` or `shopware/production`. This creates a cyclic dependency, which brings some problems: - `shopware/development` and `shopware/platform` need to be updated in lockstep, which makes updating them individually sometimes impossible -- some IDEs have trouble with multi repository projects +- some IDEs have trouble with multi-repository projects - updating development tooling breaks everything -- auto-detection of git revision and diff is broken, because the development template is the root +- auto-detection of git revision and diff is broken because the development template is the root - for each release branch an additional branch needs to be maintained ## Decision - use shopware/platform directly in the pipeline -- allow development without a template, by moving the development tooling into platform +- allow development without a template by moving the development tooling into platform - only advertise this as `shopware/platform` development setup. Projects should still start with `shopware/production` as a template - `shopware/development` should continue to work - allow testing by adding entrypoints for cli and web @@ -34,12 +34,12 @@ This creates a cyclic dependency, which brings some problems: * these scripts should be kept small and simple * essential functionality should be implemented as npm scripts or symfony commands * we should improve the symfony commands or npm scripts if they are too complicated - * if possible the scripts should allow adding arguments + * if possible, the scripts should allow adding arguments - use standard convention * `.env.dist` provides default environment variables * `.env` can be used to define a custom environment (for example, if you use a native setup) * `docker-compose.yml` provides a working environment - * `docker-compose.override.yml` can be used for local overrides to expose ports for example + * `docker-compose.override.yml` can be used for local overrides to expose ports, for example - use defaults that work out of the box in most cases * don't expose hard coded ports in docker-compose.yml. It's not possible to undo it and may prevent startup of the app service @@ -47,6 +47,6 @@ This creates a cyclic dependency, which brings some problems: - simplified CI, which also makes errors easier to reproduce locally - simplified local setup -- no custom scripts, that are not available in all setups +- no custom scripts that are not available in all setups - projects may try to use shopware/platform directly - yet another shopware setup to choose from diff --git a/resources/references/adr/2021-08-31-refactor-admin-build-process-to-webpack-multi-compiler-mode.md b/resources/references/adr/2021-08-31-refactor-admin-build-process-to-webpack-multi-compiler-mode.md index 9171d9f1c..9941c7a10 100644 --- a/resources/references/adr/2021-08-31-refactor-admin-build-process-to-webpack-multi-compiler-mode.md +++ b/resources/references/adr/2021-08-31-refactor-admin-build-process-to-webpack-multi-compiler-mode.md @@ -3,7 +3,7 @@ title: Refactor admin build process to webpack-multi-compiler mode date: 2021-08-31 area: administration tags: [administration, webpack, plugin, build] ---- +--- # Refactor admin build process to webpack-multi-compiler mode diff --git a/resources/references/adr/2021-09-06-make-core-mail-templates-independent-from-storefront-urls.md b/resources/references/adr/2021-09-06-make-core-mail-templates-independent-from-storefront-urls.md index d45bdb947..2d0c0c3eb 100644 --- a/resources/references/adr/2021-09-06-make-core-mail-templates-independent-from-storefront-urls.md +++ b/resources/references/adr/2021-09-06-make-core-mail-templates-independent-from-storefront-urls.md @@ -3,7 +3,7 @@ title: Make Core mail templates independent from Storefront urls date: 2021-09-06 area: storefront tags: [mail, storefront, headless] ---- +--- # Make Core mail templates independent from Storefront urls diff --git a/resources/references/adr/2021-09-14-technical-concept-custom-entities.md b/resources/references/adr/2021-09-14-technical-concept-custom-entities.md index ad0fb1355..9290bf997 100644 --- a/resources/references/adr/2021-09-14-technical-concept-custom-entities.md +++ b/resources/references/adr/2021-09-14-technical-concept-custom-entities.md @@ -3,7 +3,7 @@ title: Technical concept custom entities date: 2021-08-31 area: core tags: [app, custom-entities, store-api, dal, admin-api] ---- +--- # Technical concept custom entities diff --git a/resources/references/adr/2021-09-22-refactor-theme-inheritance.md b/resources/references/adr/2021-09-22-refactor-theme-inheritance.md index 5981e2d09..1c1650cc1 100644 --- a/resources/references/adr/2021-09-22-refactor-theme-inheritance.md +++ b/resources/references/adr/2021-09-22-refactor-theme-inheritance.md @@ -3,7 +3,7 @@ title: Refactor theme inheritance date: 2021-09-22 area: storefront tags: [theme, storefront, inheritance] ---- +--- # Refactor theme inheritance @@ -13,12 +13,12 @@ You can find the original version [here](https://github.com/shopware/platform/bl {% endhint %} ## Context -Currently the themes can only inherit config fields from the default Storefront theme. -Also this inheritence is only a snapshot by activation time of the theme - The configs are copied to the new theme and changes to the default theme config will not appear in the new theme without a re-activation. +Currently, the themes can only inherit config fields from the default Storefront theme. +Also, this inheritance is only a snapshot by activation time of the theme - The configs are copied to the new theme and changes to the default theme config will not appear in the new theme without a re-activation. The different possibilities to inherit different parts of a theme, like scripts, templates and config, can also cause problems on later updates. ## Decision -To take this points into account we have decided to add a new inheritance key for the `configFields` in the `theme.json` which allow a theme to inherit its config from other themes in a given order: +To take this points into account, we have decided to add a new inheritance key for the `configFields` in the `theme.json` which allow a theme to inherit its config from other themes in a given order: ```json "configInheritance": [ "@Storefront", @@ -169,11 +169,11 @@ To take this points into account we have decided to add a new inheritance key fo ## Consequences The Consequences for the two approaches are described below: ### 1. New config inheritance: -* The inheritance **can still cause incompatibility errors** because of missing subsets of a dependend theme. -* The current themes will work as always but one can also add an inheritance for the config fields. -* The inhertiance will no longer be a snapshot, but a dynamic copy of the inherited themes (The changes of child themes will be considered by the new theme automaticaly) -* The admin for the themes will get an inheritation mechanism which allows users to decide if a field will use its inherited or a new value (simmiliar to productvariant inherited fields) -* Themes which are dependend on other themes than the default storefront theme, need to add the other themes into there composer.json as `required` to prevent incomplete setups. +* The inheritance **can still cause incompatibility errors** because of missing subsets of a dependent theme. +* The current themes will work as always, but one can also add an inheritance for the config fields. +* The inheritance will no longer be a snapshot, but a dynamic copy of the inherited themes (The changes of child themes will be considered by the new theme automatically) +* The admin for the themes will get an inheritance mechanism which allows users to decide if a field will use its inherited or a new value (similar to product variant inherited fields) +* Themes which are dependent on other themes than the default storefront theme, need to add the other themes into there composer.json as `required` to prevent incomplete setups. ```json "require": { "swag/previous-theme": "~1.1" diff --git a/resources/references/adr/2021-10-01-payment-flow.md b/resources/references/adr/2021-10-01-payment-flow.md index d22e0a060..6b5c54fbc 100644 --- a/resources/references/adr/2021-10-01-payment-flow.md +++ b/resources/references/adr/2021-10-01-payment-flow.md @@ -48,7 +48,7 @@ To improve the payment workflow on headless systems or reduce orders without pay The payment handler **has to verify the given payload with the payment service**, because Shopware cannot ensure that the transaction created by the frontend is valid for the current cart. After successful verification the order will be created and the payment handler will be called again to **charge the payment**. -When the charge was successful the payment will be set to paid and the user will be forwarded to the finish page, but on [failure the after order payment process will be active](#after-order-payment-error-case). It is highly recommended implementing this optional feature, when the creation and the capturing of the payment can be seperated. +When the charge was successful the payment will be set to paid and the user will be forwarded to the finish page, but on [failure the after order payment process will be active](#after-order-payment-error-case). It is highly recommended implementing this optional feature, when the creation and the capturing of the payment can be separated. ![Pre created payment](../../../.gitbook/assets/adr/payment-flow/pre-created-payment.png) @@ -57,4 +57,3 @@ When the charge was successful the payment will be set to paid and the user will Both possible options can produce failed payments. In failure case the after order payment process begins. The client can choose a new payment method and retry the payment and the entire payment loop of a synchronous / asynchronous payment starts again. ![After order payment](../../../.gitbook/assets/adr/payment-flow/after-order-payment.svg) - diff --git a/resources/references/adr/2021-10-13-refund-handling.md b/resources/references/adr/2021-10-13-refund-handling.md index 7015a8d5d..7177bcc92 100644 --- a/resources/references/adr/2021-10-13-refund-handling.md +++ b/resources/references/adr/2021-10-13-refund-handling.md @@ -3,7 +3,7 @@ title: Refund handling date: 2021-10-13 area: checkout tags: [payment, refund, capture] ---- +--- # Refund handling diff --git a/resources/references/adr/2021-10-21-app-scripting.md b/resources/references/adr/2021-10-21-app-scripting.md index 8a02b5a15..02f68e628 100644 --- a/resources/references/adr/2021-10-21-app-scripting.md +++ b/resources/references/adr/2021-10-21-app-scripting.md @@ -22,9 +22,9 @@ To improve the abilities of Apps, they should be able to execute code synchronou - shipping method calculation - flow builder extensions -The app system requires that this code is in some way sandboxed, with no direct access to the database or filesystem and the code is not saved on the server. +The app system requires that this code is in some way sandboxed, with no direct access to the database or filesystem, and the code is not saved on the server. -Additionally, such Scripting feature generally improves the capabilities of the AppSystem, this feature is not bound to the AppSystem exclusively, it should be possible to add standalone scripts. +Additionally, such a Scripting feature generally improves the capabilities of the AppSystem, this feature is not bound to the AppSystem exclusively, it should be possible to add standalone scripts. ## Decision @@ -124,20 +124,20 @@ class ScriptEventRegistry ### Data Loading To allow apps to fetch additional data for the storefront, we will introduce PageLoaded-Hooks. -Those hooks will orient themself on the Page and PageLoadedEvents already present in the storefront. So for each PageType and PageLoadedEvent we will create a seperate Hook class. +Those hooks will orient themself on the Page and PageLoadedEvents already present in the storefront. So for each PageType and PageLoadedEvent we will create a separate Hook class. We will create separate HookClasses and not just one generic class, so we are able to type hint all the dynamic data that is available for that hook. That will improve the developer experience as it allows for autocompletion in the scripts and allows us to generate documentation for the hooks. The hooks will be instantiated and passed to the HookExecutor from the Controllers where the pages are loaded, so we are able to pass additional data if it is needed or makes sense. Additionally, we explicitly decided to not provide CriteriaEvent-Hooks, as that idea is contrary to the direction we may want to go with a separate and specialized data view for the storefront. ### Documentation -To ensure app developers can use the full potential of the app scripts we need to ensure that we document the features of app scripts extensively and make sure that the documentation is always up-to-date. -For this reason we decided to generate as much of the documentation as possible, so it never gets outdated and it's easier to generate full reference (e.g. all hook points that exist with the associated data and available services). +To ensure app developers can use the full potential of the app scripts, we need to ensure that we document the features of app scripts extensively and make sure that the documentation is always up-to-date. +For this reason we decided to generate as much of the documentation as possible, so it never gets outdated, and it's easier to generate full reference (e.g. all hook points that exist with the associated data and available services). ## Consequences - Added script events with the passed arguments need to be supported for a long time - We will create a new domain-specific way to interact with shopware core domain logic. This means we have to think of and develop a higher-level description of our core domain logic and represent it through new -functions that perform domain-specific tasks. For example, the block cart function in the example above. Those domain objects represent the API of the AppScripts, therefore breaking changes need to be considered carefully and should definitely follow our general breaking change policy. -Additionally, the domain specific layer may allow us to not break the public interface, when the implementation in the underlying services may break, so we can try to ensure even longer compatibility in the domain layer. -However, to make evolvability possible at all we need to inject the shopware version into the context of the app scripts, so that in the app scripts the version can be detected and new features used accordingly. +functions that perform domain-specific tasks. For example, the block cart function in the example above. Those domain objects represent the API of the AppScripts, therefore, breaking changes need to be considered carefully and should definitely follow our general breaking change policy. +Additionally, the domain-specific layer may allow us to not break the public interface, when the implementation in the underlying services may break, so we can try to ensure even longer compatibility in the domain layer. +However, to make evolvability possible at all, we need to inject the shopware version into the context of the app scripts, so that in the app scripts the version can be detected and new features used accordingly. diff --git a/resources/references/adr/2021-11-02-preparing-data-for-rule-evaluation.md b/resources/references/adr/2021-11-02-preparing-data-for-rule-evaluation.md index 270b2f1bd..95762836d 100644 --- a/resources/references/adr/2021-11-02-preparing-data-for-rule-evaluation.md +++ b/resources/references/adr/2021-11-02-preparing-data-for-rule-evaluation.md @@ -1,9 +1,9 @@ --- title: Preparing data for rule evaluation date: 2021-11-02 -area: business-ops +area: services-settings tags: [framework, rules, context, data-handling] ---- +--- # Preparing data for rule evaluation diff --git a/resources/references/adr/2021-11-05-adjust-adr-approval-rules.md b/resources/references/adr/2021-11-05-adjust-adr-approval-rules.md index 3d758cacf..6a837188a 100644 --- a/resources/references/adr/2021-11-05-adjust-adr-approval-rules.md +++ b/resources/references/adr/2021-11-05-adjust-adr-approval-rules.md @@ -3,7 +3,7 @@ title: Adjust ADR approval rules for the new org structure date: 2021-11-05 area: core tags: [architecture, adr, approval-rules] ---- +--- # Adjust ADR approval rules for the new org structure diff --git a/resources/references/adr/2021-11-09-increment-pattern.md b/resources/references/adr/2021-11-09-increment-pattern.md index 35efd4092..1939df7de 100644 --- a/resources/references/adr/2021-11-09-increment-pattern.md +++ b/resources/references/adr/2021-11-09-increment-pattern.md @@ -3,7 +3,7 @@ title: Introduce increment pattern date: 2021-11-09 area: system-settings tags: [architecture, increment, message-queue-stats] ---- +--- # Introduce increment pattern diff --git a/resources/references/adr/2021-11-22-merge-e2e-projects-into-a-single-project.md b/resources/references/adr/2021-11-22-merge-e2e-projects-into-a-single-project.md index 098bead3a..d81903b40 100644 --- a/resources/references/adr/2021-11-22-merge-e2e-projects-into-a-single-project.md +++ b/resources/references/adr/2021-11-22-merge-e2e-projects-into-a-single-project.md @@ -3,7 +3,7 @@ title: Merge E2E projects into a single project date: 2021-11-22 area: core tags: [e2e, cypress] ---- +--- # Merge E2E projects into a single project diff --git a/resources/references/adr/2021-11-23-add-possibility-for-plugin-to-add-a-html-file.md b/resources/references/adr/2021-11-23-add-possibility-for-plugin-to-add-a-html-file.md index 660a69eef..5eb988e08 100644 --- a/resources/references/adr/2021-11-23-add-possibility-for-plugin-to-add-a-html-file.md +++ b/resources/references/adr/2021-11-23-add-possibility-for-plugin-to-add-a-html-file.md @@ -3,7 +3,7 @@ title: Add possibility for plugins to add a HTML file date: 2021-11-23 area: administration tags: [plugin, admin, extension-api] ---- +--- # Add possibility for plugins to add a HTML file diff --git a/resources/references/adr/2021-12-07-admin-extension-api-standards.md b/resources/references/adr/2021-12-07-admin-extension-api-standards.md index 59dfcce85..837919a91 100644 --- a/resources/references/adr/2021-12-07-admin-extension-api-standards.md +++ b/resources/references/adr/2021-12-07-admin-extension-api-standards.md @@ -3,7 +3,7 @@ title: Admin extension API standards date: 2021-12-07 area: administration tags: [plugin, admin, extension-api] ---- +--- # Admin extension API standards @@ -75,7 +75,7 @@ sw.ui.componentSection('sw-manufacturer-card-custom-fields__before').add({ #### Vue Devtools Plugin for finding the PositionIDs It is impossible to create a list of all potential position IDs. And they would be hard to manage. To solve this problem we are writing a custom plugin for the Vue Devtools. This plugin will be available for Vue Devtools 6+. It makes identifying the position IDs very easy. -Just open the plugin in the Devtools (It is available directly when you open the Administration). Then you can see all positions at the current administration view which are available for extending. If you click at one position ID you get more information about it. Like the property in the Admin-Extension-SDK so that you directly know what functionality this position has. +Just open the plugin in the Devtools (It is available directly when you open the Administration). Then you can see all positions at the current administration view which are available for extending. If you click at one position ID you get more information about it. Like the property in the Meteor-Extension-SDK so that you directly know what functionality this position has. In summary: the Devtool plugin provides a visual way to see which parts can be extended and what are the positionIDs for the extension position. diff --git a/resources/references/adr/2022-01-05-add-feature-flag-support-for-storefront-scss.md b/resources/references/adr/2022-01-05-add-feature-flag-support-for-storefront-scss.md index 924be7dfd..7ba6013de 100644 --- a/resources/references/adr/2022-01-05-add-feature-flag-support-for-storefront-scss.md +++ b/resources/references/adr/2022-01-05-add-feature-flag-support-for-storefront-scss.md @@ -3,7 +3,7 @@ title: Add feature flag support for Storefront SCSS date: 2022-01-05 area: storefront tags: [feature-flag, scss] ---- +--- # Add feature flag support for Storefront SCSS diff --git a/resources/references/adr/2022-01-06-custom-app-api-endpoints.md b/resources/references/adr/2022-01-06-custom-app-api-endpoints.md index a90faadfb..3f21629a6 100644 --- a/resources/references/adr/2022-01-06-custom-app-api-endpoints.md +++ b/resources/references/adr/2022-01-06-custom-app-api-endpoints.md @@ -3,7 +3,7 @@ title: Allow apps to define custom api endpoints date: 2022-01-06 area: core tags: [admin-api, store-api, app-system] ---- +--- # Allow apps to define custom api endpoints @@ -13,7 +13,7 @@ You can find the original version [here](https://github.com/shopware/platform/bl {% endhint %} ## Context -Apps should be allowed to provide their own API and Store-API and Storefront endpoints where they can execute different logics, that deviates from the automatic entity API. +Apps should be allowed to provide their own API and Store-API and Storefront endpoints where they can execute different logics that deviate from the automatic entity API. ## Decision @@ -24,14 +24,14 @@ We implement two new endpoints: The `{hook}` parameter is used as the script hook name and prefixed with the url prefix (`api-`, `store-api-`). -This hook is then executed and apps have the possibility to load or even write data in the scripts. +This hook is then executed, and apps have the possibility to load or even write data in the scripts. The following data is given to the script: * [array] request.request.all * [context/sales channel context] context -By default multiple scripts can be executed on a single hook, however we will add a `hook.stopPropagation()` method to all API-Hooks, if that was called no further scripts will be executed. -Furthermore we will document that the hook-name the app developer chooses should contain the vendor-prefix to prevent unwanted overrides from other apps. +By default, multiple scripts can be executed on a single hook; however, we will add a `hook.stopPropagation()` method to all API-Hooks, if that was called no further scripts will be executed. +Furthermore, we will document that the hook-name the app developer chooses should contain the vendor-prefix to prevent unwanted overrides from other apps. ### Storefront We implement a new endpoint: @@ -39,7 +39,7 @@ We implement a new endpoint: The `{hook}` parameter is used as the script hook name and prefixed with the url prefix (`storefront-`). -In this hook the app can load or write data and either return a script response or render a twig template as a response. +In this hook, the app can load or write data and either return a script response or render a twig template as a response. The following data is given to the script: * [array] request.request.all @@ -51,11 +51,11 @@ The following data is given to the script: We will add a new `response` service that provides factory methods to create response objects. The returned Response object is a generic wrapper around one of the following responses: `JsonResponse`, `RedirectResponse`, `StorefrontResponse`. -To output the created response it has to be assigned to the hook: +To output the created response, it has to be assigned to the hook: ```twig {% do hook.setResponse(response) %} ``` -If no response is set an empty 204 response will be sent as default. +If no response is set, an empty 204 response will be sent as default. ##### Returning a custom JsonResponse @@ -77,7 +77,7 @@ The render() factory allows to pass the template name and the parameters (the pa ```twig {% set response = services.response.render('@myApp/storefront/pages/my-custom-page.html.twig', { 'page': hook.page }) %} ``` -If it is called outside of a SalesChannelContext (e.g. from an `/api` endpoint) or called on installations that don't have the storefront-bundle installed it will throw an exception. +If it is called outside of a SalesChannelContext (e.g., from an `/api` endpoint) or called on installations that don't have the storefront-bundle installed it will throw an exception. #### Login Protection @@ -89,7 +89,7 @@ and will throw an `CustomerNotLoggedInException` if there is no customer logged #### Caching -Our script response wrapper allows to modify the caching strategies for the responses. +Our script response wrapper allows modifying the caching strategies for the responses. ```twig {% do response.cache.invalidationState('logged-in', 'cart-filled') %} {% do response.cache.maxAge(7200) %} @@ -97,7 +97,7 @@ Our script response wrapper allows to modify the caching strategies for the resp {% do response.cache.tag('my-manufacturer-tag-' ~ manufacturerId, 'another-tag') %} ``` By default all /storefront and /store-api routes are cached, so caching it is opt-out for those routes. -For the /api routes caching is not support, if you provide cache configuration on the response of those routes it will be ignored. +For the /api routes caching is not supported, if you provide cache configuration on the response of those routes, it will be ignored. For individual cache invalidation, we add a new `cache-invalidation`-hook point. That hook-point is a hook on the general EntityWrittenContainerEvent. The app can analyze the write payload of the event and use a cache-invalidation service to invalid the cache for a given tag. @@ -127,17 +127,17 @@ Instead of providing the raw payload, we will provide a fluid, functional interf #### No XML-config App-Scripts in general and custom api endpoints in particular work without further configuration inside the manifest.xml file. -We prefer solutions inside the scripts over solution that would require additional configuration in the xml file. +We prefer solutions inside the scripts over a solution that would require additional configuration in the xml file. The reason is that everything regarding app scripts is in one place inside the app itself, namely the `Resources/scripts` folder. -Additionally the manifest.xml can get outdated which may lead to confusing errors, and in general the structure of the xml file is more limited, then the possiblilties we have in the app scripts itself. +Additionally, the manifest.xml can get outdated which may lead to confusing errors, and in general, the structure of the xml file is more limited than the possibilities we have in the app scripts itself. #### SEO-Urls -We won't add seo urls in this iteration, the reason is that that feature is pretty complex and we don't know yet if the feature would be used at all or not. -Additionally a feature like that would add a heavy maintenance burden because the tight coupling to the general seo_url solution, and we just don't know yet if the feature brings more value +We won't add seo urls in this iteration, the reason is that that feature is pretty complex, and we don't know yet if the feature would be used at all or not. +Additionally, a feature like that would add a heavy maintenance burden because of the tight coupling to the general seo_url solution, and we just don't know yet if the feature brings more value We also dropped the idea of custom-routes aka the (static) seo urls light alternative, because it is an overly specific solution -We prefer more general solutions, as we can't anticipate all use cases the app developers may have, and we can't possibly built a custom solution for every use case they may have. -Therefore we will create a separate ticket/ADR to add lifecylce scripts to the app scripts. A script like that could be used to add entries into the seo_url table with aliases for the script routes but is not limited to that use case. -It will creatly simplify the use case that on installation of the app something should be changed/added in the DB of the shop (the current way to go would be to add a webhook on the app_install event and build an external service that in turn uses the api to change stuff, we would eliminate the need of the external server) +We prefer more general solutions, as we can't anticipate all use cases the app developers may have, and we can't possibly build a custom solution for every use case they may have. +Therefore, we will create a separate ticket/ADR to add lifecycle scripts to the app scripts. A script like that could be used to add entries into the seo_url table with aliases for the script routes but is not limited to that use case. +It will greatly simplify the use case that on installation of the app something should be changed/added in the DB of the shop (the current way to go would be to add a webhook on the app_install event and build an external service that in turn uses the api to change stuff, we would eliminate the need of the external server) diff --git a/resources/references/adr/2022-01-20-feature-flags-for-major-versions.md b/resources/references/adr/2022-01-20-feature-flags-for-major-versions.md index b13ef5f57..c88abc1a3 100644 --- a/resources/references/adr/2022-01-20-feature-flags-for-major-versions.md +++ b/resources/references/adr/2022-01-20-feature-flags-for-major-versions.md @@ -3,7 +3,7 @@ title: Feature flags for major versions date: 2022-01-20 area: core tags: [core, feature-flag, workflow, major-version] ---- +--- # Feature flags for major versions @@ -17,14 +17,14 @@ Feature flags enable the developer to create new code which is hidden behind the We use this functionality to merge breaks into the trunk early, without them already being switched active. ## Decision -We will use feature flags for major versions to hide new code, that will be introduced in the next major version. +We will use feature flags for major versions to hide new code that will be introduced in the next major version. We have only one feature flag in our core sources: `v6.5.0.0`. This feature flag is used for the breaks mentioned above. ## Consequences We will use the static functions of the Feature class to check if a feature is active or not. And only hide code for the next major version behind the feature flag. ### Activating the flag -To switch flags on and off you can use the ***.env*** to configure each feature flag. Using dots inside an env variable are not allowed, so we use underscore instead: +To switch flags on and off, you can use the ***.env*** to configure each feature flag. Using dots inside an env variable is not allowed, so we use underscore instead: ```bash V6_5_0_0=1 ``` @@ -33,7 +33,7 @@ V6_5_0_0=1 The feature flag can be used in PHP to make specific code parts only executable when the flag is active. ### Using flags in methods -When there is no option via the container you can use additional helper functions: +When there is no option via the container, you can use additional helper functions: ```php use Shopware\Core\Framework\Feature; @@ -131,7 +131,7 @@ When you want to toggle different parts of the template you can use the flag in ### Using flags in config.xml -When you want to toggle config input fields in config.xml like [basicInformatation.xml](https://gitlab.shopware.com/shopware/6/product/platform/-/blob/trunk/src/Core/System/Resources/config/basicInformation.xml), you can add a `flag` element like this: +When you want to toggle config input fields in config.xml like [basicInformation.xml](https://gitlab.shopware.com/shopware/6/product/platform/-/blob/trunk/src/Core/System/Resources/config/basicInformation.xml), you can add a `flag` element like this: ```xml diff --git a/resources/references/adr/2022-02-09-controller-configuration-route-defaults.md b/resources/references/adr/2022-02-09-controller-configuration-route-defaults.md index 2a1234305..ec8aa4375 100644 --- a/resources/references/adr/2022-02-09-controller-configuration-route-defaults.md +++ b/resources/references/adr/2022-02-09-controller-configuration-route-defaults.md @@ -3,7 +3,7 @@ title: Move controller level annotation into Symfony route annotation date: 2022-02-09 area: core tags: [annotations, controller, route, defaults] ---- +--- # Move controller level annotation into Symfony route annotation @@ -15,17 +15,17 @@ You can find the original version [here](https://github.com/shopware/platform/bl ## Context Annotations are used to configure controllers in the core currently. -The configuration can contain following as example: +The configuration can contain the following as example: - @LoginRequired - - Customer needs to be loggedin + - Customer needs to be logged in - @Acl - Protects the controller with special acl privileges - @RouteScope - Defines the scope of the route - and many more -As Annotations are bound to the implementing class, all decorators have to copy and be update to date with the target class +As Annotations are bound to the implementing class, all decorators have to copy and be updated to date with the target class ## Decision @@ -48,9 +48,9 @@ public function myAction() public function myAction() ``` -Symfony passes then the defaults to the attribute bag of the Request object and we can check the attributes in the request cycle of the http kernel. +Symfony passes the defaults to the attribute bag of the Request object, and we can check the attributes in the request cycle of the http kernel. -Following annotations will be replaced: +The following annotations will be replaced: - `@Captcha` -> `_captcha` - `@LoginRequired` -> `_loginRequired` - `@Acl` -> `_acl` diff --git a/resources/references/adr/2022-02-21-rule-scripting-in-apps.md b/resources/references/adr/2022-02-21-rule-scripting-in-apps.md index 6c284f9f4..b6f508264 100644 --- a/resources/references/adr/2022-02-21-rule-scripting-in-apps.md +++ b/resources/references/adr/2022-02-21-rule-scripting-in-apps.md @@ -1,9 +1,9 @@ --- title: Rule Scripting in apps date: 2022-02-21 -area: business-ops +area: services-settings tags: [rule, app-system, app-scripts] ---- +--- # Rule Scripting in apps @@ -59,7 +59,7 @@ class AppScriptConditionDefinition extends EntityDefinition ### `ScriptRule` implementation -There will be a generice extension of `Rule` named `ScriptRule` which will be used for every condition added by apps. +There will be a generic extension of `Rule` named `ScriptRule` which will be used for every condition added by apps. It has properties for the `script` and the `constraints`, both of which will be set from the corresponding values of `app_script_condition` when the rule's payload is indexed. @@ -333,5 +333,5 @@ Component.extend('sw-condition-script', 'sw-condition-base', { ## Consequences -- Apps will be able to provide their own custom rule conditions, which will consequently be available in the administation's rule builder as any of the hard-coded rule conditions are. +- Apps will be able to provide their own custom rule conditions, which will consequently be available in the administration's rule builder as any of the hard-coded rule conditions are. - Rule scripting, first implemented for apps, eventually opens the door for scripting of rule conditions within the administration, e.g. by providing a code editor in the rule builder. diff --git a/resources/references/adr/2022-02-24-domain-exceptions.md b/resources/references/adr/2022-02-24-domain-exceptions.md index 06c7a929a..fcf21ad1e 100644 --- a/resources/references/adr/2022-02-24-domain-exceptions.md +++ b/resources/references/adr/2022-02-24-domain-exceptions.md @@ -3,7 +3,7 @@ title: Domain exceptions date: 2022-02-24 area: core tags: [architecture, exception, domain] ---- +--- # Domain exceptions @@ -18,8 +18,13 @@ Until now, we have implemented many different exception classes in Shopware to m However, this pattern is very cumbersome for developers to maintain properly, which is why we often fall back on the old \RuntimeException. Another disadvantage of this pattern is that the system is overwhelmed with exception classes and therefore the overview of possible exceptions suffers. +Domain exceptions should be specific in 99% of cases, otherwise, they are no longer clearly identifiable and traceable. If we want to add a generic exception like EntityNotFound exceptions everywhere, it will not help API consumer to identify the root cause. +Therefore, it is for a good reason that there are similar exceptions occur again in many places. If something goes wrong from anywhere, there should be a unique code for it. +In good software, you have a unique code for each error. This code is then listed in a code list that is publicly available. +For each code, there is clear documentation of when and where it occurs and how to fix it. + ## Solution -With the following pattern I would like to achieve the following goals: +With the following pattern, we would like to achieve the following goals: - Developers can **no longer** just throw any **\RuntimeException** that can't be traced. - Each exception has its **own error code**, which is passed to external APIs - We **reduce the number of exception classes** we don't react to in the system (e.g. `\InvalidArgumentException`) @@ -53,7 +58,7 @@ class CmsException extends HttpException } ``` -However, the DomainExceptions are not (necessarily) made to be caught and handled in a try-catch. Therefore, we will continue to implement own exception classes, for exceptions that we want to catch ourselves in the system via a `try-catch`, which extends the `DomainException`. These exceptions are then stored in an exception sub folder: +However, the DomainExceptions are not (necessarily) made to be caught and handled in a try-catch. Therefore, we will continue to implement our own exception classes, for exceptions that we want to catch ourselves in the system via a `try-catch`, which extends the `DomainException`. These exceptions are then stored in an exception subfolder: ```php ``` -That way symfony will reset the data between requests automatically. +That way, symfony will reset the data between requests automatically. Additionally, we've added a hook to our `IntegrationTestBehaviour`, that will also reset that state between the execution of test cases. diff --git a/resources/references/adr/2022-03-15-extract-data-handling-classes-to-extension-sdk.md b/resources/references/adr/2022-03-15-extract-data-handling-classes-to-extension-sdk.md index 18e9b3a72..5642c265a 100644 --- a/resources/references/adr/2022-03-15-extract-data-handling-classes-to-extension-sdk.md +++ b/resources/references/adr/2022-03-15-extract-data-handling-classes-to-extension-sdk.md @@ -3,7 +3,7 @@ title: Extract data handling classes to extension sdk date: 2022-03-15 area: administration tags: [admin, extension-api] ---- +--- # Extract data handling classes to extension sdk @@ -13,13 +13,13 @@ You can find the original version [here](https://github.com/shopware/platform/bl {% endhint %} ## Context -* The package `@shopware-ag/admin-extension-sdk` will be referred to as sdk +* The package `@shopware-ag/meteor-extension-sdk` will be referred to as sdk * The ts/js implementation of the Administration is referred to as administration Previously the administration held the implementation of the classes `Entity`, `EntityCollection` and `Criteria`. -This led to the problem, that the sdk was unable to identify instances of this classes easily. -Since the administration is not a standalone package which could be imported in the sdk. -Also, the sdk would need to copy the implementation since we want to copy the administration datahandling in the sdk. +This led to the problem, that the sdk was unable to identify instances of these classes easily. +Since the administration is not a standalone package that could be imported in the sdk. +Also, the sdk would need to copy the implementation since we want to copy the administration data handling in the sdk. ## Decision Move the implementation of `Entity`, `EntityCollection` and `Criteria` to the sdk. @@ -27,4 +27,4 @@ The corresponding files in the administration simply forward the default export ## Consequences This will result in the same behaviour for current implementations. -On the other hand it provides the benefit of having these basic classes in an external package anybody can use. +On the other hand, it provides the benefit of having these basic classes in an external package anybody can use. diff --git a/resources/references/adr/2022-03-17-new-nested-line-items.md b/resources/references/adr/2022-03-17-new-nested-line-items.md index 5ffc3f4ef..4ffa84f55 100644 --- a/resources/references/adr/2022-03-17-new-nested-line-items.md +++ b/resources/references/adr/2022-03-17-new-nested-line-items.md @@ -3,7 +3,7 @@ title: New templates for line items and nested line items date: 2022-03-17 area: storefront tags: [storefront, line-items, checkout] ---- +--- # New templates for line items and nested line items diff --git a/resources/references/adr/2022-03-29-specify-priority-of-translations-in-dal-write-payloads.md b/resources/references/adr/2022-03-29-specify-priority-of-translations-in-dal-write-payloads.md index 2d8c03bab..802b0f34f 100644 --- a/resources/references/adr/2022-03-29-specify-priority-of-translations-in-dal-write-payloads.md +++ b/resources/references/adr/2022-03-29-specify-priority-of-translations-in-dal-write-payloads.md @@ -3,7 +3,7 @@ title: Specify priority of translations in DAL write payloads date: 2022-03-29 area: core tags: [dal, translations] ---- +--- # Specify priority of translations in DAL write payloads diff --git a/resources/references/adr/2022-04-06-add-default-cms-layouts-to-products-and-categories.md b/resources/references/adr/2022-04-06-add-default-cms-layouts-to-products-and-categories.md index 388d96acc..3c494ced8 100644 --- a/resources/references/adr/2022-04-06-add-default-cms-layouts-to-products-and-categories.md +++ b/resources/references/adr/2022-04-06-add-default-cms-layouts-to-products-and-categories.md @@ -3,7 +3,7 @@ title: Add default cms pages to products and categories date: 2022-04-06 area: content tags: [cms, product, category] ---- +--- # Add default cms pages to products and categories diff --git a/resources/references/adr/2022-04-19-integrate-app-into-flow-action.md b/resources/references/adr/2022-04-19-integrate-app-into-flow-action.md index 22f17b762..33a82c806 100644 --- a/resources/references/adr/2022-04-19-integrate-app-into-flow-action.md +++ b/resources/references/adr/2022-04-19-integrate-app-into-flow-action.md @@ -1,9 +1,9 @@ --- title: Integrate an app into flow action date: 2022-04-19 -area: business-ops +area: services-settings tags: [flow, app, flow-action] ---- +--- # Integrate an app into flow action @@ -55,4 +55,3 @@ A complete XML structure looks like this: ``` - diff --git a/resources/references/adr/2022-05-12-remove-static-analysis-with-psalm.md b/resources/references/adr/2022-05-12-remove-static-analysis-with-psalm.md index 6f91bf8ce..14feb98fa 100644 --- a/resources/references/adr/2022-05-12-remove-static-analysis-with-psalm.md +++ b/resources/references/adr/2022-05-12-remove-static-analysis-with-psalm.md @@ -3,7 +3,7 @@ title: Remove static analysis with psalm date: 2022-05-12 area: core tags: [phpstan, psalm, static-analyse] ---- +--- # Remove static analysis with psalm diff --git a/resources/references/adr/2022-05-23-rule-condition-field-abstraction.md b/resources/references/adr/2022-05-23-rule-condition-field-abstraction.md index 67da1264c..e4d4a5beb 100644 --- a/resources/references/adr/2022-05-23-rule-condition-field-abstraction.md +++ b/resources/references/adr/2022-05-23-rule-condition-field-abstraction.md @@ -1,9 +1,9 @@ --- title: Rule condition field abstraction date: 2022-05-23 -area: business-ops +area: services-settings tags: [rule, abstraction, administration] ---- +--- # Rule condition field abstraction @@ -44,6 +44,3 @@ Starting from now, newly introduced rule conditions will make use of the `Rule:: The original components of conditions are being deprecated and marked to be removed by the next major release. If you used or extended any of these components, use/extend `sw-condition-generic` or `sw-condition-generic-line-item` instead and refer to `this.condition.type` to introduce changes for a specific type of condition. - - - diff --git a/resources/references/adr/2022-06-17-integrate-app-into-flow-event.md b/resources/references/adr/2022-06-17-integrate-app-into-flow-event.md new file mode 100644 index 000000000..db56c7283 --- /dev/null +++ b/resources/references/adr/2022-06-17-integrate-app-into-flow-event.md @@ -0,0 +1,266 @@ +--- +title: Integrate an app into the flow event +date: 2022-10-11 +area: services-settings +tags: [flow, app] +--- + +# Integrate an app into the flow event + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2022-06-17-integrate-app-into-flow-event.md) +{% endhint %} + +# 2022-10-11 - Integrate an app into the flow event + +## Context +Currently, apps can not extend the list of available events in the flow builder. + +## Decision +We update the flow builder so the apps can expand the list of available trigger events in the flow builder UI. + +### Flow aware +We define flow aware classes to detect which data will be available in the event and a function to get them. + +**Problems:** +* We are unsure which data will the app event provide + +**Solution:** +* We create an interface CustomAppAware that will use as implementation for the custom event from the app. + +**Example pseudocode** +```php +interface CustomAppAware +{ + public const APP_DATA = 'customAppData'; + + public function getCustomAppData(): array; +} +``` + +### Flow storer +Flow data storer saves the data from the event as the [StorableFlow](../adr/2022-07-21-adding-the-storable-flow-to-implement-delay-action-in-flow-builder.md), and we use them in flow actions. + +**Problems:** +* Currently, we keep the event data in the core but do not store any personalized event data from the application. + +**Solution:** +* We create a CustomAppStorer, which is used to store the data from custom app event. +* When the API triggers a custom trigger, the data in the body will be stored in FlowStore by their keys. + +*Example to define data from the API:* +```json + { + "customerId": "d20e4d60e35e4afdb795c767eee08fec", + "salesChannelId": "55cb094fd1794d489c63975a6b4b5b90", + "shopName": "Shopware's Shop", + "url": "https://shopware.com" + } +``` + +*After that, at actions we can get data thought FlowStorer.* +```php + $salesChanelId = $flow->getData(MailAware::SALES_CHANNEL_ID)); + $customer = $flow->getData(CustomerAware::CUSTOMER_ID)); +``` + +*Or we can use the data when defining the email template.* +```html +

Welcome to {{ shopName }}

+

Visit us at: {{ url }}

+``` + +**Example pseudocode** +```php +class CustomAppStore extends FlowStorer +{ + public function store(FlowEventAware $event, array $stored): array + { + //check if $event is an instance of CustomAppAware + foreach ($event->getCustomAppData() as $key => $data) { + $stored[ScalarValuesAware::STORE_VALUES][$key] = $data; + $stored[$key] = $data; + } + } + + public function restore(StorableFlow $storable): void + { + return; + } +} +``` + +### Flow Events +Events must implement FlowEventAware to be able to available in the flow builder triggers. + +**Problems:** +* We do not possess any `FlowEventAware` event instances that app developers can utilize for custom triggers to be dispatched or triggered from an app. + +**Solution:** +* We create a new CustomAppEvent class that can be triggered by the App system. + +**Example pseudocode** +```php +class CustomAppEvent extends Event implements CustomAppAware, FlowEventAware +{ + private string $name; + + private array $data; + + // __construct() + //getters +} +``` + +### BusinessEventCollector +BusinessEventCollector collects events that implemented FlowEventAware and output to flow builder. + +**Problems:** +* We currently collect events that implemented FlowEventAware. So the collector does not contain the events from the activated app. + +**Solution:** +* We will collect all `CustomAppEvent` events from activated apps. + +**Example pseudocode** +```php +public function collect(Context $context): BusinessEventCollectorResponse +{ + //fetch app event + $this->fetchAppEvents(new BusinessEventCollectorResponse) +} + +private function fetchAppEvents(BusinessEventCollectorResponse $result): BusinessEventCollectorResponse +{ + //check valid app events from the database + return $this->createCustomAppEvent(); +} + +private function createCustomAppEvent(): CustomAppEvent +{ + // return new CustomAppEvent +} +``` + +### Trigger app custom events API +We will provide an APIs to trigger CustomAppEvent. + +**Problems:** +* Currently, the events are provided and triggered from the core when the user performs specific actions from the storefront or admin, like checkout order or user recovery. 3rd parties can not add custom triggers and trigger them by themself. + +**Solution:** +* We will provide an API. The app calls the API to trigger the custom event and needs to provide the event name and the data. The API will create a CustomAppEvent object and dispatch it with the information provided. + +**Example pseudocode** +```php + /** + * @Since("6.5.2.0") + */ + #[Route(path: '/api/_action/trigger-event/{eventName}', name: 'api.action.trigger_event', methods: ['POST'])] + public function flowCustomTrigger(string $eventName, Request $request, Context $context): JsonResponse + { + $data = $request->request->all(); + + $criteria = new Criteria([$data['flowAppEventId']]) + $criteria->addFilter(new EqualsFilter('appId', $data['flowId'])); + $criteria->addFilter(new EqualsFilter('app.active', 1)); + + $flowEvent = $flowAppEventRepository->search($criteria); + //return http status code 404 if $flowEvent is empty + + $this->eventDispatcher->dispatch(new CustomAppEvent($flowEvent->getName(), $data)); + //return http status code 200 and success message + } + +``` + +## Defining an App flow event in Xml +The flow events are configured in a `/src/Resources/flow.xml` file. We can store the following information for a flow event, Also, we can define more than one event in one app: + +1. `` - The technical name - is unique and should be prefixed with the app vendor prefix, used when dispatching CustomAppEvent.php. +2. `` - Use for deciding what flow actions will be allowed to show after the event. + + - The list of aware supported following: + - `orderAware` + - `customerAware` + - `mailAware` + - `userAware` + - `salesChannelAware` + - `productAware` + - `customerGroupAware` + + - _Example:_ + + _`orderAware`_ + + _We will have a list of actions related to Order that can be selected at the flow below:_ + + - action.add.order.tag, + - action.remove.order.tag, + - action.generate.document, + - action.grant.download.access, + - action.set.order.state, + - action.add.order.affiliate.and.campaign.code, + - action.set.order.custom.field, + - action.stop.flow + + _`customerAware`_ + + _We will have a list of actions related to Customer that can be selected at the flow below:_ + + - action.add.customer.tag + - action.remove.customer.tag + - action.change.customer.group + - action.change.customer.status + - action.set.customer.custom.field + - action.add.customer.affiliate.and.campaign.code + - action.stop.flow + +A complete XML structure looks like this: +```xml + + + + swag.before.open.the.doors + customerAware + orderAware + + + ... + + + +``` + +## Defining translated +We support defining translation for custom trigger events to show in the trigger tree and the trigger's name in the flow list. + +* We will create the snippet file in folder `/src/Resources/app/administration/snippet/`. The structure of the snippet should follow some principles below: + * `sw-flow-custom-event` is a fixed key instance for snippets using at the trigger event. + * `event-tree` is a fixed key. The keys are defined inside this key based on the specified trigger name at `name` in `flow.xml` used to translate in trigger tree. + * `flow-list` is a fixed key, The keys defined inside the key based on the trigger name defined at `name` in `flow.xml` used to translate in the trigger tree. + **Example pseudocode** + ```json + { + "sw-flow-custom-event": { + "event-tree": { + "swag": "Swag", + "before": "Before", + "openTheDoors": "Open the doors" + }, + "flow-list": { + "swag_before_open_the_doors": "Before open the doors" + } + } +} +``` + +## Database migration +* We will create a new table `app_flow_event` to save defined data from the `/src/Resources/flow.xml` file. +* The table will have columns like bellow: + * `id` BINARY(16) NOT NULL, + * `app_id` BINARY(16) NOT NULL, + * `name` VARCHAR(255) NOT NULL UNIQUE, + * `aware` JSON NOT NULL, + * `created_at` DATETIME(3) NOT NULL, + * `updated_at` DATETIME(3) NULL, diff --git a/resources/references/adr/2022-06-24-add-typescript-support-for-storefront-js.md b/resources/references/adr/2022-06-24-add-typescript-support-for-storefront-js.md index bc4b24218..0f32df861 100644 --- a/resources/references/adr/2022-06-24-add-typescript-support-for-storefront-js.md +++ b/resources/references/adr/2022-06-24-add-typescript-support-for-storefront-js.md @@ -3,7 +3,7 @@ title: Add typescript support for storefront javascript date: 2022-06-24 area: storefront tags: [storefront, typescript, javascript] ---- +--- # Add typescript support for storefront javascript diff --git a/resources/references/adr/2022-06-27-providing-the-admin-extension-sdk.md b/resources/references/adr/2022-06-27-providing-the-admin-extension-sdk.md index 0385611bc..2c3e5eb01 100644 --- a/resources/references/adr/2022-06-27-providing-the-admin-extension-sdk.md +++ b/resources/references/adr/2022-06-27-providing-the-admin-extension-sdk.md @@ -2,7 +2,7 @@ title: Providing the admin extension SDK date: 2022-06-15 area: administration -tags: [admin-extension-sdk, vue] +tags: [meteor-extension-sdk, vue] --- # Providing the admin extension SDK @@ -12,6 +12,10 @@ This document represents an architecture decision record (ADR) and has been mirr You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2022-06-27-providing-the-admin-extension-sdk.md) {% endhint %} +::: warning +The Admin Extension SDK has been renamed to Meteor Extension SDK. +::: + ## Context The Admin Extension SDK is a toolkit for plugin and app developers to extend or modify the administration via their plugins or apps. The SDK contains easy to use methods which interact with the administration in the background via the PostMessage API for iFrames. diff --git a/resources/references/adr/2022-07-19-blog-concept.md b/resources/references/adr/2022-07-19-blog-concept.md index 9bd9a6b8a..d9da08f82 100644 --- a/resources/references/adr/2022-07-19-blog-concept.md +++ b/resources/references/adr/2022-07-19-blog-concept.md @@ -13,7 +13,7 @@ You can find the original version [here](https://github.com/shopware/platform/bl {% endhint %} ## Context -A highly requested feature & expected for the CMS of Shopware 6 is the blog. In addition, this is an ideal use case and example for the use of [Custom Entities](../app/2021-09-14-technical-concept-custom-entities.md). +A highly requested feature & expected for the CMS of Shopware 6 is the blog. In addition, this is an ideal use case and example for the use of [Custom Entities](../adr/2021-09-14-technical-concept-custom-entities.md). ## Decision We want to implement blogs as part of the CMS. Since Custom Entities offers a good chance to implement such a feature, the blog is supposed to show the community how to handle Custom Entities in the Shopping Experiences. diff --git a/resources/references/adr/2022-07-21-adding-the-storable-flow-to-implement-delay-action-in-flow-builder.md b/resources/references/adr/2022-07-21-adding-the-storable-flow-to-implement-delay-action-in-flow-builder.md index 1bedb1636..2dd9a1016 100644 --- a/resources/references/adr/2022-07-21-adding-the-storable-flow-to-implement-delay-action-in-flow-builder.md +++ b/resources/references/adr/2022-07-21-adding-the-storable-flow-to-implement-delay-action-in-flow-builder.md @@ -1,7 +1,7 @@ --- title: Adding the `StorableFlow` instead of the `FlowEvent` for implementing the flow DelayAction in flow builder date: 2022-07-21 -area: business-ops +area: services-settings tags: [flow, event, refactoring] --- @@ -135,7 +135,7 @@ class OrderStorer implements FlowStorer } ``` -About the additional data defined in `availabelData` in original events, that aren't defined in any Aware Interfaces and we can't restore that data in the `Storer`. +About the additional data defined in `availableData` in original events, that aren't defined in any Aware Interfaces and we can't restore that data in the `Storer`. To cover the additional data from original events, we will have another `store` `AdditionalStorer` to store those data. ```php class AdditionalStorer extends FlowStorer diff --git a/resources/references/adr/2022-09-23-add-bootstrap-util.md b/resources/references/adr/2022-09-23-add-bootstrap-util.md index fc9741324..6e780e410 100644 --- a/resources/references/adr/2022-09-23-add-bootstrap-util.md +++ b/resources/references/adr/2022-09-23-add-bootstrap-util.md @@ -3,7 +3,7 @@ title: Add bootstrap JS-plugin initialization utility to storefront JS date: 2022-09-23 area: storefront tags: [storefront, javascript, bootstrap] ---- +--- # Add bootstrap JS-plugin initialization utility to storefront JS diff --git a/resources/references/adr/2022-09-28-mapping-of-product-area.md b/resources/references/adr/2022-09-28-mapping-of-product-area.md index 62a52b591..bd7605496 100644 --- a/resources/references/adr/2022-09-28-mapping-of-product-area.md +++ b/resources/references/adr/2022-09-28-mapping-of-product-area.md @@ -3,7 +3,7 @@ title: Mapping of product area date: 2022-09-28 area: product-operations tags: [workflow] ---- +--- # Mapping of product area @@ -30,12 +30,10 @@ The areas are: - core - inventory - checkout -- sales-channel - content -- business-ops -- merchant-services - customer-order -- system-settings +- services-settings +- buyers-experience ## Consequences diff --git a/resources/references/adr/2022-10-20-deprecation-handling-during-phpunit-test-execution.md b/resources/references/adr/2022-10-20-deprecation-handling-during-phpunit-test-execution.md index b926d2bd8..c10e40c2a 100644 --- a/resources/references/adr/2022-10-20-deprecation-handling-during-phpunit-test-execution.md +++ b/resources/references/adr/2022-10-20-deprecation-handling-during-phpunit-test-execution.md @@ -3,7 +3,7 @@ title: Deprecation handling during PHPUnit test execution date: 2022-10-20 area: core tags: [phpunit, deprecation, test] ---- +--- # Deprecation handling during PHPUnit test execution @@ -40,7 +40,7 @@ We leverage this feature by using it in a way to ignore all deprecations that we ### Using our Feature Flag system for internal deprecations -Internally we use the feature flag system to trigger deprecation messages, or throw exceptions if the major feature flag is activated as explained in the [deprecation handling ADR](../workflow/2022-02-28-consistent-deprecation-notices-in-core.md). +Internally we use the feature flag system to trigger deprecation messages, or throw exceptions if the major feature flag is activated as explained in the [deprecation handling ADR](../adr/2022-02-28-consistent-deprecation-notices-in-core.md). We already use that system in our new unit test suite with a custom `@ActiveFeatures()` annotations, that allows us to run single test cases with a specific set of feature flags. But the current implementation has the big drawback that feature flags have to be actively enabled, this leads to following problems: 1. There are already tests that are not passing after all deprecations are removed, because they rely on deprecated behaviour. diff --git a/resources/references/adr/2022-11-09-composer-based-web-updater.md b/resources/references/adr/2022-11-09-composer-based-web-updater.md index 61cacb831..edebf126b 100644 --- a/resources/references/adr/2022-11-09-composer-based-web-updater.md +++ b/resources/references/adr/2022-11-09-composer-based-web-updater.md @@ -3,7 +3,7 @@ title: Composer-based web updater date: 2022-11-09 area: core tags: [composer, plugin, web-updater] ---- +--- # Composer-based web updater diff --git a/resources/references/adr/2022-21-11-replace-drop-shadow-with-box-shadow.md b/resources/references/adr/2022-21-11-replace-drop-shadow-with-box-shadow.md index 1bdb699bd..e91fdfb1f 100644 --- a/resources/references/adr/2022-21-11-replace-drop-shadow-with-box-shadow.md +++ b/resources/references/adr/2022-21-11-replace-drop-shadow-with-box-shadow.md @@ -3,7 +3,7 @@ title: Replace drop-shadow with box-shadow date: 2022-21-11 area: storefront tags: [safari, performance, storefront] ---- +--- # Replace drop-shadow with box-shadow diff --git a/resources/references/adr/2022-25-11-run-lighthouse-test-ine2e-env.md b/resources/references/adr/2022-25-11-run-lighthouse-test-ine2e-env.md index 8d1cfe196..809b0f3d4 100644 --- a/resources/references/adr/2022-25-11-run-lighthouse-test-ine2e-env.md +++ b/resources/references/adr/2022-25-11-run-lighthouse-test-ine2e-env.md @@ -3,7 +3,7 @@ title: Run Lighthouse tests in E2E env date: 2022-21-11 area: storefront tags: [lighthouse, performance, storefront] ---- +--- # Run Lighthouse tests in E2E env diff --git a/resources/references/adr/2023-01-10-atomic-theme-compilation.md b/resources/references/adr/2023-01-10-atomic-theme-compilation.md index 61d75a30b..8bbab83d9 100644 --- a/resources/references/adr/2023-01-10-atomic-theme-compilation.md +++ b/resources/references/adr/2023-01-10-atomic-theme-compilation.md @@ -3,7 +3,7 @@ title: Atomic theme compilation date: 2023-01-10 area: storefront tags: [theme, storefront, performance] ---- +--- # Atomic theme compilation diff --git a/resources/references/adr/2023-01-16-npm-packages-pre-release-versions.md b/resources/references/adr/2023-01-16-npm-packages-pre-release-versions.md index 129b9b2f4..60bdfba57 100644 --- a/resources/references/adr/2023-01-16-npm-packages-pre-release-versions.md +++ b/resources/references/adr/2023-01-16-npm-packages-pre-release-versions.md @@ -3,7 +3,7 @@ title: Npm packages pre-release versions date: 2023-01-16 area: administration tags: [npm, package, pre-release] ---- +--- # Npm packages pre-release versions diff --git a/resources/references/adr/2023-01-30-image-lazy-loading.md b/resources/references/adr/2023-01-30-image-lazy-loading.md index 40cd4f446..c4be39ab9 100644 --- a/resources/references/adr/2023-01-30-image-lazy-loading.md +++ b/resources/references/adr/2023-01-30-image-lazy-loading.md @@ -3,7 +3,7 @@ title: Add native lazy loading for images to the storefront date: 2023-01-30 area: storefront tags: [image, lazy-loading, storefront] ---- +--- # Add native lazy loading for images to the storefront @@ -29,17 +29,17 @@ You can find the original version [here](https://github.com/shopware/platform/bl * The default is not `lazy` in order to avoid unexpected behaviour for extensions which might have added the thumbnail component while using a JavaScript solution for lazy loading. * We add native lazy loading in appropriate areas to reduce the initial network load: * Main menu flyout: Category preview images will only load when flyout is being opened. - * Product boxes: Product images will only load when they appear in the viewport inside the listing. This also effects product sliders with horizontal scrolling, e.g. cross-selling. - * CMS image elements: CMS layouts will only load images which appear in the viewport (e.g. when scrolling down the page). - * Line item images: Product images in line items (e.g. cart page) will only load when they appear in the viewport. + * Product boxes: Product images will only load when they appear in the viewport inside the listing. This also affects product sliders with horizontal scrolling, e.g. cross-selling. + * CMS image elements: CMS layouts will only load images which appear in the viewport (e.g., when scrolling down the page). + * Line item images: Product images in line items (e.g., cart page) will only load when they appear in the viewport. ### Why don't we just add `loading="lazy"` everywhere? -* Even though this would technically work, there are a few pitfalls which need to be considered. +* Even though this would technically work, there are a few pitfalls that need to be considered. * For example, it is not recommended to add lazy loading to images which are very likely inside the initial viewport when loading the page aka "above-the-fold". Further reading: https://web.dev/browser-level-lazy-loading-for-cmss/#avoid-lazy-loading-above-the-fold-elements * For a system like shopware, where the content is almost entirely dynamic, it is not easy to determine where a generic image component will be rendered. It could have any position on any CMS page. * Even "guesses" like "only add lazy loading after the 8th product in a listing" can be invalid as soon as a monitor is in portrait mode or the viewport changes to mobile. -* Therefore, we live with the small drawback that e.g. all product boxes have lazy loading. Some of them will appear "above-the-fold". However, we still have the benefit of loading images later when scrolling down a page or scrolling in product sliders. +* Therefore, we live with the small drawback that, e.g., all product boxes have lazy loading. Some of them will appear "above-the-fold". However, we still have the benefit of loading images later when scrolling down a page or scrolling in product sliders. * Implementing a JavaScript solution for this would contradict the usage of native lazy loading. #### Areas without loading="lazy"` diff --git a/resources/references/adr/2023-02-01-app-script-product-pricing.md b/resources/references/adr/2023-02-01-app-script-product-pricing.md index 616389bc9..9e9e0caa2 100644 --- a/resources/references/adr/2023-02-01-app-script-product-pricing.md +++ b/resources/references/adr/2023-02-01-app-script-product-pricing.md @@ -3,7 +3,7 @@ title: App script product pricing date: 2023-02-01 area: core tags: [app-script, product, pricing] ---- +--- # App script product pricing @@ -15,52 +15,56 @@ You can find the original version [here](https://github.com/shopware/platform/bl ## Context We want to provide the opportunity to manipulate the price of a product inside the cart and within the store. For the cart manipulation we already have a hook integrated which allows accessing and manipulating the cart. -Right now we are not allowing to manipulate prices directly just over creating discounts or new price objects and add them as new line items into the cart. +Right now we are not allowing to manipulate prices directly but just creating discounts or new price objects and add them as new line items into the cart. -But there are different business cases which requires a directly price manipulation like `get an sample of the product for free` +However, there are different business cases which require a direct price manipulation like `get a sample of the product for free` The following code can be used for manipulating the prices in the product-pricing hook: ```php -{# allow resetting product prices #} -{% do product.calculatedCheapestPrice.reset %} -{% do product.calculatedPrices.reset %} -{# not allowed to RESET the default price otherwise it is not more valid - -{# get control of the default price calculation #} -{% set price = services.prices.create({ - 'default': { 'gross': 20, 'net': 20 }, - 'USD': { 'gross': 15, 'net': 15 }, - 'EUR': { 'gross': 10, 'net': 10 } -}) %} - -{# directly changes the price to a fix value #} -{% do product.calculatedPrice.change(price) %} - -{# manipulate the price and subtract the provided price object #} -{% do product.calculatedPrice.minus(price) %} - -{# manipulate the price and add the provided price object #} -{% do product.calculatedPrice.plus(price) %} - -{# the following examples show how to deal with percentage manipulation #} -{% do product.calculatedPrice.discount(10) %} -{% do product.calculatedPrice.surcharge(10) %} - -{# get control of graduated prices #} -{% do product.calculatedPrices.reset %} -{% do product.calculatedPrices.change([ - { to: 20, price: services.prices.create({ 'default': { 'gross': 15, 'net': 15} }) }, - { to: 30, price: services.prices.create({ 'default': { 'gross': 10, 'net': 10} }) }, - { to: null, price: services.prices.create({ 'default': { 'gross': 5, 'net': 5} }) }, -]) %} - -{# after hook => walk through prices and fix "from/to" values #} -{% do product.calculatedCheapestPrice.change(price) %} -{% do product.calculatedCheapestPrice.minus(price) %} -{% do product.calculatedCheapestPrice.plus(price) %} -{% do product.calculatedCheapestPrice.discount(10) %} -{% do product.calculatedCheapestPrice.surcharge(10) %} +{% foreach hook.products as product %} + {# allow resetting product prices #} + {% do product.calculatedCheapestPrice.reset %} + {% do product.calculatedPrices.reset %} + {# not allowed to RESET the default price otherwise it is not more valid + + {# get control of the default price calculation #} + {% set price = services.prices.create({ + 'default': { 'gross': 20, 'net': 20 }, + 'USD': { 'gross': 15, 'net': 15 }, + 'EUR': { 'gross': 10, 'net': 10 } + }) %} + + {# directly changes the price to a fix value #} + {% do product.calculatedPrice.change(price) %} + + {# manipulate the price and subtract the provided price object #} + {% do product.calculatedPrice.minus(price) %} + + {# manipulate the price and add the provided price object #} + {% do product.calculatedPrice.plus(price) %} + + {# the following examples show how to deal with percentage manipulation #} + {% do product.calculatedPrice.discount(10) %} + {% do product.calculatedPrice.surcharge(10) %} + + {# get control of graduated prices #} + {% do product.calculatedPrices.reset %} + {% do product.calculatedPrices.change([ + { to: 20, price: services.prices.create({ 'default': { 'gross': 15, 'net': 15} }) }, + { to: 30, price: services.prices.create({ 'default': { 'gross': 10, 'net': 10} }) }, + { to: null, price: services.prices.create({ 'default': { 'gross': 5, 'net': 5} }) }, + ]) %} + + {# after hook => walk through prices and fix "from/to" values #} + + {% do product.calculatedCheapestPrice.change(price) %} + {% do product.calculatedCheapestPrice.minus(price) %} + {% do product.calculatedCheapestPrice.plus(price) %} + {% do product.calculatedCheapestPrice.discount(10) %} + {% do product.calculatedCheapestPrice.surcharge(10) %} + +{% endforeach %} ``` The following code can be used to manipulate the prices of a product inside the cart: diff --git a/resources/references/adr/2023-02-02-deprecate-autoload-true-in-dal-associations.md b/resources/references/adr/2023-02-02-deprecate-autoload-true-in-dal-associations.md index d53f490a9..a327615c1 100644 --- a/resources/references/adr/2023-02-02-deprecate-autoload-true-in-dal-associations.md +++ b/resources/references/adr/2023-02-02-deprecate-autoload-true-in-dal-associations.md @@ -3,7 +3,7 @@ title: Deprecate autoloading associations in DAL entity definitions date: 2023-02-02 area: core tags: [dal, performance, api, core] ---- +--- # Deprecate autoloading associations in DAL entity definitions @@ -29,7 +29,7 @@ We have introduced a new PHPStan rule which will check for any usages of autoloa In order to safely migrate core code away from using autoload === true, the following steps should be followed: 1. Document all deprecations in the changelog -2. All internal API's that rely on data that is autoloaded should now specify the association in the criteria objects. +2. All internal APIs that rely on data that is autoloaded should now specify the association in the criteria objects. 3. All entity definitions should be updated to add the association conditionally based on the 6.6 feature flag, see below for an example. 4. In the run-up to the 6.6 release the feature conditional should be removed. @@ -40,7 +40,7 @@ public function defineFields(): FieldCollection $fields = new FieldCollection(...); if (Feature::isActive('v6.6.0.0') { - $fields->add(new ManyToOneAssociationField(..., autoload: false); //or don't specify the autoload flag, the default value is false + $fields->add(new ManyToOneAssociationField(..., autoload: false); } else { $fields->add(new ManyToOneAssociationField(..., autoload: true); } diff --git a/resources/references/adr/2023-02-02-flow-storer-with-scalar-values.md b/resources/references/adr/2023-02-02-flow-storer-with-scalar-values.md index 471045c26..c21a1407f 100644 --- a/resources/references/adr/2023-02-02-flow-storer-with-scalar-values.md +++ b/resources/references/adr/2023-02-02-flow-storer-with-scalar-values.md @@ -3,7 +3,7 @@ title: Flow storer with scalar values date: 2023-02-02 area: core tags: [flow, storer, scalar, deprecation] ---- +--- # Flow storer with scalar values diff --git a/resources/references/adr/2023-02-13-follow-test-pyramid.md b/resources/references/adr/2023-02-13-follow-test-pyramid.md index 109060f11..c4511fc6f 100644 --- a/resources/references/adr/2023-02-13-follow-test-pyramid.md +++ b/resources/references/adr/2023-02-13-follow-test-pyramid.md @@ -3,7 +3,7 @@ title: Follow test pyramid date: 2023-02-16 area: product-operations tags: [test, structure, performance, flakiness] ---- +--- # Follow test pyramid diff --git a/resources/references/adr/2023-02-20-unstructured-adrs.md b/resources/references/adr/2023-02-20-unstructured-adrs.md index 1708ce3b5..662f8e333 100644 --- a/resources/references/adr/2023-02-20-unstructured-adrs.md +++ b/resources/references/adr/2023-02-20-unstructured-adrs.md @@ -3,7 +3,7 @@ title: Unstructured ADRs date: 2023-02-23 area: core tags: [adr, file-structure, workflow] ---- +--- # Unstructured ADRs diff --git a/resources/references/adr/2023-04-01-mocking-repositories.md b/resources/references/adr/2023-04-01-mocking-repositories.md index 3536ae881..93a71edcd 100644 --- a/resources/references/adr/2023-04-01-mocking-repositories.md +++ b/resources/references/adr/2023-04-01-mocking-repositories.md @@ -3,7 +3,7 @@ title: Mocking repositories date: 2023-01-04 area: core tags: [testing, core, repository] ---- +--- # Mocking repositories @@ -13,9 +13,9 @@ You can find the original version [here](https://github.com/shopware/platform/bl {% endhint %} ## Context -Right now it is complicated to test classes which has a dependency on a repository. This is because mocking a repository `search` or `searchIds` call requires creating empty `EntitySearchResults` or `IdSearchResults`. This leads to much boilerplate code when writing tests and faking database results. For this reason we should provide a way to mock the `search` and `searchIds` calls in a much easier way. +Right now it is complicated to test classes which have a dependency on a repository. This is because mocking a repository `search` or `searchIds` call requires creating empty `EntitySearchResults` or `IdSearchResults`. This leads to much boilerplate code when writing tests and faking database results. For this reason we should provide a way to mock the `search` and `searchIds` calls in a much easier way. -Faking a search results of a repository looks like this at the moment: +Faking a search result of a repository looks like this at the moment: ```php $result = new EntitySearchResult( diff --git a/resources/references/adr/2023-04-11-new-language-inheritance-mechanism-for-opensearch.md b/resources/references/adr/2023-04-11-new-language-inheritance-mechanism-for-opensearch.md new file mode 100644 index 000000000..721227048 --- /dev/null +++ b/resources/references/adr/2023-04-11-new-language-inheritance-mechanism-for-opensearch.md @@ -0,0 +1,201 @@ +--- +title: New language inheritance mechanism for opensearch +date: 2023-04-11 +area: Core,Elasticsearch +tags: [Elasticsearch,Opensearch,Multilingual search] +--- + +# New language inheritance mechanism for opensearch + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-04-11-new-language-inheritance-mechanism-for-opensearch.md) +{% endhint %} + +## Context + +Currently, when using Elasticsearch for searching on storefront, we are creating multiple indexes of each language. This would be fine till now however there are a few problems with it: + +- We need to manage multiple indexes, if the shop's using multilingual, we need to create several indexes for each language, this is a big problem on cloud especially +- "Indices and shards are therefore not free from a cluster perspective, as there is some level of resource overhead for each index and shard." +- Everytime a record is updated, we need to update that record in every language indexes +- There's currently no fallback mechanism when searching, therefor duplicating default language data for each index is needed, but not every field is translatable, this take more storage for each index + +## Decision + +### New feature flag + +We introduce a new feature flag `ES_MULTILINGUAL_INDEX` to allow people to opt in to the new multilingual ES index immediately. + +### New Elasticsearch data mapping structure + +We changed the approach to Multilingual fields strategy following these criteria + +1. Each searchable entity now have only one index for all languages (e.g sw_product) +2. Each translated field will be mapped as an `object field`, each language_id will be a key in the object +3. When searching for these fields, use multi-match search with ., . and . as fallback, this way we have a fallback mechanism without needing duplicate data +4. Same logic applied when sorting with the help of a painless script (see 3.Sorting below) +5. When a new language is added or a record is update, we do a partial update instead of replacing the whole document, this will reduce the request update payload and thus improve indexing performance overall + +Example: + +### 1. Create mapping setting + +**OLD structure** + +```json +// PUT /sw_product +{ + "mappings": { + "properties": { + "productNumber": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "text": { + "type": "text" + }, + "ngram": { + "type": "text", + "analyzer": "sw_ngram_analyzer" + } + } + } + } + } +} +``` + +**NEW structure** + +```json +// PUT /sw_product/_mapping +{ + "mappings": { + "properties": { + "productNumber": { + "type": "keyword" + }, + "name": { + "properties": { + "en": { + "type": "keyword", + "fields": { + "text": { + "type": "text", + "analyzer": "sw_english_analyzer" + }, + "ngram": { + "type": "text", + "analyzer": "sw_ngram_analyzer" + } + } + }, + "de": { + "type": "keyword", + "fields": { + "text": { + "type": "text", + "analyzer": "sw_german_analyzer" + }, + "ngram": { + "type": "text", + "analyzer": "sw_ngram_analyzer" + } + } + } + } + } + } + } +} +``` + +### 2. Searching + +Assume we're searching products in german + +```json +// GET /sw_product/_search +{ + "query": { + "multi_match": { + "query": "some keyword", + "fields": [ + "title.de.search", // context languge + "title.en.search" // fallback language + ], + "type": "best_fields" + } + } +} +``` + +### 3. Sorting + +We add new painless scripts in `Framework/Indexing/Scripts/translated_field_sorting.groovy` and `Framework/Indexing/Scripts/numeric_translated_field_sorting.groovy`, this script then will be used when sorting + +**Example: Sort products by name in DESC** + +```json +// GET /sw_product/_search +{ + "query": { + ... + }, + "sort": [ + { + "_script": { + "type": "string", + "script": { + "id": "translated_field_sorting", + "params": { + "field": "name", + "languages": [ + "119317f1d1d1417c9e6fb0059c31a448", // context language + "2fbb5fe2e29a4d70aa5854ce7ce3e20b" // fallback language + ] + } + }, + "order": "DESC" + } + } + ] +} +``` + +## Adding a new language + +- When a new language is created, we perform this request to update mapping includes new added language + +```json +// PUT /sw_product/_mapping +{ + "properties": { + "name": { + "properties": { + "": { + "type": "keyword", + "fields": { + "text": { + "type": "text", + "analyzer": "" + }, + "ngram": { + "type": "text", + "analyzer": "sw_ngram_analyzer" + } + } + } + } + } + } +} +``` + +## Consequences + +- From the next major version, old language based indexes will not be used any longer thus could be removed on es cluster +- When the feature is activated, the shop must reindex using command `bin/console es:index` in the next update diff --git a/resources/references/adr/2023-05-09-optimise-cart-cleanup.md b/resources/references/adr/2023-05-09-optimise-cart-cleanup.md new file mode 100644 index 000000000..00d4c6657 --- /dev/null +++ b/resources/references/adr/2023-05-09-optimise-cart-cleanup.md @@ -0,0 +1,44 @@ +--- +title: Optimize cart cleanup +date: 2023-05-09 +area: core +tags: [performance] +--- + +# Optimize cart cleanup + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-05-09-optimise-cart-cleanup.md) +{% endhint %} + +## Context + +The existing SQL snippet to delete the outdated cart entries doesn't use any database index to narrow down entries that can be deleted. +On high traffic shops this leads to SQL query times larger than 30 seconds to find and remove these database entries. + +Running +``` +EXPLAIN DELETE FROM cart +WHERE (updated_at IS NULL AND created_at <= '2023-02-01') + OR (updated_at IS NOT NULL AND updated_at <= '2023-02-01') LIMIT 1000; +``` +shows that the original sql query doesn't use an index (`possible_keys` = `NULL`) + +## Decision + +Reorder the query parameters so that the relevant cart entries can be narrowed down by an indexed field. + +Testing the new SQL snippet by running +``` +EXPLAIN DELETE FROM cart + WHERE created_at <= '2023-02-01' + AND (updated_at IS NULL OR updated_at <= '2023-02-01') LIMIT 1000; +``` +shows that the new query uses an index (`possible_keys` = `idx.cart.created_at`). + + +## Consequences + +The logic stays the same but the amount of time needed to find the record drops +dramatically, so the change results in a better performance during cart cleanup. diff --git a/resources/references/adr/2023-05-10-experimental-features.md b/resources/references/adr/2023-05-10-experimental-features.md new file mode 100644 index 000000000..be7dc718b --- /dev/null +++ b/resources/references/adr/2023-05-10-experimental-features.md @@ -0,0 +1,172 @@ +--- +title: Experimental features +date: 2023-05-10 +area: core, administration, storefront +tags: [process, backwards compatibility] +--- + +# Experimental features + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-05-10-experimental-features.md) +{% endhint %} + +## Context + +Currently, it is hard to publish features in an early state to gather feedback regarding those features. If they are useful, what needs to be improved etc. +One major reason is that everything we publish (that is not marked as internal) is part of our backwards compatibility promise, thus changing foundational parts of features is quite hard after first release. +That leads to features being developed over quite some time without getting actual feedback from users or being able to release them, as they need to be implemented to a pretty final state in order to confidently release them in a stable manner, where we will keep backwards compatibility. + +This at the same time also means that the current approach is not beneficial to our ecosystem, whom the whole backwards compatibility promise should benefit, because the features are built behind closed curtains they can't chime in with ideas and use cases regarding extendability, etc. + +Examples of features that could benefit from an earlier experimental release: +* B2B: + * We could release the multi account B2B feature with a "simple" employee management system first, and then add the more complex budget management or access control on top of that later. +* Advanced Search: + * We could release a first version of the new advanced search feature, without all configuration options and customizability that we might envision. + +In both cases, releasing the first increments of the features without an "experimental"-flag would mean that we would have to keep backwards compatibility for the whole feature, even if we later decide to change the implementation of the feature, thus making the development of the feature harder. +Also in getting feedback from the customers what additional functionalities are needed after we released the first foundational increment of the feature, we can base our further prioritization on real customer feedback. +Thus, we can ship business value sooner to our customers and lower the risk of building the wrong thing. + +## Decision + +To ship features earlier, we add the concept of "experimental" features, thus giving early access to meaningful increments of features that are still in active development. +That means in particular that there is no backwards compatibility promise for experimental features, thus we can change the implementation as is needed, without having to worry about breaking changes. +We mark the code for those features with a new `experimental` annotation, to make it clear on code level that the API is **not yet** stable. +For code where already expect that it should never become part of the public API we will use the `@internal` annotation directly, to make sure that even if the feature is stable we will continue to tread those parts of the code as internal and not keep backwards compatible. +Everything that is marked with `@experimental` is designed to be part of the public API, when the feature is stable. + +At the same time, it offers a way for the ecosystem to give early feedback on the feature, as well as to test it in their own projects. Especially, extension developers can check how they might want to integrate and extend the feature being built, and thus suggest the needed extension points during the development process. +To make this possible that means that there also will be documentation (API docs, dev docs and user docs) for experimental features + +All experimental features are developed with a specific target version, beginning with that version, the feature is considered stable, and the APIs will be kept backwards compatible. +This means that `experimental` annotation/attribute have to be removed, before the version can be released. Because it is hard to estimate exactly with which release a feature may be stable (as it also depends on the feedback we get) it makes sense to mark them as being stable with the next major version. +That does not mean that the feature won't be finished and stable earlier (we can remove the experimental status with any minor version), it only means that at the latest with that version it is considered stable, this prevents a situation where a lot of features stay in the experimental state for a long time. + +### Our experimental promise + +Experimental features don't compromise in terms of quality or any other guidelines we have, that means experimental features are production ready. +While the UI and processes and functionalities of a single feature may change considerably during the experimental phase, we won't discard any data that was generated when the feature was actively used in a previous stage, meaning that even if there are changes to the underlying data, we will migrate the existing data. +This ensures that customers using an early version of the feature can continue working with that feature. + +As said earlier experimental features do not hone our backwards compatibility promise, allowing us to react more flexibly to the feedback we gather based on the earlier iterations of the feature. + +### Killing a feature + +It may happen that during development of a feature we get the feedback that our feature idea does not provide the value we expected, if that is the case we may kill a feature again. +If that is the case, we will mark the feature as deprecated for the next major version, so even if the feature was marked as experimental and does not fall under the backwards compatible promise we will not remove a experimental feature with a minor version. We will only kill the feature for the next major version, and announce the deprecation as soon as possible. + +This is also important as features can't stay in the experimental state forever, that means either they are further developed to a stable state, or they are killed to the next major version. + +### How does this compare to the "old" feature flag approach? + +With the old feature flag approach work-in-progress code was hidden with a feature flagging mechanism. That meant that code that was not production ready was in the released product, but it was turned off via flag. +Experimental features are neither work in progress, nor finished and finalized features. Whatever is included in an experimental feature is production ready and ready to use for customers, but it may mean that not all functionalities we envision for a feature are ready yet, but those that are can be used standalone. + +# Do you have to opt-in to experimental features or are they always there? + +From a technical perspective, experimental features are always there and they can not be deactivated. This reduces the number of permutations of the system and greatly reduces the overall complexity and thus makes testing, etc. a lot easier. +From the perspective of an external developer, this makes things also more predictable, as externals can rely on the feature being there in a given version, independent of the specific systems configuration, thus it will help in getting real feedback from the ecosystem. + +From a merchants/users perspective, this might not be the case and it might be beneficial for them that some early features are opt-in only. +But as already detailed in the [UI section](#UI) this ADR does not focus on the UI and merchants perspective and leaves that open for a future ADR. +However when the decision will be made to make certain features opt-in for the user we should always built it in a way that only the UI of the new feature is hidden, but from the technical perspective the whole feature is always there. +The opt-in then only makes the entry point to the new feature visible for the user. + +## Consequences +### Core + +We add a `@experimental` annotation, that can be used similar as the `@internal` annotation, to indicate parts of the code (class or method level) that are not yet stable, and thus not covered by the backwards compatibility promise.+ +Additionally all `@experimental` annotation need to have a `stableVersion` property when the feature will be made available as stable at the latest, e.g. `@experimental stableVersion:v6.6.0`. +This means that at the latest with that major version the feature should be stable (or removed), however the `@experimental` annotation can always be removed earlier. As experimental features can be considered as technical debt, we should strive to stabilize features as soon as possible. +When a feature can not be stabilized for the targeted major version, the experimental phase can be extended on a case by case basis. + +There will be a static analysis rule / unit test, that checks that every `@experimental` annotation has the stable version property and there are no `@experimental` annotations for a version that is already released (similar to the test case we have for `@deprecated`). +Additionally, the BC checker needs to be adapted to handle the `@experimental` annotation in the same way as it handles `@internal`. + +We use an annotation here over an attribute because of the following reasons: +* Similarity to other annotations like `@deprecated` and `@internal` +* Symfony also uses an `@experimental` annotation, see [this example](https://github.com/symfony/symfony/blob/6.3/src/Symfony/Component/Webhook/Client/AbstractRequestParser.php#LL23C5-L23C17) and their [documentation for experimental code](https://symfony.com/doc/current/contributing/code/experimental.html) +* The same annotation can be used for PHP, JS and template code +* We don't need to evaluate the annotation at runtime, so using attributes over annotations won't bring that much benefit + +### Database Migrations + +As said earlier data from experimental features needs to be migrated if the underlying structure changes, so that no customer data is lost. +But additionally, we also provide a blue/green compatible migration system, this means that all destructive changes to the DB layout (e.g. dropping a table or column) can only be done in a major version and can not happen immediately. +As blue/green compatibility is a overall system property we can't exclude `@experimental` features from that. + +### API + +API routes and also entity definitions (that automatically will be mapped to the auto-generated CRUD-API) can be marked as experimental, meaning that they are also not covered by the backwards compatibility promise. +The experimental state then will be reflected in the OpenAPI definition for those routes. +To do so add the `Experimental` tag to the OpenApi definition of the route and add a hint that that route currently still is experimental in the summary for that route, and use the `@experimental` annotation on the entity definition class. + +### Admin + +Modules, Components, Services, etc. can be marked as experimental, meaning that they are not covered by the backwards compatibility promise. + +```js +/** + * @experimental stableVersion:v6.6.0 + */ +Component.register('sw-new-component', { + ... +}); +``` +### Storefront + +Blocks, SCSS classes, JS plugins etc. can be marked as experimental, meaning that they are not covered by the backwards compatibility promise. + +In twig blocks can be wrapped as being experimental: +```twig +{# @experimental stableVersion:v6.6.0 #} +{% block awesome_new_feature %} + ... +{% endblock %} + +``` + +In addition to that, we can also mark the whole template as experimental: +```twig +{# @experimental stableVersion:v6.6.0 #} +{% sw_extends '@Storefront/storefront/page/product-detail/index.html.twig' %} +``` + +### UI + +This concept does not deal with how experimental features may be displayed to the merchant on UI level. +While the concepts are overlapping, we keep them separate, this ADR only answers the technical side and should enable teams developing features to work in a incremental and iterative way, +without being able to revisit early decisions (because they are covered by our BC promise) and without the need to use a long-lived feature branch. + +As far as this ADR is concerned the UI part, there is by default no way for a merchant to distinguish between experimental and stable features. +If that is needed can be decided individually per feature or in general in a separate ADR. +Those considerations should not hinder us from starting to use the `@experimental` annotation as explained here. + +### Commercial + +For commercial the same thing applies as for platform itself. There is no difference in how we handle experimental core features and experimental commercial features. + +### Docs + +Experimental features will be documented. This includes Dev docs, API docs and user docs. As we want to encourage the use of the features for end-users, they have to understand how the feature works under the hood. +For external developers, documentation for experimental features is also important, as they can check how they might want to integrate and extend the feature being built, and thus suggest the needed extension points during the development process. +In the docs it will also be marked that the features are experimental and that the APIs and user interface is not yet stable. + +### Roadmap + +The experimental status of features should also be reflected in the roadmap. That means that for a given feature, the progress in the roadmap can have a progress of 30% but already released in an experimental state. +In that case, the version where it was made available as experimental should be shown in the roadmap. +When a feature is completed, it leaves the experimental state and all features that are displayed under "released" in the roadmap are stable. + +### Automated checks + +We will add the following automated checks to ensure that the `@experimental` annotation is used correctly: +* Static analysis rule / unit test, that checks that every `@experimental` annotation has the stable version property and there are no `@experimental` annotations for a version that is already released (similar to the test case we have for `@deprecated`). +* The BC checker will be adapted to handle the `@experimental` annotation in the same way as it handles `@internal`. +* The API schema generator will be adapted to add the `Experimental` tag to all auto-generated CRUD-routes if the entity definition is marked as experimental. +* The test that checks that all API routes have OpenApi specification also checks that the route is marked as experimental in the documentation when the route or controller method is marked as experimental. + +this ADR was supplemented in [Add Feature property to \`@experimental\` annotation](./2023-09-06-feature-property-for-experimental-anotation.md) diff --git a/resources/references/adr/2023-05-15-stock-api.md b/resources/references/adr/2023-05-15-stock-api.md new file mode 100644 index 000000000..dc46bda89 --- /dev/null +++ b/resources/references/adr/2023-05-15-stock-api.md @@ -0,0 +1,308 @@ +--- +title: Stock Manipulation API +date: 2023-04-25 +area: core +tags: [stock] +--- + +# Stock Manipulation API + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-05-15-stock-api.md) +{% endhint %} + +## Context + +The stock handling in Shopware 6 is currently not very flexible and does not support many common use cases. + +* It's not possible to easily replace the loading of stocks with a custom implementation, for example one that communicates with an ERP. +* It's not possible to easily modify how stock is increased/decreased throughout the order lifecycle. +* Available stock calculation is very slow on large catalogs. +* Stock is stored as two distinct values: stock and available stock. This is due to the fact that stock is not reduced until an order is set as completed. Therefore, the available stock is calculated as the stock minus all open orders. This is unnecessarily complex. + +## Decision + +We have only one field `stock` in the product definition which always has a real time calculated value. + +The `stock` value should be correctly updated as an order and its line items transition through the various states. Eg, stock is decremented when an order is placed. If it is cancelled, the stock is increased, and so on. + +We have a clear API for manipulating stock which can be extended and supports arbitrary data, which could, for example, support features such as multi warehouse inventory. + +We have a way to disable the stock handling behavior of Shopware. + +### New feature flag + +We introduce a new feature flag `STOCK_HANDLING` to allow people to opt in to the new stock handling behavior immediately. In 6.6 the flag will be removed and the new stock handling will be activated by default. + +### Abstract Stock Storage + +We will introduce a new `AbstractStockStorage`. The API will be as follows: + +```php + $changes + */ + abstract public function alter(array $changes, Context $context): void; + + /** + * This method is executed when a product is created or updated. It can be used to perform some calculations such as update the `available` flag based on the new stock level. + * + * @param list $productIds + */ + abstract public function index(array $productIds, Context $context): void; +} +``` + +With a few DTOs: + +```php + + */ + public function all(): array + { + } +} +``` + +```php +setStock($stock->stock); +$product->setAvailable($stock->available); + +// optional values +$product->setMinPurchase($stock->minPurchase ?? $product->get('minPurchase')); +$product->setMaxPurchase($stock->maxPurchase ?? $product->get('maxPurchase')); +$product->setIsCloseout($stock->isCloseout ?? $product->get('isCloseout')); + +// really flexible for projects +$product->addExtension('stock_data', $stock); +``` + +However, in order to support this API, we must update `\Shopware\Core\Content\Product\SalesChannel\Detail\AvailableCombinationLoader::load` because it currently does not pass along the `SalesChannelContext` which is necessary for `AbstractStockStorage::load`. + +Therefore, we deprecate `load` in `AbstractAvailableCombinationLoader` for 6.6 and introduce: + +`public function loadCombinations(string $productId, SalesChannelContext $salesChannelContext): AvailableCombinationResult`. + +It is introduced as not abstract and throws a deprecation error if called (eg when the method is not implemented in concrete implementations) in 6.6, otherwise it forwards to `load`. It will be made abstract in 6.6. + +`AvailableCombinationLoader` implements the new `loadCombinations` method and `load` is deprecated for 6.6. + +Finally, `ProductConfiguratorLoader` is updated to call `loadCombinations` instead of `load`. + +### Stock changing scenarios + +The following table contains all the scenarios that should trigger stock changes. All implementations of `AbstractStockStorage` should be able to handler these scenarios. + +| Scenario | Items Before | Items After | Before Stock Values | After Stock Values | Diff | +|----------------------------------------------|-------------------------------------------------|-------------------------------------------------|---------------------------------------------------|--------------------------------------------------|----------------------------------| +| Order placed | N/A | Product 1: 10
Product 2: 5 | Product 1: 100
Product 2: 55 | Product 1: 90
Product 2: 50 | Product 1: -10
Product 2: -5 | +| Order cancelled | Product 1: 10
Product 2: 5 | Product 1: 10
Product 2: 5 | Product 1: 90
Product 2: 50 | Product 1: 100
Product 2: 55 | Product 1: +10
Product 2: +5 | +| Cancelled Order -> Open | Product 1: 10
Product 2: 5 | Product 1: 10
Product 2: 5 | Product 1: 100
Product 2: 55 | Product 1: 90
Product 2: 50 | Product 1: -10
Product 2: -5 | +| Line Item Added -> Product 3 | Product 1: 10
Product 2: 5 | Product 1: 10
Product 2: 8
Product 3: 1 | Product 1: 90
Product 2: 50
Product 3: 5 | Product 1: 90
Product 2: 47
Product 3: 4 | Product 2: -3
Product 3: -1 | +| Line Item Removed -> Product 3 | Product 1: 10
Product 2: 8
Product 3: 1 | Product 1: 10
Product 2: 8 | Product 1: 90
Product 2: 47
Product 3: 4 | Product 1: 90
Product 2: 50
Product 3: 5 | Product 3: +1 | +| Line Item Updated -> Product 2 qty increased | Product 1: 10
Product 2: 5 | Product 1: 10
Product 2: 8 | Product 1: 90
Product 2: 50 | Product 1: 90
Product 2: 47 | Product 2: -3 | +| Line Item Updated -> Product 2 qty decreased | Product 1: 10
Product 2: 5 | Product 1: 10
Product 2: 1 | Product 1: 90
Product 2: 50 | Product 1: 90
Product 2: 54 | Product 2: +4 | +| Line Item Updated -> P2 changed to P3 | Product 1: 10
Product 2: 5 | Product 1: 10
Product 3: 5 | Product 1: 90
Product 2: 50
Product 3: 10 | Product 1: 90
Product 2: 55
Product 3: 5 | Product 2: +5
Product 3: -5 | +| Non cancelled order deleted | Product 1: 10
Product 2: 5 | Product 1: 10
Product 2: 5 | Product 1: 90
Product 2: 50 | Product 1: 100
Product 2: 55 | Product 1: +10
Product 2: +5 | + +It is the role of `Shopware\Core\Content\Product\Stock\OrderStockSubscriber` to listen to the required shopware events for these scenarios and then interact with the stock storage implementation. + +1. Order placed: The product stock should be reduced by the order line item qties. (`BeforeWriteEvent` -> No items will exist pre insertion, so we know it's a decrement operation) +2. Order cancelled: The product stock should be increased by the order line item qties. (`StateMachineTransitionEvent -> $event->getToPlace()->getTechnicalName() === OrderStates::STATE_CANCELLED`) +3. Order reopened: The product stock should be reduced by the order line item qties. (`StateMachineTransitionEvent -> $event->getFromPlace()->getTechnicalName() === OrderStates::STATE_CANCELLED`) +4. Order item added: The product stock should be reduced by the new order line item qty. (`BeforeWriteEvent` -> filter for order line item writes and diff old and new state) +5. Order item removed (Status: Any non cancelled): The product stock is increased by the old order line item qty. (`BeforeWriteEvent` -> filter for order line item writes and diff old and new state) +6. Order item qty increased (Status: Any non cancelled): The product stock should be decreased by the difference between the old and new qty. (`BeforeWriteEvent` -> filter for order line item writes and diff old and new state) +7. Order item qty decreased (Status: Any non cancelled): The product stock should be increased by the difference between the old and new qty. (`BeforeWriteEvent` -> filter for order line item writes and diff old and new state) +8. Order item product changed (Status: Any non cancelled): The old product stock should be increased by the old qty. The new product stock should be decreased by the new qty. (`BeforeWriteEvent` -> filter for order line item writes and diff old and new state) + +## Consequences + +* By creating an abstract class, we can maintain a consistent interface for stock updating while allowing for different implementations. +* New inventory management strategies can be easily added by creating new concrete classes that extend `AbstractStockStorage`. +* Developers working with the inventory management system can be confident that any concrete implementation of the `AbstractStockStorage` will provide the required methods for handling stock updates. +* Developers wanting to completely remove and rewrite the inventory management logic can completely disable the `OrderStockSubscriber` and implement their own solution. diff --git a/resources/references/adr/2023-05-16-php-enums.md b/resources/references/adr/2023-05-16-php-enums.md new file mode 100644 index 000000000..f47b6ae03 --- /dev/null +++ b/resources/references/adr/2023-05-16-php-enums.md @@ -0,0 +1,162 @@ +--- +title: Use PHP 8.1 Enums +date: 2023-05-16 +area: core +tags: [php, '8.1', enum] +--- + +# Use PHP 8.1 Enums + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-05-16-php-enums.md) +{% endhint %} + +## Context + +As of Shopware 6.5 the minimum version of PHP is 8.1. We would like to promote the usage of PHP Enums. + +Enums are useful where we have a predefined list of constant values. It's now not necessary to provide values as constants, and it's not necessary to create arrays of the constants to check validity. + +## Decision + +All new code which needs to represent a collection of constant values should now use Enums. + +A few examples might be: + +* Product Types (Parent, Variant) +* Product Status (Enabled, Disabled) +* Backup Type (Full, Incremental) + +Where possible, we should migrate existing constant lists to use Enums. See the following Migration Strategy: + +## Backwards Compatibility / Migration Strategy + +To migrate a list of constant values, where an API accepts a "type" parameter which should exist in the list of constant values we can use the [Expand & Contract pattern](https://www.tim-wellhausen.de/papers/ExpandAndContract/ExpandAndContract.html) to migrate in a backwards compatible manner: + +Consider the following example: + +```php +class Indexer +{ + public const PARTIAL = 'partial'; + public const FULL = 'full'; + + public function product(int $id, string $method): void + { + if (!in_array($method, [self::PARTIAL, self::FULL], true)) { + throw new \InvalidArgumentException(); + } + + match ($method) { + self::PARTIAL => $this->partial($id), + self::FULL => $this->full($id) + }; + } +} +``` + +Step 1: Create the ENUM & Accept in API as well as string. For this step it is necessary to maintain the allowed values in both the constants and the ENUM: + +Note: In PHP 8.1 we cannot assign directly an ENUM to a constant. For the future, it is worth noting that this is supported in PHP 8.2 with backed ENUMS: `public const PARTIAL = IndexMethod::PARTIAL->value;` + +```php +enum IndexMethod +{ + case PARTIAL; + case FULL; +} + +class Indexer +{ + public function product(int $id, IndexMethod|string $method): void + { + ... + } +} +``` + +Step 2: Create ENUM from primitive type if string value passed: + +Note: If your ENUM is backed with a value you can use `BackedEnum::from` to perform automatic casting and validation. Otherwise you will need to map the values manually. + +```php +class Indexer +{ + public const PARTIAL = 'partial'; + public const FULL = 'full'; + + public function product(int $id, IndexMethod|string $method): void + { + if (is_string($method)) { + $method = match ($method) { + 'partial' => IndexMethod::PARTIAL, + 'full' => IndexMethod::FULL, + default => throw new \InvalidArgumentException() + }; + } + + match ($method) { + IndexMethod::PARTIAL => $this->partial($id), + IndexMethod::FULL => $this->full($id) + }; + } +} +``` + +Step 3: Deprecate the constants and passing primitive values in the method: + +```php +class Indexer +{ + // @deprecated tag:v6.6.0 - Constant will be removed, use enum IndexMethod::PARTIAL + public const PARTIAL = 'partial'; + // @deprecated tag:v6.6.0 - Constant will be removed, use enum IndexMethod::FULL + public const FULL = 'full'; + + /** + * @deprecated tag:v6.6.0 - Parameter $method will not accept a primitive in v6.6.0 + */ + public function product(int $id, IndexMethod|string $method): void + { + if (is_string($method)) { + $method = match ($method) { + 'partial' => IndexMethod::PARTIAL, + 'full' => IndexMethod::FULL, + default => throw new \InvalidArgumentException() + }; + } + + match ($method) { + IndexMethod::PARTIAL => $this->partial($id), + IndexMethod::FULL => $this->full($id) + }; + } +} +``` + +Step 4: Remove deprecations in next major. + + +Which leaves us with the following, succinct code: + +```php +enum IndexMethod +{ + case PARTIAL; + case FULL; +} + +class Indexer +{ + public function product(int $id, IndexMethod $method): void + { + match ($method) { + IndexMethod::PARTIAL => $this->partial($id), + IndexMethod::FULL => $this->full($id) + }; + } +} + +(new Indexer())->product(1, IndexMethod::PARTIAL); +``` diff --git a/resources/references/adr/2023-05-16-symfony-dependency-management.md b/resources/references/adr/2023-05-16-symfony-dependency-management.md new file mode 100644 index 000000000..d4a2b8947 --- /dev/null +++ b/resources/references/adr/2023-05-16-symfony-dependency-management.md @@ -0,0 +1,52 @@ +--- +title: Symfony Dependency Management +date: 2023-05-16 +area: core +tags: [php, symfony, dependency] +--- + +# Symfony Dependency Management + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-05-16-symfony-dependency-management.md) +{% endhint %} + +## Context + +The process of configuring dependencies has been upgraded with various new features in recent versions of Symfony. + +We would like to utilise new features such as: + +* [Autowiring](https://symfony.com/doc/current/service_container.html) +* [PHP configuration](https://symfony.com/doc/current/service_container/import.html) +* [Attributes for autowiring](https://symfony.com/blog/new-in-symfony-6-1-service-autowiring-attributes) + +## Decision + +1. Autowiring will be enabled +2. Support will be added to load service configuration from PHP files (as well as XML for backwards compatibility) +3. Where services need a particular non default service, for example a different implementation, or scalar values, we can use attributes. + +Note: Attributes should only be used in framework glue code, for example, in Controllers and commands. We do not want to couple our domain code too close to Symfony. + +With autowiring enabled, we can greatly reduce the amount of configuration in the XML files since most of the configuration is unnecessary. Most dependency graphs can be automatically resolved by Symfony using type hints. +There are no runtime performance implications because the container with its definitions is compiled. + +Advantages: + +* Less code to maintain. +* Better autocompletion with PHP. +* More modern approach + +## Backwards Compatibility / Migration Strategy + +To migrate our current XML dependency configurations, we can follow the below steps: + +Step 1: Add support for loading service definitions from PHP files as well as XML files. + +Step 2: Enable autowiring. Symfony should prefer the registered configuration before trying to autowire. In other words, Symfony will only autowire classes without configured definitions. + +Step 3: Delete definitions which are not required because they can be autoloaded. + +Step 4: Migrate any existing definitions to the PHP configurations or Attributes. diff --git a/resources/references/adr/2023-05-22-switch-to-uuidv7.md b/resources/references/adr/2023-05-22-switch-to-uuidv7.md new file mode 100644 index 000000000..422624a18 --- /dev/null +++ b/resources/references/adr/2023-05-22-switch-to-uuidv7.md @@ -0,0 +1,39 @@ +--- +title: Switch to UUIDv7 +date: 2023-05-22 +area: core +tags: [DAL] +--- + +# Switch to UUIDv7 + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-05-22-switch-to-uuidv7.md) +{% endhint %} + +## Context + +Using UUIDs as primary keys eases the integration of several different data sources, +but it also brings some performance issues. + +Currently, we're using UUIDv4, which is a random UUID the completely random prefix means +that the B-tree indexes of the database are not very efficient. + +UUIDv7 time-based prefix is less spread than that of UUIDv4, this helps the database to keep the index more compact. +It allows the Index to allocate fewer new pages and to keep the index smaller. + +## Decision + +Considering there is little risk to using UUIDv7, as v4 and v7 share the same +length and are indistinguishable for shopware, we can switch to v7 without any risk +of breaking anything. + +The effort is also very low as we only need to change the +implementation of the `Uuid` class. As using UUIDv7 will improve the speed of +bulk product inserts by about 8 %, we think the effort is worth the measurable and +theoretical gain. + +## Consequences + +We will switch to UUIDv7 as default and add performance guides promoting v7. diff --git a/resources/references/adr/2023-05-25-exception-log-levels.md b/resources/references/adr/2023-05-25-exception-log-levels.md new file mode 100644 index 000000000..0b66e49a1 --- /dev/null +++ b/resources/references/adr/2023-05-25-exception-log-levels.md @@ -0,0 +1,39 @@ +--- +title: Exception Log Level configuration +date: 2023-05-25 +area: core +tags: [core, devops, observability] +--- + +# Exception Log Level configuration + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-05-25-exception-log-levels.md) +{% endhint %} + +## Context +By default, every exception that is thrown in the PHP stack and not caught will be logged by the `symfony/monolog-bridge` on `error` level. +But there are some cases where the exception is caused by clients accessing the API wrong (missing fields etc.) and throwing an `ShopwareHttpException` with an HTTP-Status-Code of 40x is our way of handling such situations and returning a correct HTTP-Status-Code to the client. +So those cases are in fact no "errors" that need to be analyzed, but are expected given a malformed API request. +Logging those cases as "errors" produces a lot of noise, which makes it harder to actually find errors in the logs. + +For our cloud product, we already used a configuration list that configures that some Exception classes should only be logged as notices. + +## Decision + +We add a configuration to the platform that degrades the error level of specific exceptions to notices. This way, external hosters can also profit from our classification. +We use [symfony's `exceptions` configuration](https://symfony.com/doc/current/reference/configuration/framework.html#exceptions) for this. + +This has the benefit that for specific projects this configuration could be adjusted, for example, for cases where you also control all clients that access the shop, you may want to log also every client error, just to help debugging the client. + +Another solution could be to do the configuration of the log level directly in the exception class either by attributes or a separate method specifying the log level, but that would make overwriting it for a specific project harder, so we stick to the default symfony configuration. + +## Consequences + +We will add the `exceptions` configuration to the platform, that way the error logging in existing projects might change. But in general, we assume that this change is for the better. + +Additionally, we will need to extend on the default symfony configuration as that is not compatible with our new [domain exceptions](./2022-02-24-domain-exceptions.md) as there are multiple exception cases in one file/class. +Therefore, we will add a similar configuration option, that does not rely on the FQCN, but instead we will use the shopware specific `error code` from the shopware exception as that is unique to the exception case. + +On a side note, we should be able to get rid of most of the cloud-specific configuration for the exception logging mapping. diff --git a/resources/references/adr/2023-06-27-store-api-to-app-server.md b/resources/references/adr/2023-06-27-store-api-to-app-server.md new file mode 100644 index 000000000..c50d2ae79 --- /dev/null +++ b/resources/references/adr/2023-06-27-store-api-to-app-server.md @@ -0,0 +1,66 @@ +--- +title: Client side communication to App Server +date: 2023-06-27 +area: core +tags: [core, app-system] +--- + +# Client side communication to App Server + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-06-27-store-api-to-app-server.md) +{% endhint %} + +## Context + +Right now only Shopware Backend is able to communicate with the App Server in a secure way. +This is because the App Server is able to verify the origin of the request by checking request signature, +and this signature is generated by the Shopware 6 backend application with a shop to app server secret. + +When an app wants to communicate directly with the App Server, +the App Server is not able to verify the origin of the request and has to trust the client. +This works fine for public available data, but not when you reliably need the logged-in customer and other information. + + +## Decision + +We provide a new endpoint `/store-api/app-system/{appName}/generate-token` (`/app-system/{appName}/generate-token` in Storefront) which generates a JWT token for the given app. +This endpoint requires that the customer is logged-in and the app is installed and active. + +The JWT token contains claims like: +- `iat` - issued at +- `exp` - expiration time +- `shopId` - the shop id of the current shop +- `salesChannelId` - the sales channel id of the current sales channel +- `customerId` - the customer id of the logged-in customer +- `cartToken` - the cart token of the current cart + +The additional claims are bound to app permissions when an app does not have permissions to read a customer, +it does not get the `customerId` claim. + +The JWT token is signed with the shop to app server secret key which we use already for the signature. +So the App Server needs to be able to verify the JWT key and use the claims in a secure way. +The request body of the client is still untrusted and has to be properly validated by the app server. + +The request cycle would look like this: + +```mermaid +sequenceDiagram + participant Client + participant Shopware Backend + participant App Server + Client->>Shopware Backend: GET /store-api/app-system/TestApp/generate-token + Shopware Backend->>Client: Responds with Signed JWT Token + Client->>App Server: Post /product-review/submit containing JWT in header +``` + +The JWT token is valid for 15 minutes and can be used multiple times until expired. +The client should save it in the [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) +and request it only on expiry again. +Additionally, the API route should be rate limited to not generate too often an expensive JWT key. + +## Consequences + +- We add a helper for the Storefront to obtain the token and do the expiry and regenerating +- We add support for JWT token verification in our PHP App SDK to make this process simple as possible diff --git a/resources/references/adr/2023-06-28-default-handle-for-non-specified-salutations.md b/resources/references/adr/2023-06-28-default-handle-for-non-specified-salutations.md new file mode 100644 index 000000000..85b0e77e6 --- /dev/null +++ b/resources/references/adr/2023-06-28-default-handle-for-non-specified-salutations.md @@ -0,0 +1,35 @@ +--- +title: Default handling for non specified salutations +date: 2023-06-28 +area: core +tags: [adr, salutation] +--- + +# Default handling for non specified salutations + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-06-28-default-handle-for-non-specified-salutations.md) +{% endhint %} + +## Context +The current implementation of the salutation in Shopware 6 needs to handle cases where the salutation is not specified by the customer or administrator. To address this requirement and promote inclusivity, we have updated the default salutation to "not_specified" for unspecified salutations in our Shopware 6 platform. + +## Decision +We have modified the existing salutation handling in Shopware 6 to update the default value to "not_specified" when the salutation is null. This decision was made based on the following considerations: + +* Inclusivity: By offering a default salutation of "not_specified" for null values, we promote inclusivity and ensure that all customers are appropriately addressed, even when salutation data is missing. +* Customer Experience: Providing a default salutation ensures consistency in customer communications and prevents any confusion or misinterpretation when a salutation is not explicitly specified. +* Non-Deletable Default Salutation: It has been decided that the "not_specified" salutation, being the default value for unspecified salutations, should not be deletable by the shop owner. This ensures that there is always a fallback option available, guaranteeing a consistent experience for customers. + +## Consequences +As a result of this decision, the following consequences will occur: + +* Improved Default Handling: When a customer or administrator does not specify a salutation, the default value will be automatically set to "not_specified." This default value itself is configurable by the shop owner. They have the flexibility to customize the "not_specified" value to their preferred salutation or leave it as it is to use the generic "not_specified" salutation. +* Enhanced Inclusivity: Customers who have not specified their salutation will be addressed using the default "not_specified" salutation, reflecting our commitment to inclusivity and respect within our platform. +* Code Changes: The necessary code changes have been implemented to update the default handling of null salutations. This includes validation checks, database updates, and modifications to relevant logic to accommodate the "not_specified" default value. +* Different Default Values in Specific Locations: The default values used in specific locations within the platform are as follows: + * Letters and Documents: When generating letters or documents where a salutation is required, the default value will be "Dear Customer" or an appropriate alternative if customization is allowed. This ensures a professional and personalized approach in written communications. + * Email Communications: In email communications, the default value will be "Hello" or an alternative greeting if customization is allowed. This provides a friendly and welcoming tone in electronic correspondences. + * User Interfaces: Within the user interfaces of the Shopware 6 platform, the default value will be displayed as "not_specified" for customers who have not specified a salutation. This allows for a neutral and inclusive representation in the platform's user-facing components. +* Testing and Quality Assurance: Rigorous testing procedures will be conducted to ensure the accuracy and reliability of the updated default handling. Quality assurance measures will be in place to identify and address any potential issues. diff --git a/resources/references/adr/2023-07-13-flow-builder-preview.md b/resources/references/adr/2023-07-13-flow-builder-preview.md new file mode 100644 index 000000000..f91cd0a60 --- /dev/null +++ b/resources/references/adr/2023-07-13-flow-builder-preview.md @@ -0,0 +1,82 @@ +--- +title: Flow Builder Preview +date: 2023-07-13 +area: Business Ops +tags: [services-settings, flow] +--- + +# Flow Builder Preview + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-07-13-flow-builder-preview.md) +{% endhint %} + +## Context +In the past merchants had to deal with issues where their custom-built flow did not +behave how they intended it to do. An concrete example: We've had a merchant that contacted +us (shopware) that their shop did not sent out mails. Debugging the flow turned out to +be harder than we thought and honestly harder than it should be. The flow builder should +empower users to build reliable flows and not spend their precious time trying to +figure out what went wrong. + +To improve the experience when using the Flow Builder were taking measures. First, +Flow Builder Preview and second Flow Builder Logging. This ADR only discusses the former one, +there are plans internally for the latter one, but it won't get much attention for now, as +it also addresses different issues (what does my flow do vs what went wrong in the past). + +Users should be able to preview a flow and get further understanding on how a flow executes. +This preview only displays the steps and decisions happening inside a flow but doesn't +execute / simulate the real flow + +## Decision +The whole scope of the Flow Builder Preview is to help merchants *creating* new flows and +*update* their existing flows. + +Will do: +* Evaluate path of flow by executing rules +* Validating the input data of the actions +* Leave it up to the action how much “real” code is actually executed + +Won't do: +* Executing Flow Builder Actions i.e. calling the `handleFlow` method of an action +* Execute one huge transaction to the database and rollback later + * Mail actions e.g. could render the mail template and send it to a given mail address + * CRUD actions e.g. could decide, if they only want to check the input data or + open a transaction to roll it back + +### Preview is optional / We and Third Parties can provide this functionality +It is important to note that with this change existing and new actions do *not* need +to implement the "preview" functionality and implementing it is completely optional. +Actions that do not implement the new feature will be marked as "skipped" inside +the administration. + +### A new core interface +The core interface defines the data structure of the output and third party developers +could use this to make the flow action previewable. The interface could look like this: + +```php +interface Previewable +{ + public function preview(...): PreviewResponseStruct +} +``` + +Flow actions are responsible to implement this interface if they want to and execute the +necessary steps to generate a preview without actually writing / executing anything real. + +### Separation from Flow Logging +The Flow Builder Preview only addresses half of merchants pain points. While it does +help with the creation of new flow it does not help merchants to manage incidents in +their flows. This is where the Flow Logging feature comes in hand. + +## Consequences +Though, it is completely optional to implement the "preview" feature for an action, +we advice developers to do so. Doing, this will benefit... +1. ... the merchant when previewing their flow, +2. ... the plugin developer because the values the action provides increases + +Because, the execution of the flow in a preview mode behaves different compared to +when it is actually being executed, developers should make sure that the implementation +their action preview stays as close as possible to the implementation of their action. +Otherwise the preview could present a wrong impression to the merchant diff --git a/resources/references/adr/2023-08-03-collecting-entity-data.md b/resources/references/adr/2023-08-03-collecting-entity-data.md new file mode 100644 index 000000000..704ecf208 --- /dev/null +++ b/resources/references/adr/2023-08-03-collecting-entity-data.md @@ -0,0 +1,152 @@ +--- +title: Collecting and dispatching entity data +date: 2023-08-03 +area: data-services +tags: ['entity', 'usage-data', 'ai', 'machine-learning'] +--- + +# Collecting and dispatching entity data + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-08-03-collecting-entity-data.md) +{% endhint %} + +## Context +*shopware AG* aims to provide data-driven features to its merchants. +The foundation for these features are data that our merchants provide to us with their consent. +A subset and primary pillar of these data lies within the entities, stored within each and every Shopware shop. +This ADR addresses main concepts of extracting the information out of the shops and transferring it to *shopware AG*. + +## Decision +### No data sharing without consent +Merchants must explicitly agree and consent to share their data with *shopware AG*. +As long as there is no consent, no data will be collected or transferred. +The consent to data sharing can be revoked at any time by the merchants. + +We will actively prompt Administration users to decide whether they are willing to give their consent to data sharing on the Administration's dashboard. +Changing the consent state later is possible via the system settings. To keep track of the consent changes, we will send to and store it on our gateway. + +### No collection of sensitive information +Data stored in all types of entities might contain sensitive information including e.g. personal or business critical information. +This kind of data is excluded. + +### Personally identifiable information (PII) +Data that would enable *shopware AG* to identify a person is modified in such a way that it is no longer possible to draw conclusions about the person. +A so-called *personal unique identifier (PUID)* is generated to identify users across multiple sources (e.g. entity data, on-site tracking) with the goal of analyzing their behavior which is used for generating insights and making predictions. +Again, it is not possible to find out **who** the person is, just that it is the **same** person. + +### Transitioning to data pulling +The processes described in this ADR can be viewed as an approach of *data pushing*. +Data is fetched from the database and prepared on the merchant's servers and infrastructure before it is sent to *shopware AG*. + +To be more flexible and to reduce the load on our merchant's infrastructure, we plan to transition to a *data pulling* approach. +With this approach we are planning to use Shopware's Admin API to fetch the data, rather than fetching it from the database directly. + +### Providing data-driven features via the app system +The features built upon the data that is collected, will be rolled out as an extension based on the app system. +This way, we make feature releases independent of the Shopware 6 release cycle and can provide new features faster. + +### Including entities and fields +By default, entities are not considered for data collection. +Only entities and their fields listed in an allow-list will be included in the data collection. + +The format looks as follows: +```json +{ + "entity_one": [ + "fieldOne", + "fieldTwo", + "fieldThree" + ], + "entity_two": [ + "fieldOne", + "fieldTwo" + ], + "entity_three": [ + "fieldOne" + ] +} +``` + +Example: +```json +{ + "category": [ + "id", + "parentId", + "type" + ], + "product": [ + "id", + "parentId", + "name" + ] +} +``` + +#### Many-to-many associations +Entities representing many-to-many associations between other entities should not be included in the allow-list. +Instead, they are either fetched from the database directly or resolved by querying the associated entity table. + +When adding a many-to-many association to the allow-list, the referenced field is the `associationName` instead of the `propertyName`. + +Example: + +```php +class ProductDefinition +{ + protected function defineFields(): FieldCollection + { + return new FieldCollection([ + // ... + (new ManyToManyIdField('category_ids', 'categoryIds', associationName: 'categories'))->addFlags(new ApiAware(), new Inherited()), + (new ManyToManyAssociationField('categories', CategoryDefinition::class, ProductCategoryDefinition::class, 'product_id', 'category_id'))->addFlags(new ApiAware(), new CascadeDelete(), new Inherited(), new SearchRanking(SearchRanking::ASSOCIATION_SEARCH_RANKING)), + // ... + (new ManyToManyAssociationField('tags', TagDefinition::class, ProductTagDefinition::class, 'product_id', 'tag_id'))->addFlags(new CascadeDelete(), new Inherited(), new SearchRanking(SearchRanking::ASSOCIATION_SEARCH_RANKING), new ApiAware()), + // ... + ]); + } +} +``` + +```json +{ + "product": [ + "categories", + "tags" + ] +} +``` + +The allow-list contains the `categories` and the `tags` field of the product entity. When the data is queried from the database, the many-to-many associations are resolved as follows: +* Identifiers of many-to-many associations that have a corresponding `ManyToManyIdField` are fetched from the database directly. +* Other many-to-many associations are resolved by fetching the associated entities from the database beforehand and matching them against the currently processed entity. + +#### Associations problem: product -> category + +#### Translated fields +Translated fields are not resolved automatically. +Instead, translation entities must be added to the allow-list explicitly. + +## Consequences +#### Activating and deactivating the data collecting process +The process will be triggered once a day by a scheduled task as long as the consent is given. +It will also be triggered right away when the consent is given. +The merchant can revoke the consent at any time, which will prevent the process from starting. + +#### Collecting data asynchronously +Once the process is running, for each entity definition, some messages will be added to a low priority message queue, and so they will be processed asynchronously. +The process will create batches of up to 50 entities (configurable) before sending them to the gateway. + +#### First run and consecutive runs +Deltas of the data are calculated after the first time the data is sent, so consecutive runs are lighter and faster. +In order to achieve this, the process will keep track of the last time the data was sent and will only send the data that was created or updated after that time. + +For deletions, an event subscriber will take care of storing the deletions of the entities. +These deletions will be sent and deleted when the process is run. +No deletion will be stored if the consent for collecting data is revoked or not given in the first place. + +#### Remote kill-switch +A kill-switch on the Gateway enables us to (temporarily) stop shops from sending us data. +Messages already dispatched to the queue will still be handled but no new messages will be added by the scheduled task if the kill-switch is enabled. diff --git a/resources/references/adr/2023-08-17-media-path.md b/resources/references/adr/2023-08-17-media-path.md new file mode 100644 index 000000000..73e519d26 --- /dev/null +++ b/resources/references/adr/2023-08-17-media-path.md @@ -0,0 +1,184 @@ +--- +title: Media path rewrite +date: 2023-08-17 +area: core +tags: [media, url, strategy] +--- + +# Media path rewrite + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-08-17-media-path.md) +{% endhint %} + +## Context +In the current media system it is possible to configure different `Shopware\Core\Content\Media\Pathname\PathnameStrategy\PathnameStrategyInterface`. + +These strategies are used to store files, which are uploaded for media entity, under a certain path. + +The configured strategy is then also used to generate the URL that is used in the frontend/store API to embed the file. + +The generation of this URL is currently triggered in an event subscriber which is registered to the `media.loaded` event. + +For generating the URL an implementation of the `UrlGeneratorInterface` is used. +```php +interface UrlGeneratorInterface +{ + public function getAbsoluteMediaUrl(MediaEntity $media): string; + + public function getRelativeMediaUrl(MediaEntity $media): string; + + public function getAbsoluteThumbnailUrl(MediaEntity $media, MediaThumbnailEntity $thumbnail): string; + + public function getRelativeThumbnailUrl(MediaEntity $media, MediaThumbnailEntity $thumbnail): string; +} + +interface PathnameStrategyInterface +{ + public function getName(): string; + + /** + * Generate a hash, missing from url if omitted + */ + public function generatePathHash(MediaEntity $media, ?MediaThumbnailEntity $thumbnail = null): ?string; + + /** + * Generate the cache buster part of the path, missing from url if omitted + */ + public function generatePathCacheBuster(MediaEntity $media, ?MediaThumbnailEntity $thumbnail = null): ?string; + + /** + * Generate the filename + */ + public function generatePhysicalFilename(MediaEntity $media, ?MediaThumbnailEntity $thumbnail = null): string; +} +``` + +## Issues + +* `PathnameStrategyInterface` as well as `UrlGeneratorInterface` have a dependency on the DAL and always need a fully loaded entity to generate the URL. This is a big overhead when you consider what data is (currently) needed for the URL generation in the end. +* The media upload "must" always be done via the shopware application, so that the folder structure stored in the file system and generated in the URL match. So it is only conditionally (or not at all) possible to upload all media directly to a S3 CDN without uploading the files via the shopware stack. +* In theory, the strategy must never be reconfigured after a file has been uploaded. If the file is uploaded to `/foo/test.jpg` and then the strategy is changed to one that would place the same file under `/bar/test.jpg`, the new strategy will take effect when the URL is generated, but the file will never be moved in the filesystem. +* The current strategies use a so called "cache busting" system, where the "uploaded-at" value is included in the file path. However, this does not work if the URL has been statically included in the CMS. Here, replacing the media file always leads to a new file path and the image can no longer be reached under the old file path. + +## Decision +To address the issues listed above, we will make the following changes to the system: + +- The file path will be saved directly to the media entity (and thumbnail) when the file is uploaded. + - This way we don't have to access the strategy when generating the URL, which might have changed in the meantime. +- We allow to change the file path via API and write it directly when creating the entity + - This way files can be synchronized directly with an external storage and the path only has to be changed in the entity or given during import. +- To generate the strategy we use new location structs, which can be easily created via a service. + - So we remove the dependency to the DAL to generate the file path. +- For generating the URL, we implement a new service, which can be operated without entities. + - The URL generation is more resource efficient and can be done without fully loaded entities. +- The new URL generator uses a new "cache busting" system, which writes the updated at timestamp of the media entity as query parameter into the URL. + - This way the file path can remain the same even if the file is updated. Changes to the entity's meta data will also result in a new URL. + +## BC promise + +For the Backwards compatibility we will take the following measures: + +- Until 6.6.0 the old URL generator will still be used, which generates the URL with the old strategy. +- From 6.6.0 the new URL generator will be used, which generates the URL based on the `MediaEntity::$path`. +- If a project specific strategy is used, it must be migrated to the new pattern by 6.6.0. +- We provide a `BCStrategy` which converts the new format to the old format. +- For an easy transition between the majors, we make it possible to always use `MediaEntity::$path` for the relative path. We realize this via an entity loaded subscriber, which generates the value at runtime via the URL generator and writes it to the path property. + +## Consequences + +- We need less resources to generate the absolute media URLs +- We can generate the URLs even without fully loaded entities +- The strategy can be changed over time without moving the files in the file system +- We can load the files directly to an external storage and adjust the path in the entity +- We remove the dependency to the DAL from the strategy and the URL generator. + +## Example + +```php +urlGenerator->getRelativeMediaUrl($media); + + $absolute = $this->urlGenerator->getAbsoluteMediaUrl($media); + } + + public function bar(MediaThumbnailEntity $thumbnail) + { + $relative = $this->urlGenerator->getRelativeThumbnailUrl($thumbnail); + + $absolute = $this->urlGenerator->getAbsoluteThumbnailUrl($thumbnail); + } +} + +class AfterChange +{ + private AbstractMediaUrlGenerator $generator; + + public function foo(MediaEntity $media) + { + $relative = $media->getPath(); + + $urls = $this->generator->generate([UrlParams::fromMedia($media)]); + + $absolute = $urls[0]; + } + + public function bar(MediaThumbnailEntity $thumbnail) + { + // relative is directly stored at the entity + $relative = $thumbnail->getPath(); + + // path generation is no more entity related, you could also use partial entity loading and you can also call it in batch, see below + $urls = $this->generator->generate([UrlParams::fromMedia($media)]); + + $absolute = $urls[0]; + } + + public function batch(MediaCollection $collection) + { + $params = []; + + foreach ($collection as $media) { + $params[$media->getId()] = UrlParams::fromMedia($media); + + foreach ($media->getThumbnails() as $thumbnail) { + $params[$thumbnail->getId()] = UrlParams::fromThumbnail($thumbnail); + } + } + + $urls = $this->generator->generate($paths); + + // urls is a flat list with {id} => {url} for media and also for thumbnails + } +} + +class ForwardCompatible +{ + // to have it forward compatible, you can use the Feature::isActive('v6.6.0.0') function + public function foo(MediaEntity $entity) + { + // we provide an entity loaded subscriber, which assigns the url of + // the UrlGeneratorInterface::getRelativeMediaUrl to the path property till 6.6 + // so that you always have the relative url in the MediaEntity::path proprerty + $path = $entity->getPath(); + + if (Feature::isActive('v6.6.0.0')) { + // new generator call for absolute url + } else { + // old generator call for absolute url + } + } +} +``` diff --git a/resources/references/adr/2023-08-27-post-updater.md b/resources/references/adr/2023-08-27-post-updater.md new file mode 100644 index 000000000..f69a94d81 --- /dev/null +++ b/resources/references/adr/2023-08-27-post-updater.md @@ -0,0 +1,76 @@ +--- +title: Post updater +date: 2023-08-17 +area: core +tags: [indexer, update, installer] +--- + +# Post updater + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-08-27-post-updater.md) +{% endhint %} + +## Context +We often need a way between the different Shopware versions to provide a one-time update for data. This is currently done on the way to extend an indexer to this logic and then trigger this via a migration. +This is of course a possible way to enable certain migrations of data, but this migration is also executed again and again when the indexer is executed. +With certain data this is critical and can lead to system errors. For example, the one-time migration of media path information. + +## Decision + +We implement a new `PostUpdateIndexer`. This is an extension of the `EntityIndexer` and the system can be adapted 1:1. Also, the indexing registration via database migration can be adapted 1:1. +However, the indexer is not triggered via the `IndexerRegistry` when a full re-index or an entity written event is triggered. +These indexers are only included after the update process. +In addition to the one-time update of the data, we then often also provide a command that can be used to trigger the migration of the data again. + +## Consequences + +- We allow computationally intensive migrations, which need to be performed only once, to be performed after the update process. +- These one-time migrations do not affect the normal indexer process and cannot be triggered by it. +- Developers can trigger more complex migrations without worrying about the impact on the normal indexer process. +- The transition from one system to the other is very simple and can be adapted 1:1. + +## Example + +```php +iteratorFactory->createIterator('my_entity', $offset); + + $ids = $iterator->fetch(); + + if (empty($ids)) { + return null; + } + + return new EntityIndexingMessage(array_values($ids), $iterator->getOffset()); + } + + public function handle(EntityIndexingMessage $message): void + { + // handle ids + } +} +``` + +```php +registerIndexer($connection, 'post.update.example'); + } +} +``` diff --git a/resources/references/adr/2023-09-06-feature-property-for-experimental-anotation.md b/resources/references/adr/2023-09-06-feature-property-for-experimental-anotation.md new file mode 100644 index 000000000..0c5d6ae16 --- /dev/null +++ b/resources/references/adr/2023-09-06-feature-property-for-experimental-anotation.md @@ -0,0 +1,120 @@ +--- +title: Add Feature property to `@experimental` annotation +date: 2023-09-06 +area: core, administration, storefront +tags: [process, backwards compatibility] +--- + +# Add Feature property to `@experimental` annotation + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-09-06-feature-property-for-experimental-anotation.md) +{% endhint %} + +## Context +Our current development process uses ['Experimental features'](./2023-05-10-experimental-features.md) to publish features in an early state to gather feedback regarding those features. +During the implementation, developers may encounter challenges related to the effective management of extensive code scattered throughout the platform, particularly in connection with specific experimental features. This codebase fragmentation presents impediments to the tracking, maintenance, and comprehensive understanding of each feature's scope, thereby hindering our development progress. + +Potential problems: +* Update `stableVersion` property for Prolonged Experiments + * When a decision is made to extend an experiment, locating all relevant sections of code for updating the property `stableVersion` in `@experimental` annotation becomes a cumbersome task. +* Deprecation of Killed Features + * Identifying and marking as deprecated the components associated with a deprecated experimental feature is problematic, particularly when multiple experimental features coexist simultaneously within the platform. + * The ['Experimental features'](./2023-05-10-experimental-features.md) stipulates the "Killing Feature" rule, which mandates that a feature must remain within the platform's codebase until the next major version and be appropriately marked as deprecated. However, it is hardly possible to check with current annotation. + +In all the above case main problem is detection to which feature belongs experimental code. + +## Decision +To address the existing challenges, we propose implementing a refined approach to the use of the `@experimental` annotation. + +The key modifications are as follows: +* Mandatory `feature` property: + * Every `@experimental` annotation will now require a mandatory `feature` property. This property is a string that must contain the name of the associated feature. +* Uniform feature Naming: + * To enhance code organization and traceability, all sections of code related to a particular feature must use the same feature name in the `feature` property of the `@experimental` annotation. + * Feature names should follow the conventions. + * Feature names cannot contain spaces + * Feature names should be written in `ALL_CAPS`. + +## Consequences +### Core +Implementation of the new `feature` property for the `@experimental` annotation will require the following changes: +* To `@experimental` annotation should be added required string property `feature`. The value of the features should follow the conventions. +* There will be implemented a static analysis rule / unit test, that checks that every `@experimental` annotation has the `feature` property. + +Examples of usage: +php +```php +/** + * @experimental stableVersion:v6.6.0 feature:WISHLIST + */ +class testClass() +{ + //... +} +``` +js +```js +/** + * @experimental stableVersion:v6.6.0 feature:WISHLIST + */ +Component.register('sw-new-component', { + ... +}); +``` + +In twig blocks can be wrapped as being experimental: +```twig +{# @experimental stableVersion:v6.6.0 feature:WISHLIST #} +{% block awesome_new_feature %} + ... +{% endblock %} + +``` + +In addition to that, we can also mark the whole template as experimental: +```twig +{# @experimental stableVersion:v6.6.0 feature:WISHLIST #} +{% sw_extends '@Storefront/storefront/page/product-detail/index.html.twig' %} +``` + +## Combining `@experimental` annotation and `feature flag` + +Despite that, the `@experimental` annotation and the `feature flag` are two different concepts. The `@experimental` annotation is used to mark code as experimental and influential only on BC promises regarding this code, while the `feature flag` is used to control the visibility of the experimental code. +There might be scenarios where introducing a feature flag (akin to a switch) becomes necessary, for example, in integration points. 'Experimental features' ADR doesn't explicitly prohibit this practice and does not regulate it in any way. Simultaneously, it would be beneficial to ensure a clear linkage between the feature flag and the experimental functionality it enables. + +To achieve this linkage, we recommend the following: +1. Ensure that the feature flag's name matches the name used in the @experimental annotation's `feature` property. +2. The description field in the feature flag configuration should include the experimental annotation along with all the required properties, namely 'stableVersion' and 'feature'. + +Example: + +feature.yaml +```yaml +shopware: + feature: + flags: + - name: WISHLIST + default: false + major: true + description: "experimental stableVersion:v6.6.0 feature:WISHLIST" +``` +New experimental class +```php +/** + * @experimental stableVersion:v6.6.0 feature:WISHLIST + */ +class Foo +{ +} +``` +Connection point +```php +if (Feature.isActive('WISHLIST') { + $obj = new Foo(); + // New implementation +} else { + // Old/current implementation +} +``` diff --git a/resources/references/adr/2023-09-22-catalog-import-api.md b/resources/references/adr/2023-09-22-catalog-import-api.md new file mode 100644 index 000000000..1fc87c509 --- /dev/null +++ b/resources/references/adr/2023-09-22-catalog-import-api.md @@ -0,0 +1,294 @@ +--- +title: Catalog Import API +date: 2023-09-06 +area: core +tags: [catalog, product, category, import, api] +--- + +# Catalog Import API + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-09-22-catalog-import-api.md) +{% endhint %} + +## Context + +Our current API for importing entities is very flexible, +however: + +* The documentation is lackluster and brittle due to the fact that the schema maps directly to the databases. +* This exposure of implementation details leads to issues documenting the api but also using it as certain fields and entities are just bookkeeping. +* The size and complexity of the current schema make it impossible to generate clients or mappers for it. +* Variants and Media uploads have to be fully managed by the client +* The api puts the effort of error handling onto the client + +Resulting in increased effort to build and maintain middlewares. + +We want to provide an HTTP API and a PHP API for importing catalog related entities independent of the database schema. +And reduce the amount of implementation details developers have to know about when integrating Shopware within existing +systems e.g., ProductVisibility, ProductMedia Entities, Media creation, errors, etc. + +## Decision + +We will introduce a new PHP Layer for importing catalog entities. It will: + +* Evolve independently of the current database schema +* Be strongly typed +* Support referencing associations by different fields (eg assign tax by name instead of id) +* Be simpler to import images and generate variants +* Support asynchronous importing +* Have advanced error handling and reporting + +We will expose this new PHP layer over an HTTP API with full Open API specifications. + +## Consequences + +We will first release an @experimental feature with a very limited scope, a minimal data set. +We will not support the full breadth of options available to the catalog system. + +This will allow us to gather feedback, make changes and provide a well-designed system. + + +### API Overview + +**PHP api** + +```php +// for the structure of $myProductData see: Appendix A +function myMigration(array $myProductData) +{ + + $productBatch = ProductBatch::fromRequestData($myProductData); + $session = $this->importFactory->startSession('my-import'); + $session->addProductRecords($productBatch); + $session->commit(); + // not required, as import runs async + while(!$session->done()) { + echo $session->status(); + } +} +``` + +**REST api** + +This API is exposed as the following HTTP endpoints: + +### Starting an import + +`POST /api/import/catalog/start`: + +This creates an import return a unique identifier to the import. This endpoint accepts a configuration payload, allowing you to customize the import, for example: + +* The name of the import, eg `product-import-202307012` +* The indexing configuration +* Reporting webhooks for errors +* And so on + +### Creating/Updating entities + +`POST /api/import/{{import_id}}/record`: + +#### Examples + + * [Create a product with a new media](../../../.gitbook/assets/adr/catalog-import/examples.md#create-a-product-with-a-new-media) + * [Update a product and update its media](../../../.gitbook/assets/adr/catalog-import/examples.md#update-a-product-and-update-its-media) + * [Create a category and assign products to it](../../../.gitbook/assets/adr/catalog-import/examples.md#create-a-category-and-assign-products-to-it) + * [Create a product with a custom entity](../../../.gitbook/assets/adr/catalog-import/examples.md#create-a-product-with-a-custom-entity) + * [Update a custom entity](../../../.gitbook/assets/adr/catalog-import/examples.md#update-a-custom-entity) + +This endpoint is used to create and update entities. The data is stored for later import, specifically during the `commit` http call. + +Note: They will not be imported in to the system yet. + +This stage takes care of resolving specific associations and existence checks. + +It is important to note that while it is possible to create nested entities, it is not possible to update them via the parent entity. Updates should be performed on the root level. + +For example: you can create a product with nested media. If you want to update the media, you must do it at the root level, by referencing the entity directly (by its ID or any resolvers the entity supports). + +This stage will also extract some inline records, for example, media records specified directly on the product and create separate records for them. +After that process, we will sort records based on their dependencies, to optimize for bulk inserts. This is only performed for entities which should be created. + +Custom entities can also be added and updated in the same manner as normal entities, however, they must be defined under the `extensions` key due to the fact that the schema is not known ahead of time. See above examples relating to custom entities. + +For example, consider the following record set: + +``` +├── Product 1 +│ ├── Tax 1 +│ ├── Media 1 +│ ├── Media 2 +├── Product 2 +│ ├── Tax 2 +│ ├── Media 3 +``` + +The entities will be assigned a level value like so: + +| Entity | Level | +|-----------|-------| +| Tax 1 | 1 | +| Tax 2 | 1 | +| Media 1 | 1 | +| Media 2 | 1 | +| Media 3 | 1 | +| Product 1 | 0 | +| Product 2 | 0 | + +With this data, we can decide which records can be processed at the same time. The highest level has no dependencies. In this case, level 1. All of those records can be processed at the same time. Once done, the next level can be processed. + +The records will be saved into a simple intermediate store, in a generic structure, not related to the database or DAL. + +The store will either be something like redis or mysql. Regardless, this store will be behind an interface and will be exchangeable. + +You can push as many records to this end point as you wish. When you are done, you call the commit endpoint. + +Calling 'record' is not possible after an import has been committed. An error will be returned. + +### Deleting entities + +`POST /api/import/{{import_id}}/record/delete`: + +#### Examples + +* [Delete a product & media](../../../.gitbook/assets/adr/catalog-import/examples.md#delete-a-product-and-media) + +This endpoint is used to delete entities from the system, as with POST calls to the `/record` endpoint, the deletions are only queued. They delete will happen during the `/commit` call. + +The payload is a simple array of entities with an array of ID's to delete. + +### Unassigning nested entities + +`POST /api/import/{{import_id}}/record/unassign`: + +#### Examples + +* [Un-assign a media from a product](../../../.gitbook/assets/adr/catalog-import/examples.md#un-assign-a-media-from-a-product) + +This endpoint is used to remove entity associations. + +### Committing an import + +`POST /api/import/{{import_id}}/commit` + +This endpoint is used to commit the records and begin the actual import into Shopware. + +As previously noted, records will be batched by workers according to their level, traversing from the highest level to the lowest. During this process, the generic record structure will be mapped to the expected DAL structure. + +Internally, the sync API will be used to perform the actual "inserts". + +Calling commit can only be performed once. Any subsequent commits will return an error. + +### Getting the status of an import + +`POST /api/import/{{import_id}}/status` + +This endpoint returns the status of the import. + +Subsequent calls to the status end point will return the current status of the import and errors reported so far. + +The status will be reported as one of `started`, `importing`, `cancelled` or `done`. + +When the import is finished, `status` will be reported as `done`, and all the errors will be included and the duration and totals will reflect the total imported. + +### Cancelling an import + +`POST /api/import/{{import_id}}/cancel` + +This endpoint cancels a non-committed import. It's not possible to cancel an already committed import. + +Cancelling an import will delete all records pushed to it. This might be useful in case some erroneous data was pushed to the import. + + +### Http Schema +[Openapi schema](../../../.gitbook/assets/adr/catalog-import/import-http-schema.yml) + +### Resolver Concept + +When associating records, it will be possible to specify the ID of the association, for example, when you want to assign a tax entity to a product, you can pass the tax ID. + +The problem here is that you may not know, or want to keep a record of the referenced entity ID. + +We will introduce the concept of Resolvers to solve this problem. + +The job of a resolver is to look for references to its entities (in this case Tax) and resolve any of them which are not ID references, to ID references. + +In the initial experimental release, we will ship with a minimal set of reference resolvers to prove the concept. We will also explore how we can open it up for extension developers to provide reference resolvers and have them automatically integrated with the API specification. + +## Media Uploading + +In the first iteration, we will only support uploading media files from an external URL. The image will be downloaded during import. + +We will still honor the configuration setting `media.enable_url_upload_feature`. If the config is disabled, the import will skip importing media. + +## Error handling + +### Whilst adding import records + +Whilst adding import instructions via the '/record' endpoints, errors will be directly reported to you regarding resolving. For example, if you try to update an entity which does not exist. Or assign an entity to another that does not exist. + +See [Error Response: Resolving Root Entities](../../../.gitbook/assets/adr/catalog-import/examples.md#error-response-resolving-root-entities) for the request and response when trying to update a product which does not exist. + +When a nested entity does not exist, the error is reported under the root entity. When an error occurs in a nested entity, the root entity will not be updated. + +See [Error Response: Resolving Nested Entities](../../../.gitbook/assets/adr/catalog-import/examples.md#error-response-resolving-nested-entities) for the request and response when trying to assign products which do not exist to a category. + +### Whilst committing + +* When an error occurs created a nested entity, the root entity will also fail, it will not be updated. +* When a root entity cannot be updated or created, all nested entities will be deleted. Note that it is not possible to update nested entities, so there is no issue with rolling back nested entities to their previous state. + +See [Error Response Status Root](../../../.gitbook/assets/adr/catalog-import/examples.md#error-response-status-root) for the request and response when trying to create a product with a not unique product number. +See [Error Response Status Nested](../../../.gitbook/assets/adr/catalog-import/examples.md#error-response-status-nested) for the request and response when trying to create a product with a media which cannot be downloaded. + + +## Error reporting + +Errors will be reported in real time to a given webhook URL, or via the `status` end point under the error key. The properties are described in the `Failure` object of the API specification. + +Note: the webhook will not be implemented in the first draft and the general concept is open for discussion. + +The errors will be keyed by the id of the entity given during the `record` endpoint. + +Errors will be classified with a severity using the levels from [RFC5424](https://datatracker.ietf.org/doc/html/rfc5424). + +# Appendix + +## Appendix A: An example PHP payload to import a product with categories, media and tax records. + +```php +$myProductData = [ + [ + 'id' => '018a6b222b5a734d956fb03dda765bf8', + 'name' => 'Cool Prod 1', + 'productNumber' => 'COOLPROD1', + 'tax' => [ + 'name' => 'Reduced rate 2', //The tax record is referenced via its name attribute instead of its ID + ], + 'prices' => [ + [ + 'currency' => 'EUR', + 'gross' => 15, + 'net' => 10, + 'linked' => false, + ], + ], + 'stock' => '100', + 'categories' => [ + [ + 'path' => ['Home', 'Category 2', 'Category 3'], //The product will be assigned to the category: Home/Category 2/Category 3 + ], + ], + 'media' => [ + [ + //this image will be downloaded and assigned to a media record, and then assigned to the product + 'url' => 'https://images.unsplash.com/photo-1660236822651-4263beb35fa8?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1170&q=80', + 'title' => 'pommes', + 'alt' => 'alt', + 'filename' => 'other-media.jpg', + ], + ], + ], + ]; +``` diff --git a/resources/references/adr/2023-10-17-add-unique-identifiers-for-checkout-methods.md b/resources/references/adr/2023-10-17-add-unique-identifiers-for-checkout-methods.md new file mode 100644 index 000000000..0a45e6af9 --- /dev/null +++ b/resources/references/adr/2023-10-17-add-unique-identifiers-for-checkout-methods.md @@ -0,0 +1,52 @@ +--- +title: Introduction of Unique Identifiers for Checkout Methods +date: 2023-10-17 +area: Checkout +tags: [payment, shipping] +--- + +# Introduction of Unique Identifiers for Checkout Methods + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-10-17-add-unique-identifiers-for-checkout-methods.md) +{% endhint %} + +## Context +In the current implementation, there exists a challenge for extension developers in uniquely identifying payment and shipping methods using identifiers. +This issue is particularly significant for app servers, as it necessitates calls to the Shopware Admin API for the identification of payment and shipping methods based on their respective IDs. + +## Decision +We will introduce a new property called `technicalName` to both the `payment_method` and `shipping_method` entities. +This `technicalName` property will serve as a unique identifier for payment and shipping methods, significantly simplifying the identification process. + +While the `technicalName` field will be optional within the database and API to ensure backward compatibility, it will be made mandatory in the Administration. +This ensures that merchants will update their payment and shipping methods accordingly for the upcoming requirement. +An unique index will ensure uniqueness. +Starting from version 6.7.0.0, this `technicalName` field will also become required within the database and the API. + +As part of the database migration process, the `technicalName` field will be automatically generated for the default payment and shipping methods provided by Shopware, as illustrated below: + +| Type | Name | Technical Name | +|----------|------------------|-------------------------| +| Payment | Debit | payment_debitpayment | +| Payment | Invoice | payment_invoicepayment | +| Payment | Cash on Delivery | payment_cashpayment | +| Payment | Pre Payment | payment_prepayment | +| Shipping | Standard | shipping_standard | +| Shipping | Express | shipping_express | + +Furthermore, all payment and shipping methods provided by apps will also benefit from the automatic generation of their `technicalName`. +This generation will be based on the app's name and the `identifier` defined for the payment method in the manifest: + +| App Name | Identifier | Technical Name | +|----------|--------------------|-----------------------------------| +| MyApp | my_payment_method | payment_MyApp_my_payment_method | +| MyApp | my_shipping_method | shipping_MyApp_my_shipping_method | + +## Consequences +Plugin developers will be required to supply a `technicalName` for their payment and shipping methods, at least beginning with version 6.7.0.0. + +Merchants must review their custom created payment and shipping methods for the new `technicalName` property and update their methods through the administration accordingly. + +It is essential to exercise caution when modifying the `technicalName` through the administration, as such changes could potentially disrupt existing integrations. diff --git a/resources/references/adr/2023-10-19-bootstrap-css-utils.md b/resources/references/adr/2023-10-19-bootstrap-css-utils.md new file mode 100644 index 000000000..2ec69f22c --- /dev/null +++ b/resources/references/adr/2023-10-19-bootstrap-css-utils.md @@ -0,0 +1,113 @@ +--- +title: Make more use of Bootstrap tooling and remove !important from Bootstrap CSS utils +date: 2023-10-19 +area: storefront +tags: [storefront, bootstrap, css] +--- + +# Make more use of Bootstrap tooling and remove !important from Bootstrap CSS utils + +{% hint style="info" %} +This document represents an architecture decision record (ADR) and has been mirrored from the ADR section in our Shopware 6 repository. +You can find the original version [here](https://github.com/shopware/platform/blob/trunk/adr/2023-10-19-bootstrap-css-utils.md) +{% endhint %} + +## Context + +At the moment the Storefront is implementing a lot of custom SCSS inside `app/storefront/src/scss`. +Some custom SCSS is not necessary, and it already exists within Bootstrap as a utility. +For example default spacing with a custom selector: + +```scss +.register-login-collapse-toogle { + margin-bottom: $spacer; +} +``` + +This can be replaced with a Bootstrap [spacing utility](https://getbootstrap.com/docs/5.2/utilities/spacing/) in the HTML and the SCSS can be completely removed: + +```diff +-