-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Added service contract technical guidelines #3421
Changes from 2 commits
a14ae2d
62d65a9
1527d6c
ae0b146
d9786b8
9b63185
361769c
c9c0d3d
793075f
a3fe869
ee3d95e
f57c274
c2102ee
4de91aa
0327977
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
group: coding-standards | ||
title: Magento technical vision | ||
--- | ||
|
||
A collection of component technical vision documents describes the desired state of Magento product. | ||
Each individual technical vision relates to a set of modules logically grouped together. Technical vision typically describes component's place in the system, its architecture, extension scenarios and a list of invariants which must be preserved at all times during component refactoring or extension to ensure consistency. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
group: coding-standards | ||
title: Magento technical vision | ||
--- | ||
|
||
A collection of component technical vision documents describes the desired state of Magento product. | ||
Each individual technical vision relates to a set of modules logically grouped together. Technical vision typically describes component's place in the system, its architecture, extension scenarios and a list of invariants which must be preserved at all times during component refactoring or extension to ensure consistency. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -532,7 +532,75 @@ class View extends Template | |
|
||
### 6.4. Service Contracts (Application) layer | ||
|
||
We are reviewing this section and will publish it soon. | ||
6.4.1. Location | ||
|
||
6.4.1.1. Service contract interfaces should be placed in separate API modules. All other modules will depend on the API module, while implementations could be easily swapped via `di.xml`. For example, if module is named `MyModule`, its APIs should be declared in the module named `MyModuleApi`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add explicitly the "Api" part requirement for the name (not just in an example). |
||
|
||
6.4.1.2. Service interfaces which should be exposed as web APIs must be placed under `MyModuleApi/Api/Service` directory. Service data interfaces must be placed under `MyModuleApi/Api/Data`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does API folder still makes sense for api modules? Maybe we should have something like below? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see strong arguments for either of the approaches. Considering existence of BTW, API modules might have more directories in the root, please take a look at https://github.com/magento-engcom/msi/tree/2.3-develop/app/code/Magento/InventoryApi as an example |
||
|
||
6.4.1.3. All other APIs, including explicit extension points like Chain or Composite implementations must be placed under `MyModuleApi/Model`. | ||
|
||
6.4.1.4. Arbitrary directory nesting can be used for APIs to improve code structure. For example, `MyCompany\MyModuleApi\Api\Service\Relations\DeleteInterface`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest to call it RemoverInterface, name should be noun. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have examples in MSI which use verbs for service names and the only method To me it sounds more natural to have a verb, even if it is not expected for the interface. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Actually not. This rule is applicable for Classes which represent "things/entities", therefore you want to name them with nouns, and methods inside these classes represent actions over this entity so that methods usually represented by verbs. But this approach does not work with Functors (Function objects) - https://en.wikipedia.org/wiki/Function_object
In the beginning, we even wanted to use So, if we deal with Functors it's much more natural to them to be represented by verbs, as functors represent "action" by their nature. You can read more about naming of factors here - https://stackoverflow.com/questions/10811169/do-you-use-nouns-for-classnames-that-represent-callable-objects |
||
|
||
6.4.2. Types Hierarchy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider moving this section to Service implementation. |
||
|
||
6.4.2.1. If service data interface should be extensible, it must extend from `Magento\Framework\Api\ExtensibleDataInterface`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we have examples of non-extensible data interfaces? Potentially just Value-Objects could be kept non-extensible, as an identity of value object is defined by the whole set of nested attributes, so extending the Value Object could break business invariants of such objects. So, my suggestion here is to say that: "All Data interfaces must extend from |
||
|
||
6.4.2.1. Extensible data interfaces must not form hierarchies. Extension attributes do not work for child extensible interfaces. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's covered in main document (point about super types). But if you want to keep it, I suggest to elaborate more, might not be obvious what is the issue. |
||
|
||
6.4.3. Service Interface Structure | ||
|
||
6.4.3.1. Methods named similarly must serve similar purpose across different services, but still might have different signatures. | ||
|
||
6.4.3.2. Services must only operate on scalar types or service data interfaces. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about arrays (as lists)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed since it is very close to |
||
|
||
6.4.3.3. Service contracts should not be used for read scenarios on the storefront, GraphQL should be used for storefront scenarios instead. Check out [web API technical vision]({{ page.baseurl }}/coding-standards/technical-vision/webapi.html) for more details. | ||
|
||
6.4.3.4. Each service interface should declare a single public method. Interface name should reflect task/action to be performed. Example `Magento\InventoryApi\Api\StockSourceLinksDeleteInterface::execute(array $links)`. The only exception is Repository API which may be added for convenience and must be limited to singular CRUD operations and `getList($searchCriteria)`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a best practice to make name a noun, I would suggest to follow it. In this example we would have StockSourceLinksRemoverInterface. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we consider having a single-method interfaces and allow a facade with combination of such interfaces for convenience? Repository can be one of examples. Maybe there can be more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Single method interfaces should operate on arrays of entities. Repositories operate on individual entities. We do not prohibit creation of facades anyway. |
||
|
||
6.4.4. Service Method Signature | ||
|
||
6.4.4.1. Service contracts should not apply presentation layer formatting to the returned data. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider moving this section to Service implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both sections have a number of items, and combining them would reduce readability. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some of the items not related to method signature. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please propose a list of exact items to be moved. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 6.4.3.1 and 6.4.3.3. |
||
|
||
6.4.4.2. Service data interfaces represent domain entities and can reference related data interfaces. | ||
|
||
6.4.4.3. Service data interfaces must be implemented as DTOs with a set of symmetric getters and setters. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a limitation of the existing WebAPI framework implementation, but not the desirable state. Btw this limitation has been eliminated recently with a code contribution by @phoenix128 so that new DTOs could be introduced without unnecessary setter methods and being implemented in a pure immutable fashion. Based on the above I would say that precise statement should look like: "Service data interfaces must not contain any business logic. They should represent a container of data transferable over the wire. All the business logic should be moved to services." |
||
|
||
6.4.4.3. Strict typing is enforced for Service and Data interfaces located under `MyCompany/MyModuleApi/Api`. Only the following types are allowed: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This item is similar to 6.4.3.2. (just about output). Why that one is in the Structure section and this one is in the Signature section? Probably, both should be in the Signature section, and ideally be declared together. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed 6.4.3.2 |
||
|
||
* Scalar types: `string` (including Date and DateTime); `int`; `float`; `boolean` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest to create and use our own types for date types. We could have API similar to what Java has, Date, DateTime, LocalDate, LocalDateTime. I can look into this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this sounds like a more sophisticated topic, as you are proposing to introduce Value-Objects into Magento. What distinguishes Value Object from Entity:
Even if we would introduce such entities, not sure that we should distinguish Value Objects from Data Interfaces (entities) in this document. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be clarified separately in the future in scope of independent PR. |
||
|
||
* Data interfaces | ||
|
||
* One-dimensional indexed arrays of scalars or data interfaces: for example `string[]`, `\MyCompany\MyModuleApi\Api\Data\SomeInterface[]`. Hash maps (associative arrays) are not supported | ||
|
||
* Optional scalars or data interfaces: `string|null`. It is prohibited to use just `null` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's better to call this as Nullable types as this term maps to the functionality introduced in PHP 7.1 Also, term "optional" is applicable just for input arguments to methods, but not to return values. |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should also mention void as an allowed type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might not be supported by the web APIs, but I think this is the desired state. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we fixed that for Web API framework in MSI scope and delivered to 2.3.0 Magento release So that now all MSI services modifying data exposing through the web API return void <route url="/V1/inventory/source-items" method="POST">
<service class="Magento\InventoryApi\Api\SourceItemsSaveInterface" method="execute"/>
<resources>
<resource ref="Magento_InventoryApi::source"/>
</resources>
</route> declare(strict_types=1);
namespace Magento\InventoryApi\Api;
/**
* Service method for source items save multiple
* Performance efficient API
*
* Used fully qualified namespaces in annotations for proper work of WebApi request parser
*
* @api
*/
interface SourceItemsSaveInterface
{
/**
* Save Multiple Source item data
*
* @param \Magento\InventoryApi\Api\Data\SourceItemInterface[] $sourceItems
* @return void
* @throws \Magento\Framework\Exception\InputException
* @throws \Magento\Framework\Validation\ValidationException
* @throws \Magento\Framework\Exception\CouldNotSaveException
*/
public function execute(array $sourceItems): void;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @maghamed how would REST and SOAP responses look like in this case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @paliarush for example, like this https://devdocs.magento.com/guides/v2.3/rest/tutorials/inventory/create-sources.html so, just an empty array returned in accordance with https://jsonapi.org/ standard : |
||
6.4.4.4. Service contracts should support batch data whenever possible. For example, entity persisting interface should accept an array of entities to persist instead of a single entity. Customizations via plugins must be adjusted respectively. Batch retrieval operations must accept `SearchCriteriaInterface` and return `SearchResultInterface` to support pagination. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd recommend to avoid phrases like "whenever possible". This meaning is included in the "should". On the contrary, "must" means that there are no exceptions. |
||
|
||
6.4.4.5. Command operations (the ones which modify the state) must support asynchronous execution. Void must be used as return type, the status of the operation may be checked by polling of the respective query operation. Create operations must support UUID generated on the client side. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the description in brackets applies for all Command operations it's not accurate. I propose to change the statement on "All services which modify the state must support asynchronous execution." |
||
|
||
6.4.4.6. Command operations (the ones which modify the state) must support asynchronous execution. Void must be used as return type, the status of the operation may be checked by polling of the respective query operation. Create operations must support UUID generated on the client side. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like 6.4.4.6 fully duplicates 6.4.4.5 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you check status of operation if you receive void (sorry if it was discussed and I missed)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you use UUID as an identity of the operation, you can check the status based on that UUID which is known in advance, so that no return value needed to resolve the current status. So, there is no necessity to return "promise" or similar object There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
6.4.4.7. Data objects returned by service contracts should be fully loaded to ensure consistency. | ||
|
||
6.4.5. Service Implementation | ||
|
||
6.4.5.1. Service implementations and plugins must not rely on storage-specific integrity features, such as foreign key constraints. | ||
|
||
6.4.5.2. Replace strategy should be used to persist main entity fields/attributes, child entities and relation links. | ||
|
||
6.4.5.3. During update operations, Web APIs with `PATCH` HTTP method and all action controllers that accept entities should pre-load them first, merge request data on top of that and provide full data to service. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this logic be part of the service? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it should NOT be, the same as deciding how to guarantee atomicity of these changes and prevent data inconsistency (i.e. wrapping all the process into the transaction). This is the responsibility of the Infrastructure layer, but not the Domain one to which service belong to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think yes. This item was migrated from previous discussions, @buskamuza @maghamed do you know what is the history behind this item? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We definitely don't have such ability on web API framework level at the moment and if agree to have this statement in technical vision, will have to start supporting PATCH with pre-load. |
||
|
||
6.4.5.4. If service method needs to return a modified version of the argument, the original argument object must not be modified and its copy should be modified and returned instead. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about a case when the argument should be modified, but not returned? Arguments should not be modified in any case. |
||
|
||
6.4.5.5. Services should not apply ACL rules to methods or returned data. | ||
|
||
6.4.5.6. If multiple data scopes available, within one service call entity should be persisted only for one specific data scope. | ||
|
||
6.4.6. Exception Handling | ||
|
||
6.4.6.1. Service methods must only throw exceptions defined as part of service contract layer. Exceptions thrown from lower layers should not be exposed outside and must be wrapped with appropriate service contract layer exception. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not specific to Service Layer and covered by
|
||
|
||
## 7. Configuration | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../v2.1/coding-standards/technical-vision/index.md |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../v2.2/coding-standards/technical-vision/index.md |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe call it Location of API interfaces?