diff --git a/design-documents/graph-ql/coverage/Cart.graphqls b/design-documents/graph-ql/coverage/Cart.graphqls new file mode 100644 index 000000000..2cf44bac2 --- /dev/null +++ b/design-documents/graph-ql/coverage/Cart.graphqls @@ -0,0 +1,59 @@ +type Query { + cart(input: CartQueryInput): CartQueryOutput +} + +input CartQueryInput { + cart_id: String! +} + +type CartQueryOutput { + cart: Cart +} + +type Cart { + id: String! + + line_items_count: Int! + items_quantity: Float! + + selected_payment_method: CheckoutPaymentMethod + available_payment_methods: [CheckoutPaymentMethod]! + + customer: CheckoutCustomer + customer_notes: String + + gift_cards_amount_used: Money + applied_gift_cards: [CartGiftCard] + + is_multishipping: Boolean! + is_virtual: Boolean! +} + +type CheckoutCustomer { + is_guest: Boolean! + email: String! + prefix: String + first_name: String! + last_name: String! + middle_name: String + suffix: String + gender: GenderEnum + date_of_birth: String + vat_number: String # Do we need it at all on storefront? Do we need more details +} + +enum GenderEnum { + MALE + FEMALE +} + +type CheckoutPaymentMethod { + code: String! + label: String! + balance: Money + sort_order: Int +} + +type CartGiftCard { + code: String! +} diff --git a/design-documents/graph-ql/coverage/CartAddressOperations.graphqls b/design-documents/graph-ql/coverage/CartAddressOperations.graphqls new file mode 100644 index 000000000..6abb53aa1 --- /dev/null +++ b/design-documents/graph-ql/coverage/CartAddressOperations.graphqls @@ -0,0 +1,110 @@ +type Query { + setBillingAddressOnCart(input: SetBillingAddressOnCartInput): SetBillingAddressOnCartOutput + setShippingAddressesOnCart(input: SetShippingAddressesOnCartInput): SetShippingAddressesOnCartOutput + setShippingMethodsOnCart(input: SetShippingMethodsOnCartInput): SetShippingMethodsOnCartOutput +} + +input SetShippingMethodsOnCartInput { + shipping_methods: [ShippingMethodForAddressInput!]! +} + +input ShippingMethodForAddressInput { + cart_address_id: String! + shipping_method_code: String! +} + +input SetBillingAddressOnCartInput { + customer_address_id: Int + address: CartAddressInput +} + +input SetShippingAddressesOnCartInput { + customer_address_id: Int # Can be provided in one-page checkout and is required for multi-shipping checkout + address: CartAddressInput + cart_items: [CartItemQuantityInput!] +} + +input CartItemQuantityInput { + cart_item_id: String! + quantity: Float! +} + +input CartAddressInput { + firstname: String! + lastname: String! + company: String + street: [String!]! + city: String! + region: String + postcode: String + country_code: String! + telephone: String! + save_in_address_book: Boolean! +} + +type SetShippingAddressesOnCartOutput { + cart: Cart! +} + +type SetShippingMethodsOnCartOutput { + cart: Cart! +} + +type SetBillingAddressOnCartOutput { + cart: Cart! +} + +type Cart { + addresses: [CartAddress]! +} + +type CartAddress { + firstname: String! + lastname: String! + company: String + street: [String!]! + city: String! + region: CartAddressRegion + postcode: String + country: CartAddressCountry! + telephone: String! + address_type: AdressTypeEnum! + + selected_shipping_method: CheckoutShippingMethod + available_shipping_methods: [CheckoutShippingMethod]! + + items_weight: Float + customer_notes: String + gift_cards_amount_used: Money + applied_gift_cards: [CartGiftCard] + + cart_items: [CartItemQuantity] +} + +type CartItemQuantity { + cart_item_id: String! + quantity: Float! +} + +type CartAddressCountry { + code: String + label: String +} + +type CartAddressRegion { + code: String + label: String +} + +enum AdressTypeEnum { + SHIPPING + BILLING +} + +type CheckoutShippingMethod { + code: String + label: String + free_shipping: Boolean! + error_message: String + # TODO: Add more complex structure for shipping rates +} diff --git a/design-documents/graph-ql/coverage/CartPrices.graphqls b/design-documents/graph-ql/coverage/CartPrices.graphqls new file mode 100644 index 000000000..2c6ed116f --- /dev/null +++ b/design-documents/graph-ql/coverage/CartPrices.graphqls @@ -0,0 +1,55 @@ +interface CartItemInterface { + prices: CartItemPrices +} + +type Cart { + prices: CartPrices +} + +type CartAddress { + prices: CartAddressPrices + # additional fields will be added later +} + +interface CartPricesInterface { + grand_total: Money + + # price display settings should be requested via store config query + subtotal_including_tax: Money + subtotal_excluding_tax: Money + + subtotal_with_discount_excluding_tax: Money + discount_tax_compensation: Money #Should we have subtotal with discount including tax instead, is it different from grand_total? + + applied_taxes: [CartTaxItem]! # Should include regular taxes and WEEE taxes + applied_discounts: [CartDiscountItem]! +} + +type CartItemPrices implements CartPricesInterface { + price_including_tax: Money + price_excluding_tax: Money + + custom_price: Money +} + +type CartAddressPrices implements CartPricesInterface { + + shipping_including_tax: Money + shipping_excluding_tax: Money + + shipping_discount: Money # Do we need shipping_with_discount_including_tax? + shipping_discount_tax_compensation: Money +} + +type CartPrices implements CartPricesInterface { +} + +type CartTaxItem { + amount: Money! + label: String! +} + +type CartDiscountItem { + amount: Money! + label: String! +} diff --git a/design-documents/graph-ql/coverage/CouponOperations.graphqls b/design-documents/graph-ql/coverage/CouponOperations.graphqls new file mode 100644 index 000000000..186218fe7 --- /dev/null +++ b/design-documents/graph-ql/coverage/CouponOperations.graphqls @@ -0,0 +1,34 @@ +type Mutation { + applyCouponToCart(input: ApplyCouponToCartInput): ApplyCouponToCartOutput + removeCouponFromCart(input: RemoveCouponFromCartInput): RemoveCouponFromCartOutput +} + +input ApplyCouponToCartInput { + cart_id: String! + coupon_code: String! +} + +type ApplyCouponToCartOutput { + cart: Cart! +} + +type Cart { + applied_coupon: AppliedCoupon +} + +type CartAddress { + applied_coupon: AppliedCoupon +} + +type AppliedCoupon { + # Wrapper allows for future extension of coupon info + code: String! +} + +input RemoveCouponFromCartInput { + cart_id: String! +} + +type RemoveCouponFromCartOutput { + cart: Cart +} diff --git a/design-documents/graph-ql/coverage/add-items-to-cart.md b/design-documents/graph-ql/coverage/add-items-to-cart.md new file mode 100644 index 000000000..550948628 --- /dev/null +++ b/design-documents/graph-ql/coverage/add-items-to-cart.md @@ -0,0 +1,45 @@ +**Overview** + +As a Magento developer, I need to manipulate the shopping cart via GraphQL so that I can programmatically create orders on behalf of a shopper. + +GraphQL needs to provide sufficient mutations (ways to create/update/delete data) for a developer to build out the storefront checkout experience for a shopper. + +**Use cases:** +- Both guest and registered shoppers can add new items to cart +- Both guest and registered shoppers can update item qty in cart +- Both guest and registered shoppers can remove items from cart +- Both guest and registered shoppers can update the configuration (for a configurable product) or quantity of a previously added configurable product in cart + - Edit Item link > Product page > Update configuration or qty > Update Cart + +**Main decision points:** + +- Separate mutations for each product type while adding items to cart. Each operation will be supporting bulk use case +- Uniform interface for guest vs customer +- Separate mutations for each checkout step + - Create empty cart + - Add items to cart + - Set shipment method + - Set payment method + - Set addresses + - Same granularity for updates and removals +- Possibility to combine mutations for checkout steps +- Can create "order in one call" mutation in the future if needed +- Hashed IDs for cart items +- Single input object +- Async nature of the flow must be supported on the client side (via AJAX calls) +- Server-side asynchronous mutations can be supported in the future on framework level in a way similar to Async REST. + +**Proposed schema for adding items to cart:** + +- [AddSimpleProductToCart](AddSimpleProductToCart.graphqls) +- [AddBundleProductToCart](AddBundleProductToCart.graphqls) +- [AddConfigurableProductToCart](AddConfigurableProductToCart.graphqls) +- [AddDownloadableProductToCart](AddDownloadableProductToCart.graphqls) +- [AddGiftCardProductToCart](AddGiftCardProductToCart.graphqls) +- [AddGroupedProductToCart](AddGroupedProductToCart.graphqls) +- [AddVirtualProductToCart](AddVirtualProductToCart.graphqls) + + +**My Account area impacted:** +- Cart +- Minicart diff --git a/design-documents/graph-ql/coverage/add-items-to-cart/AddBundleProductToCart.graphqls b/design-documents/graph-ql/coverage/add-items-to-cart/AddBundleProductToCart.graphqls new file mode 100644 index 000000000..e92f12223 --- /dev/null +++ b/design-documents/graph-ql/coverage/add-items-to-cart/AddBundleProductToCart.graphqls @@ -0,0 +1,59 @@ +type Mutation { + addBundleProductsToCart(input: AddBundleProductsToCartInput): AddBundleProductsToCartOutput + updateBundleProductsInCart(input: UpdateBundleProductsInCartInput): UpdateBundleProductsInCartOutput +} + +input UpdateBundleProductsInCartInput { + cart_id: String! + cartItems: [UpdateBundleProductCartItemInput!]! +} + +input UpdateBundleProductCartItemInput { + details: UpdateCartItemDetailsInput! + bundle_options:[BundleOptionInput!] + customizable_options:[CustomizableOptionInput] +} + +input AddBundleProductsToCartInput { + cart_id: String! + cartItems: [BundleProductCartItemInput!]! +} + +input BundleProductCartItemInput { + details: CartItemDetailsInput! + bundle_options:[BundleOptionInput!]! + customizable_options:[CustomizableOptionInput!] +} + +input BundleOptionInput { + id: Int! + quantity: Float! + value: [String!]! +} + +type AddBundleProductsToCartOutput { + cart: Cart! +} + +type BundleCartItem implements CartItemInterface { + customizable_options: [SelectedCustomizableOption]! + bundle_options: [SelectedBundleOption!]! +} + +type SelectedBundleOption { + id: Int! + label: String! + type: String! + # No quantity here even though it is set on option level in the input + values: [SelectedBundleOptionValue!]! + sort_order: Int! +} + +type SelectedBundleOptionValue { + id: Int! + label: String! + quantity: Float! # Quantity is displayed on option value level, while is set on option level + price: CartItemSelectedOptionValuePrice! + sort_order: Int! +} + diff --git a/design-documents/graph-ql/coverage/add-items-to-cart/AddConfigurableProductToCart.graphqls b/design-documents/graph-ql/coverage/add-items-to-cart/AddConfigurableProductToCart.graphqls new file mode 100644 index 000000000..7dc069af0 --- /dev/null +++ b/design-documents/graph-ql/coverage/add-items-to-cart/AddConfigurableProductToCart.graphqls @@ -0,0 +1,43 @@ +type Mutation { + addConfigurableProductsToCart(input: AddConfigurableProductsToCartInput): AddConfigurableProductsToCartOutput + updateConfigurableProductsInCart(input: UpdateConfigurableProductsInCartInput): UpdateConfigurableProductsInCartOutput +} + +input UpdateConfigurableProductsInCartInput { + cart_id: String! + cartItems: [UpdateConfigurableProductCartItemInput!]! +} + +input UpdateConfigurableProductCartItemInput { + details: UpdateCartItemDetailsInput! + variant_sku: String + customizable_options:[CustomizableOptionInput] +} + +input AddConfigurableProductsToCartInput { + cart_id: String! + cartItems: [ConfigurableProductCartItemInput!]! +} + +input ConfigurableProductCartItemInput { + details: CartItemDetailsInput! + variant_sku: String! + customizable_options:[CustomizableOptionInput!] +} + +type AddConfigurableProductsToCartOutput { + cart: Cart! +} + +type ConfigurableCartItem implements CartItemInterface { + customizable_options: [SelectedCustomizableOption]! + configurable_options: [SelectedConfigurableOption!]! +} + +type SelectedConfigurableOption { + id: Int! + option_label: String! + value_id: Int! + value_label: String! +} + diff --git a/design-documents/graph-ql/coverage/add-items-to-cart/AddDownloadableProductToCart.graphqls b/design-documents/graph-ql/coverage/add-items-to-cart/AddDownloadableProductToCart.graphqls new file mode 100644 index 000000000..5ac44c289 --- /dev/null +++ b/design-documents/graph-ql/coverage/add-items-to-cart/AddDownloadableProductToCart.graphqls @@ -0,0 +1,45 @@ +type Mutation { + addDownloadableProductsToCart(input: AddDownloadableProductsToCartInput): AddDownloadableProductsToCartOutput + updateDownloadableProductsInCart(input: UpdateDownloadableProductsInCartInput): UpdateDownloadableProductsInCartOutput +} + +input UpdateDownloadableProductsInCartInput { + cart_id: String! + cartItems: [UpdateDownloadableProductCartItemInput!]! +} + +input UpdateDownloadableProductCartItemInput { + details: UpdateCartItemDetailsInput! + downloadable_links: [DownloadableLinksInput] + customizable_options:[CustomizableOptionInput] +} + +input AddDownloadableProductsToCartInput { + cart_id: String! + cartItems: [DownloadableProductCartItemInput!]! +} + +input DownloadableProductCartItemInput { + details: CartItemDetailsInput! + downloadable_links: [DownloadableLinksInput!] + customizable_options:[CustomizableOptionInput!] +} + +input DownloadableLinksInput { + id: [Int!]! +} + +type AddDownloadableProductsToCartOutput { + cart: Cart! +} + +type DownloadableCartItem implements CartItemInterface { + links_label: String! + links: [DownloadableCartItemLink!]! + configurable_options: [SelectedConfigurableOption!]! +} + +type DownloadableCartItemLink { + id: Int! + label: String! +} diff --git a/design-documents/graph-ql/coverage/add-items-to-cart/AddGiftCardProductToCart.graphqls b/design-documents/graph-ql/coverage/add-items-to-cart/AddGiftCardProductToCart.graphqls new file mode 100644 index 000000000..92ae1ddb6 --- /dev/null +++ b/design-documents/graph-ql/coverage/add-items-to-cart/AddGiftCardProductToCart.graphqls @@ -0,0 +1,49 @@ +type Mutation { + addGiftCardProductsToCart(input: AddGiftCardProductsToCartInput): AddGiftCardProductsToCartOutput + updateGiftCardProductsInCart(input: UpdateGiftCardProductsInCartInput): UpdateGiftCardProductsInCartOutput +} + +input UpdateGiftCardProductsInCartInput { + cart_id: String! + cartItems: [UpdateGiftCardProductCartItemInput!]! +} + +input UpdateGiftCardProductCartItemInput { + details: UpdateCartItemDetailsInput! + sender_name: String + recepient_name: String + amount: MoneyInput # Do we need complex type here or just Float? + message: String + customizable_options:[CustomizableOptionInput] +} + +input AddGiftCardProductsToCartInput { + cart_id: String! + cartItems: [GiftCardProductCartItemInput!]! +} + +input GiftCardProductCartItemInput { + details: CartItemDetailsInput! + sender_name: String! + recepient_name: String! + amount: MoneyInput! # Do we need complex type here or just Float? + message: String + customizable_options:[CustomizableOptionInput!] +} + +input MoneyInput { + value: Float @doc(description: "A number expressing a monetary value") + currency: CurrencyEnum @doc(description: "A three-letter currency code, such as USD or EUR") +} + +type AddGiftCardProductsToCartOutput { + cart: Cart! +} + +type GiftCardCartItem implements CartItemInterface { + sender_name: String! + recepient_name: String! + amount: Money! + message: String + customizable_options: [SelectedCustomizableOption]! +} diff --git a/design-documents/graph-ql/coverage/add-items-to-cart/AddGroupedProductToCart.graphqls b/design-documents/graph-ql/coverage/add-items-to-cart/AddGroupedProductToCart.graphqls new file mode 100644 index 000000000..38f97184f --- /dev/null +++ b/design-documents/graph-ql/coverage/add-items-to-cart/AddGroupedProductToCart.graphqls @@ -0,0 +1,27 @@ +type Mutation { + addGroupedProductsToCart(input: AddGroupedProductsToCartInput): AddGroupedProductsToCartOutput + updateGroupedProductsInCart(input: UpdateGroupedProductsInCartInput): UpdateGroupedProductsInCartOutput +} + +input UpdateGroupedProductsInCartInput { + cart_id: String! + cartItems: [UpdateGroupedProductCartItemInput!]! +} + +input UpdateGroupedProductCartItemInput { + details: UpdateCartItemDetailsInput! +} + +input AddGroupedProductsToCartInput { + cart_id: String! + cartItems: [GroupedProductCartItemInput!]! +} + +input GroupedProductCartItemInput { + details: CartItemDetailsInput! + # the difference from simple products is that grouped products do not support customizable options +} + +type AddGroupedProductsToCartOutput { + cart: Cart! +} diff --git a/design-documents/graph-ql/coverage/add-items-to-cart/AddSimpleProductToCart.graphqls b/design-documents/graph-ql/coverage/add-items-to-cart/AddSimpleProductToCart.graphqls new file mode 100644 index 000000000..8a3d6963f --- /dev/null +++ b/design-documents/graph-ql/coverage/add-items-to-cart/AddSimpleProductToCart.graphqls @@ -0,0 +1,78 @@ +type Mutation { + addSimpleProductsToCart(input: AddSimpleProductsToCartInput): AddSimpleProductsToCartOutput + updateSimpleProductsInCart(input: UpdateSimpleProductsInCartInput): UpdateSimpleProductsInCartOutput +} + +input UpdateSimpleProductsInCartInput { + cart_id: String! + cartItems: [UpdateSimpleProductCartItemInput!]! +} + +input UpdateSimpleProductCartItemInput { + details: UpdateCartItemDetailsInput! + customizable_options:[CustomizableOptionInput] +} + +input AddSimpleProductsToCartInput { + cart_id: String! + cartItems: [SimpleProductCartItemInput!]! +} + +input SimpleProductCartItemInput { + details: CartItemDetailsInput! + customizable_options:[CustomizableOptionInput!] +} + +input CartItemDetailsInput { + sku: String! + quantity: Float! +} + +input UpdateCartItemDetailsInput { + cart_item_id: String! + quantity: Float +} + +input CustomizableOptionInput { + id: Int! + value: String! +} + +type AddSimpleProductsToCartOutput { + cart: Cart! +} + +type Cart { + items: [CartItemInterface] +} + +interface CartItemInterface @typeResolver(class: "Magento\\CatalogCheckoutGraphQl\\Model\\CartItemInterfaceTypeResolverComposite") { + id: String! # Hashed cart item ID + qty: Float! + product: ProductInterface! +} + +type SimpleCartItem implements CartItemInterface { + customizable_options: [SelectedCustomizableOption] +} + +type SelectedCustomizableOption { + id: Int! + label: String! + type: String! + values: [SelectedCustomizableOptionValue!]! + sort_order: Int! +} + +type SelectedCustomizableOptionValue { + id: Int + label: String! + price: CartItemSelectedOptionValuePrice! + sort_order: Int! +} + +type CartItemSelectedOptionValuePrice { + value: Float! + units: String! + type: PriceTypeEnum! +} diff --git a/design-documents/graph-ql/coverage/add-items-to-cart/AddVirtualProductToCart.graphqls b/design-documents/graph-ql/coverage/add-items-to-cart/AddVirtualProductToCart.graphqls new file mode 100644 index 000000000..76013ada1 --- /dev/null +++ b/design-documents/graph-ql/coverage/add-items-to-cart/AddVirtualProductToCart.graphqls @@ -0,0 +1,34 @@ +type Mutation { + # for now this mutation is identical to addSimpleProductsToCart and exists as a syntax sugar. Also it allows product type based customizations + addVirtualProductsToCart(input: AddVirtualProductsToCartInput): AddVirtualProductsToCartOutput + updateVirtualProductsInCart(input: UpdateVirtualProductsInCartInput): UpdateVirtualProductsInCartOutput +} + +input UpdateVirtualProductsInCartInput { + cart_id: String! + cartItems: [UpdateVirtualProductCartItemInput!]! +} + +input UpdateVirtualProductCartItemInput { + details: UpdateCartItemDetailsInput! + customizable_options:[CustomizableOptionInput] +} + +input AddVirtualProductsToCartInput { + cart_id: String! + cartItems: [VirtualProductCartItemInput!]! +} + +input VirtualProductCartItemInput { + details: CartItemDetailsInput! + customizable_options:[CustomizableOptionInput!] +} + +type AddVirtualProductsToCartOutput { + cart: Cart! +} + +# Custom cart item type can be used to customize rendering when there are no physical producs available, e.g. skip shipping +type VirtualCartItem implements CartItemInterface { + customizable_options: [SelectedCustomizableOption] +} diff --git a/design-documents/graph-ql/coverage/add-items-to-cart/RemoveItemFromCart.graphqls b/design-documents/graph-ql/coverage/add-items-to-cart/RemoveItemFromCart.graphqls new file mode 100644 index 000000000..f1039fc86 --- /dev/null +++ b/design-documents/graph-ql/coverage/add-items-to-cart/RemoveItemFromCart.graphqls @@ -0,0 +1,12 @@ +type Mutation { + removeItemFromCart(input: RemoveItemFromCartInput): RemoveItemFromCartOutput +} + +input RemoveItemFromCartInput { + cart_id: String! + cart_item_id: String! +} + +type RemoveItemFromCartOutput { + cart: Cart! +} diff --git a/design-documents/graph-ql/coverage/input-optput-extensibility.md b/design-documents/graph-ql/coverage/input-optput-extensibility.md new file mode 100644 index 000000000..c86805e15 --- /dev/null +++ b/design-documents/graph-ql/coverage/input-optput-extensibility.md @@ -0,0 +1,64 @@ +**Problem statement** + +Current way of declaring queries and mutations limits extensibility and deprecation capabilities. + +```$graphqls +type Mutation { + generateCustomerToken( + email: String!, + password: String! + ): String! +} +``` + +Such declaration has several issues: +- It is not possible to extend or modify the list of arguments from 3rd party extension. Schema stitching mechanism performs merge on types level only +- There is no way to add extra data to the output of the mutation in a backward compatible way +- It is not possible to evolve arguments list and output type of our API in the future. Even if we decide to do breaking changes, there is no way to deprecate existing arguments and output data first + + +**Proposed solution: Wrappers for output and merger for arguments** + +Wrappers for output type along with merging capabilities for arguments can solve extensibility and deprecation issues. + +```$graphqls +type Mutation { + generateCustomerToken( + email: String!, + password: String! + ): GenerateCustomerTokenOutput! +} + +type GenerateCustomerTokenOutput { + token: String! +} +``` + +With such schema it is possible to extend the list of arguments (not reduce, however). For example, if system integrator got a new requirement to enable multi-factor authentication, the schema can be extended from 3rd party module to support this requirement as follows. All arguments from different modules will be merged and the resulting schema will contain all of them. + +```$graphqls +type Mutation { + generateCustomerToken( + email: String!, + password: String!, + multi_factor_auth_token: String! + ): GenerateCustomerTokenOutput! +} +``` +Additionally, plugin can be added for the `generateCustomerToken` mutation resolver to implement additional verification step. + +Now let's assume that client app needs to know when to request a new token. One of the solutions could be to return token TTL along with its value. +The output data can be extended in this case as follows: +```$graphqls +input GenerateCustomerTokenOutput { + token_ttl: String! +} +``` +The `token_ttl` value can be populated via new resolver for this field or from the plugin on existing `generateCustomerToken` mutation resolver. + + +**Action items** + +1. Modify schema for all existing queries and mutations to use wrappers as output types +1. Make sure that this requirement is documented and followed for the new GraphQL coverage +1. Implement merging of arguments for the same query/mutation defined in different modules. GraphQL functional tests should be added diff --git a/design-documents/graph-ql/framework/resolver-output-type.md b/design-documents/graph-ql/framework/resolver-output-type.md new file mode 100644 index 000000000..151f7e02a --- /dev/null +++ b/design-documents/graph-ql/framework/resolver-output-type.md @@ -0,0 +1,83 @@ +**Problem statement** + +All resolvers in Magento must implement Resolver interface, which in its turn forces the instance of `\GraphQL\Deferred` to be returned by each resolver. To be precise, Magento wrapper for *deferred*, `\Magento\Framework\GraphQl\Query\Resolver\Value`, must be returned. + +```php +/** + * Fetches the data from persistence models and format it according to the GraphQL schema. + * + * @param \Magento\Framework\GraphQl\Config\Element\Field $field + * @param $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @throws \Exception + * @return Value + */ +public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null +) : Value; +``` + +The main reason for using *deferred* in GraphQL is to optimize performance by solving [N+1 problem](http://webonyx.github.io/graphql-php/data-fetching/#solving-n1-problem) where it exists. + +An example of valid *deferred* usage in Magento can be found in [`\Magento\CatalogGraphQl\Model\Resolver\Product::resolve`](https://github.com/magento/graphql-ce/blob/5a570b8b674b04eb36930aa73f92d1e789b8843a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product.php#L57). + +In all other cases, when there is no need to solve N+1 problem, we end up with boilerplate code like: +```php +$result = function () use ($data) { + return $data; +}; + +return $this->valueFactory->create($result); +``` + +Response type unification is one of the reasons why *deferred* was made the only possible return type for resolvers. However, after implementing more resolvers it became obvious that current design just adds unnecessary complexity to the resolver implementations. + +**Proposed solution** + +In addition to *deferred*, allow scalars and arrays of scalars as return types for `\Magento\CatalogGraphQl\Model\Resolver\Product::resolve`. + +It will also be easier to customize resolver with plugins, if it returns array/scalar instead of `deferred` object. + +**Action items** + +1. Modify existing resolvers, which have boilerplate code and do not require solving N+1 problem + + Change + ```php + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) : Value; + ``` + to + ```php + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ); + ``` + Also, replace boilerplate code in resolvers + ```php + $result = function () use ($data) { + return $data; + }; + + return $this->valueFactory->create($result); + ``` + with + ```php + return $data; + ``` +1. Document the decision and make sure that all new resolvers return *deferred* only when necessary