* A broken crane in the Magento office and a quick solution implemented by one of the engineers is a typical Backward Compatible fix.Why backward compatibility is important
When developing a framework, after the release of several versions of it, you encounter the fact that the user (or in the case of Magento, the customer) who bought / downloaded / installed it, as well as the programmer, who develops his own extensions and modules based on the framework, want to upgrade between versions was as easy as possible.
')
For the customer / user, the process must be cost effective. The costs spent on moving to a new version of the product, which include: the upgrade itself, automated testing after the upgrade, regression testing, fixing the bugs introduced by the new version of the platform, updating customizations and third-party modules; should be minimal.
A programmer developing his extension for the platform wants his module to be
forward-compatible as long as possible, and so that he knows in advance in which release the code of his module will be broken by changes in the system so that he can prepare in advance a version of his module compatible with the new release.
As a result, the framework developer comes to an unexpected dilemma: the more changes you need to make (including bug fixes), the more likely that incompatible changes will be made that will break someone.
Code Backward Compatibility Policy
Semantic versioning (SemVer) provides an answer to the dilemma, helping not to refuse any of the options (frequent backward compatible releases).
The version numbers of the framework components are specified as MAJOR.MINOR.PATCH, where the update is:
- MAJOR - talks about incompatible API changes.
- MINOR - says that backward compatible functionality has been added
- PATCH - talks about backward fix bug
The backward compatibility policy applies to entities that are annotated with
@api in the code base.
The concept of public and private code
Developers with experience working with C ++ or Java are familiar with this concept. When the output of the program is delivered as a .h (header) file containing descriptions of external contracts, the semantics of the methods are easy to read by any text editor and the DLL file containing the compiled and compiled code that is hard to read and cannot be changed.
PHP at the language level does not provide this feature. Therefore, the frameworks written in this language have been looking for the “right” approach for a long time. For example, Magento 1, like many other frameworks of that time (Symfony 1) used the Inheritance Based API, when the framework for customizing and expanding its components offered to inherit from any of its classes, and redefine or extend behavior in the class of the heir. Accordingly, in Magento 1, private properties and methods were not used at all, and Magento core developers were obliged to follow two contracts (Public is a contract that forms public properties, methods and constants of entities; Protected is a contract of entity inheritance) and prevent the addition of backward incompatible changes to both . When all entities and all methods of these entities in the code base are API, then adding new changes and fixing bugs in any case can break someone.
In this regard, Magento 2 decided to follow a different approach - the Inheritance Based API was banned for internal use, and do not recommend using this approach to customize the framework classes or modules by third-party developers. Prohibited the use of Protected access modifier for attributes and methods of classes. The main idea in these changes is that having only Public and Private - we need to monitor only one contract - public. We have no contract inheritance.
The next step was to divide the code into
Public (analog header files) - code and annotation
@api
and
Private (analog compiled DLL) - code that is not annotated, saying that this is an API.
Closed code is not intended to be used by third-party developers. Thus, its changes will only increase the component's PATCH version, where this code has been changed.
Changes in Public Code always increase the MINOR or MAJOR version of the component.
We promise to be backward compatible for classes marked with
@api
inside the MINOR and PATCH component releases. In the case when we need to make changes to the class / method marked as
@api
, we mark it as @deprecated and it will be removed not earlier than the next MAJOR release of the component.
Examples of what falls under the definition of the Public Code in Magento 2
- PHP interfaces marked with
@api
- PHP classes tagged with
@api
- JavaScript interfaces marked
@api
- JavaScript classes tagged with
@api
- Virtual Type marked
@api
- Paths url
- Console commands and their arguments
- Less Variables & Mixins
- AMQP message queue topics and their data types
- Declaration of UI components
- Layout declaration of modules
- Events triggered by modules
- Module configuration schema added
- System Configuration Structure
API vs SPI (Extension Points)
PHP contracts in Magento can be used in different ways. There are 3 ways to use them:
- API usage : interface methods are called in PHP code
- Service Provider Interface (SPI) usage : the interface can be implemented, allowing the new implementation to extend the current behavior of the platform
- API and SPI at the same time
We expect that all calls to the module will pass through the API (service module contracts), modules also provide interfaces, implementing which external developers and customizers can provide an alternative implementation or extend the implementation out of the box. For example, for the Search functionality, there is an agnostic
contract for an adapter that executes a query. And it is assumed that this contract will be used as an API (just called in the business logic code). While a set of interfaces is provided, for implementing search adapters, by providing their own implementation, a third-party developer can add support for a new search engine, for example Sphinx. And this
should not break the business logic that uses the Search API.
API and SPI are not mutually exclusive, so we do not separate them separately in the code. SPI have the same annotation as the API -
@api
.
But who then determines what API is and what SPI is? - Those who use them, i.e. external developers
Rules for specifying dependencies
First of all, why modules should depend on each other in different ways depending on how they use another module.
Imagine that you have a
product category repository interface
.with methods:
The interface has already been published in the previous release of the system and it is used.
At some point, you realize that you forgot to add a getList method to this interface to search for categories by the specified search criteria. If you add this method to the current interface (and the implementation to the class that implements it), does it break the code that uses it?
If the code uses the interface as an API, i.e. just calls get / save / delete methods in business logic - the emergence of a new method will not bring problems. Existing code will continue to work. If the module code provides an alternative implementation for this interface (SPI), then we get an error during the build process, since the implementation interface class does not provide an implementation for one of its methods.
So we had a separate
service to search for categories .
In the case of removing a method from an interface, the opposite is true; for SPI, using it is not breaking changes; for an API, this is a problem.

