Magento 2 Service Layer

One of the “cool” technologies in Magento 2 is the new service layer. In this post I describe why I think the service layer is going to be one of the key new technologies in Magento 2.

Disclaimer: I work with the Magento team, but this post contains personal opinions and perspectives and does not necessarily reflect those of eBay Inc.  Or in other words, this post is not binding!

Acknowledgements: This post has borrowed heavily from Chris O’Toole’s (@frostmagus) presentation at the Magento Imagine 2014 conference.

What is the Service Layer?

In Magento 2, the Service Layer is a new layer that sits between the presentation tier and business logic. The objective is to have all calls from other modules go via the service layer, as well as all calls from the presentation tier (that is, blocks, templates, and controllers). In addition, the service layer can be easily exposed as a REST web service or SOAP endpoint.

M2-service-layer-arch

Benefits

Combined, this approach:

  • Encourages moving business logic out of templates and blocks below the service layer. This thins out templates, making customization easier.
  • Ensures that all business logic can be easily made available to external applications as web services – a simple XML configuration file is all that is required. (This was a problem at times in Magento 1 where web service code logic included duplicate logic of the presentation tier, which caused problems when extensions changed UI functionality but missed the web service API, resulting in inconsistent behavior.)
  • Can make debugging easier – breakpoints in a debugger is easier when you know where all calls will flow through.
  • Makes it easier to replace business logic – all calls funnel through a single API. Before other modules would jump into the code of another module at any point, making it harder for an extension to guarantee it intercepted all calls correctly.
  • Use of interface definitions makes it easier to completely replace the implementation of a service with an alternative definition.  (One interesting idea here is to have a module that only contains the service interface definition and have other modules provide alternative implementations of the service interface. This may be overkill for every module service layer, but for key APIs like shipping this could be an interesting idea.)

Implementation

The service layer for a module is a set of PHP interfaces, data types for holding data to be passed across the service layer, and implementations of those interfaces. The interfaces and methods tend to be use-case oriented. That is, methods that provide some logical business operation (such as create a order, add to a cart, create a new customer account, or compute shipping costs). Because services can also be easily exposed as web services, efficient design has a single API call provide and return a rich data structure (rather than requiring many smaller calls with simpler, shallower APIs). This is to ensure a single web service request and response can complete an entire operation. The goal also is for a single call to be stateless and atomic, making application design simpler.

Because of the need of the Magento framework to be able to serialize arguments and return values to/from JSON (for the REST API) and XML (for the SOAP API), there are restrictions placed on the data types that can be used. Withou bogging down into details, the legal type constructs for a data object are much like a JSON object. These are referred to as “Data Objects” and are immutable. They are strongly typed (not just PHP arrays of name/value pairs.) The “builder pattern” is used to create such objects, with a create() call to form the final immutable data object. Example code is below.

A method of a service layer may have a method prototype such as

public function createCustomer(
  CustomerDetails $customerDetails,
  $password = null,
  $redirectUrl = null
);

