cancel
Showing results for 
Search instead for 
Did you mean: 

Service Contracts in Separate Packages

akent99
Regular Contributor

Service contracts (defined using PHP interfaces) are currently included in same module that implements the interfaces and are versioned with the rest of Magento. One strategy under consideration is to separate into two modules the PHP interfaces that define service contracts and their implementation. The version number of the modules holding the service contracts would then only change if the contract changes. This provides more stability for extension developers.

 

What Are Service Contracts?

 

Service contracts are PHP interfaces that define the API for interacting with a given module. By using PHP interfaces, this allows different implementations to be swapped in more easily. For example, see the Customer module Api directory on GitHub.

 

The concept of contracts in this blog post is restricted to PHP interfaces. The definition needs to be expanded to whatever one module may need to depend on in another module, such as block ids in layout files and events, but that is beyond the scope of this blog post.

 

Why is Versioning So Hard?

 

The source code for Magento Community Edition is currently all in one git repo. This may change in the future, but that is how the code is today. Git only supports branching at the git repo level – you cannot branch different subdirectories independently. Magento creates a repository branch each minor release of Community Edition (2.0, 2.1, 2.2 etc). Patches for that release level are then created on that release branch (2.0.1 is created on the 2.0 release branch, 2.2.5 is created on the 2.2 release branch, etc).

 

To avoid the same version number of a module existing on two branches, each minor release (e.g. going from 2.1 to 2.2) always increments the minor or major number (first or second digit) of every module in the repository; on the release branch, only patch number increments are allowed (third digit), never the major or minor number (first or second digit). This strategy guarantees you can never get two branches in the git repository having different code with the same version number. It also guarantees that all patches are cumulative on release branches. The problem is it sometimes forces the minor version number change for a module even if the module is unchanged. This is unfortunate.

 

If each module was in a separate git repo, a different strategy could be used. When moving from 2.1 to 2.2 a decision could be made per module whether the same minor version of that module can be shared between the two release levels. For example, 2.1 and 2.2 could both point to 100.1.* of the Magento Customer module. If it is shared, any patch to that module would be shared by both releases. That also avoids back-porting bug fixes to as many releases.

 

The only reason this is not done today is all the modules are in the one git repo and share the same branching strategy. This may change in the future, but has not been done to date for two pragmatic reasons:

 

  • It is more cumbersome for core developers having to manage hundreds of separate repositories. A global change would require separate coordinated releases of multiple modules.
  • There have been higher priority things to fix first. Changing the approach requires work on release scripts, test frameworks, documentation, etc.

 

This blog post is a first step in the direction of splitting the Magento code base into independent git repos.

 

Proposal

 

The proposal is as follows:

 

  • Move everything except service contracts (the Api directories) out of existing modules (such as “Magento_Customer”) into new modules (such as “Magento_CustomerImpl”). You can think of this like creating a PHP interface (the contract module) that a class implements (the implementation module).
  • Create a new git repo per contract module. Contract modules will then only have new versions created if the contract changes, providing projects and extension developers more stability. (Extensions specify the module version they depend on. If they depend on a contract module, and that contract module major/minor version is shared between two releases, the extension does not need to change to support the new release.)
  • Implementation packages continue to be versioned per Community Edition release as they are today.
  • Product metapackages (the metapackage that lists all the exact module versions for, say, Community Edition 2.1.6) would reference both the contract packages and the implementation packages that implement those contracts.
  • Extensions may need to be updated to depend on the new implementation modules if there is an implementation-level dependency. (Such dependencies are undesirable, but may still be needed at times.)

 

Considerations

 

There are some considerations with this proposal.

 

  • There will be more modules than today due to the separation of contract modules from implementation modules.
  • Existing code will need modifying due to code being moved.
  • This may be a good opportunity to group related contract modules together so there is a “promotions contract module” even if today the functionality resides in a range of implementation modules.

 

Conclusions

 

The goal of separating contracts from implementation modules is for contracts to capture all the touch points needed by extensions. If an extension can 100% depend on a contract module and not on an implementation module, this provides Magento the flexibility and freedom to refactor and improve the implementation module with the stability that extension developers crave. As a result, extensions could support multiple Magento releases without change. To be effective, this will require Magento and extension developers coming together to work out what each contract should be.

 

5 Comments
patrick vbergen
Senior Member

I think this is a bad idea.

 

Separating the API of a module into another module just leads to extra complexity for the developer. It introduces extra directories, extra dependencies, backward incompatibilities, and bugs.

 

I don't understand the problem well enough to give an alternative solution, but perhaps one can be found by introducing another versioning system just for API's. Just a version number for the API. Since Composer will not be able to handle it, perhaps a helper application can be written for the extension developer who needs to check versions.

 

paistal
Occasional Visitor

Is doubling the amount of packages really going to solve this? Wouldn't it be enough if interfaces would be kept in a separate folder from implementation (currently they're intermingled which makes it harder to browse the available interfaces as it forces one to surf countless sub-folders).

 

The issue that I would like to see being addressed is indeed the fact that there aren't enoguh interfaces to begin with (or not enoguh public methods exposed via the beforementioned interfaces). I have a suspicion, though, that in the end (if we can imagine infinite amount of extensions created against the framework), someone needs every conceivable public method for something so you'd end up listing everything in the interfaces 

 

How about (ideas in no specific order):

  • Tap into the code generation and something similar to interceptors to provide a way for interfaces to be injected onto used core models (let's call them generated adapters) which would allow extension creators to get to a point where they write code only against interfaces even when there isn't one in the module that provides the implementation.
  • Guideline for extension creators to never use implementation directly and create an interface for it instead (in case the implementation does not have one) and inject it via code generation (something that's not possible at the moment).

This would allow the extension creator to create light-weight adapter in case the core-implementation changes somewhere in the future.

akent99
Regular Contributor

The "service contracts" are always in Api/ of a module - the goal is to move everything towards service contracts between modules. At present there are other interfaces scattered around the place, but they are not service contracts.

 

I don't think I expressed the problem well enough. The issue at present is including the interfaces in modules is if an extension is an extensions composer.json file specifies the versions the module is compatible with. When a new minor version of a module is released (triggered by a product release, such as the upcoming 2.2 release), all extensions MUST be re-released because the module has a new version released ALWAYS.

 

Sounds like your point (if I paraphrase) is the benefits of having a separate module for service contracts are still in the noise compared to the problems of Magento APIs not being clean and more consistent. Magento should get the APIs cleaned up first before worrying about versioning the APIs separately.

Pronto
M2 Certified

So basically you want to keep API / Protocol version separate from extension / module version. I can relate with that; it's one way to make already released extensions future proof (up to the point when API changes enough to warrant API version update which should happen WAY less often than extension updates / fixes). It makes possible to adapt "release early, release often" approach we're sorely missing.

aleron75
Magento Master

I like the idea of breaking Service Contracts in separate packages and wouldn't be afraid of a growing number of packages.

 

As a developer, provided I stuck to public interfaces (that is, Service Contracts) this would be a great step forward.

 

After all, other SAAS or API-based e-commerce platforms are extensible and they only have public interfaces exposed by definition.

 

I agree on the importance of defining guidelines to push developers to adhere to public interfaces as much as possible or even disallowing the usage of private code; the latter is a bit more difficult to obtain in the short run.

 

Cheers,

 

-Alessandro