API
If a module uses (calls) contracts declared by another Magento module, it must depend on the MAJOR version of this module. And the system provides backward compatibility for the entire major release.
{ ... "require": { "magento/module-customer": "~100.0",
SPI (Extension Points)
If a module offers its implementation for contracts declared by another Magento module, it should depend on the MAJOR + MINOR version of this module. And the system provides backward compatibility in a minor release.
{ ... "require": { "magento/module-customer": "~100.0.0",
Dependency on private code
If the module depends on an entity that is not marked as
@api
, then the module should depend on the MAJOR + MINOR + PATCH version. And already an upgrade to the next patch release can turn into problems for this module.
{ ... "require": { "magento/module-customer": "100.0.0",
The nearest major commercial release 2.2 - What to expect?
In current versions of Magento 2.0.x and 2.1.x, you cannot do without dependencies on the private code.
Because we have insufficient
@api
coverage for this. Some modules do not have service contracts at all (for example, wishlist). Therefore, third-party developers have no other choice except as a dependency on unlabeled api annotation classes.
API development is the most complex process in software design and development. And since we do not yet know exactly when the work on adding service contracts to all Magento modules will end, we decided in release 2.2 to mark
@api
all entities that are necessary for writing / customizing modules for magento by third-party programmers.
Those. in 2.2 we note all “
fair contracts ”, i.e. if the functionality that the module provides is implemented by a helper or a resource model. And it is impossible to get this functionality by calling the API module, then we mark this helper as
@api
.
For example, we have a ProductInterface that defines a set of operations on the product entity. And there is a product model that implements this interface. Since now the product model is inherited from an abstract model and accordingly has an abstract model contract, we cannot say now that a third-party developer can only rely on ProductInterface, and if someone provides their own implementation of this interface, they can easily change the internal implementation of Magento (if it is not a heir to the product model). Those. Quite a lot of code in Magento uses the get / setData method that came from an abstract model. Therefore, in 2.2 we will mark the product model as
@api
, despite the fact that we already have an API in the form of ProductInterface.
Before release 2.2. we consider all code as public, i.e. all classes are subject to backward compatibility policy. Not just for classes labeled @api
. The division of the concept into public and private will begin with the 2.2 release.Now, how do we determine what is used and what is not.
We analyze what dependencies between modules Magento uses internally. We also analyze which dependencies are used by extensions on the Marketplace (non api dependency). And if the dependencies are valid, i.e. this result cannot be obtained using the current API of the module - we mark the entity as
@api
* This article is part 1; Part 2, which will be released soon, will describe the limitations in the code that are introduced by the backward compatibility policy and what we are doing to stop refactoring from following BC policy and not accumulating technical debt due to these restrictions