(At the time this post was written, the above method can be found in GitHub at https://github.com/magento/magento2/blob/master/app/code/Magento/Customer/Service/V1/CustomerAccountServiceInterface.php. This may however change as the code is refactored further before release.)

A sample fragment of code using the builder pattern is as follows:

// Construct a Customer data object
$customer = $this->customerBuilder
    ->setFirstname(...)
    ->setLastname(...)
    ->setEmail(...)
    ->create();

// Construct a CustomerDetails data object
// (holds Customer plus address information, not shown here)
$customerDetails = $this->customerDetailsBuilder
    ->setCustomer($customer)
    ->create();

// Call the createCustomer() service method
$customer = $this->customerAccount->createCustomer($customerDetails);

Versioning

The goal of the service layer is to provide a stable interface for other modules to use. The intent is to have multiple releases of a module with the same service interface available. The goal is also to allow a module to expose several versions of the service interface. When significant changes are made, a new version of the interface should be created. If possible, the module should continue to support the old interface as well. This gives other extensions that use the service interface a period of time to migrate to the new service interface.

There will be times where the old API can no longer be supported. Versioning can reduce, but not completely eliminate upgrade pain. For example if a new mandatory value is required to create customer accounts, the old version of the service cannot be preserved. It may also be good practice to not support the old version of the interface across too many releases of the module, as it may encourage lazy behavior by other extensions. Supporting the previous and current version of a service across several releases can take some of the sting of an upgrade away without the pain of supporting every previous service interface version.

Performance

The service layer has been introduced for several reasons described above – performance is not one of them. A negative of the service layer is internal calls go through an extra level of processing. Rather than expose internal data structures like models and resources directly, internal data structures may need to be copied into data objects. I personally believe the benefits of the service layer are however significant and worth this slight additional overhead. The fact that data objects are immutable means returned objects can be cached if desired, so if multiple blocks on a page access the same service, there is the potential to cache the returned values rather than recompute the data structure multiple times. Currently there is no plan for Magento to support service layer caching support natively, but this is something a service could decide to implement if appropriate.

Exposing a Web Service

The following XML configuration binds a URL to a method of a service interface. This configuration is all that is required. The Magento framework performs all serialization/de-serialization to/from JSON. The framework also implements authentication infrastructure (controlled by the <resources> element).

<route url="/V1/customerAccounts/vip" method="POST">
  <service class=”Acme\…\V1\VipServiceInterface" method="createVipCustomer"/>
  <resources>
    <resource ref="Magento_Customer::manage"/>
  </resources>
</route>

A HTTP POST to the URL may look like:

{
  "customerDetails": {
    "customer": {
      "firstname": "James",
      "lastname": "Page",
      "email": "jp@example.com"
    }
  }
}

The response may look like:

{
    "id": "8",
    "website_id": "1",
    "created_in": "Default Store View",
    "store_id": "1",
    "group_id": "1",
    "firstname": "James",
    "lastname": "Page",
    "email": "jp@example.com",
    "created_at": "2014-05-14 19:58:51"
}

The names of the JSON fields are taken directly from the field names in the data objects.

Exposing via SOAP is similarly straightforward. The same request would be encoded as XML as follows:

<soap:Body>
  <def:acmeCustomerVipServiceV1CreateVipCustomerRequest>
    <customerDetails>
      <customer>
        <email>jp@example.com</email>
        <firstname>James</firstname>
        <lastname>Page</lastname>
      </customer>
    </customerDetails>
  </def:acmeCustomerVipServiceV1CreateVipCustomerRequest>
</soap:Body>

The response would be along the lines of (not all the namespace declarations are shown):

<env:Body>
  <ns1:acmeCustomerVipServiceV1CreateVipCustomerResponse>
    <result>
      <createdAt>2014-05-14 19:53:23</createdAt>
      <createdIn>Default Store View</createdIn>
      <email>jp@example.com</email>
      <firstname>James</firstname>
      <groupId>1</groupId>
      <id>42</id>
      <lastname>Page</lastname>
      <storeId>1</storeId>
      <websiteId>1</websiteId>
      <customAttributes/>
    </result>
  </ns1:acmeCustomerVipServiceV1CreateVipCustomerResponse>
</env:Body>

Conclusions

I believe the new service layer in Magento 2 is both nothing particularly radical (services are hardly a new software development concept) and a potential game changer in Magento 2. Encouraging moving business logic out of templates and behind a service layer by itself can be a thankless, time consuming task. But the benefits are numerous and strikes at the core of modularity and extensibility improvement. More on that in later posts.

The “cool” bit to me is that if you go to the effort of defining a service layer, with just a little XML configuration you can expose the service layer as REST or SOAP. Any module can do this. Reducing this barrier to creating customer web services to a Magento site greatly simplifies the ability to integrate a Magento site with other applications. But I will admit, maybe I am just being geeky.

One comment

  1. […] zweitest Posting erklärt den neuen Service-Layer, der für alle Arten von Aufrufe (Web-Requests, REST-Anfragen, SOAP-Callsetc.) eine gemeinsame […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.