vuestorefront / vue-storefront

Alokai is a Frontend as a Service solution that simplifies composable commerce. It connects all the technologies needed to build and deploy fast & scalable ecommerce frontends. It guides merchants to deliver exceptional customer experiences quickly and easily.
https://www.alokai.com
MIT License
10.6k stars 2.08k forks source link

Composables extendibility #4704

Closed filrak closed 3 years ago

filrak commented 4 years ago

Motivation

Its straightforward to modify the composables output but here are some situations when we have to change composables internals. For example we would like to change the GQL query or whole API Client method. This RFC describes extensibility mechanism that allows integration creators to define extension points within their composables.

Having extendibility in composables enables extension scoping which is a very useful feature.

User API

In a project users can use override method available in every composable to override predefined extension points:

const { search, product, override } = useProduct()

// for each composable user can extend parts defined in "extendable" object via "extend" method
// now instead of invoking "apiGetProduct" in search we will invoke console.log
override.api.getProduct(() => console.log('I am extended!'))

search() // "I am extended!"

Integrator API

For the integrator all extension points will be defined in extensionPoints property of factoryParams and then accessible from every function as its last argument

For example this is how we can: make API endpoint as extension point:

import { getProduct } from '@vue-storefront/commercetools'

const factoryParams = {
    extensionPoints: {
       api: {
         getProduct
       }
    },
    productsSearch: async ({ params, extensionPoints }) {
       const response = await extensionPoints.api.getProduct(params)
       return response
   }
}
const useProduct = useProductFactory<any, any>(factoryParams)

Other examples

// integration
import { getProduct, defaultQuery } from '@vue-storefront/commercetools'

const factoryParams = {
    extensionPoints: {
       api: {
         getProduct {
           fn: getProduct,
           query: defaultQuery
         }
       }
    },
    productsSearch: async ({ params, extensionPoints }) {
       const response = await extensionPoints.api.getProduct.fn(params, extensionPoints.api.getProduct.query)
       return response
   }
}
// project
const { search, product, override } = useProduct()

override.api.getProduct.query = require('./queries/customProductQuery.gql`)

Moreover every action from factoryParams can be overriden as well via override.actions object:

// project
override.actions.productSearch = () => console.log('I do nothing for living')

What should be extendable "by default"

I think at a bare minimum every integration should enable extensibility for

Please let me know about your opinions ;)

┆Issue is synchronized with this Jira Story by Unito

lukeromanowicz commented 4 years ago

The problem of extension points is that they make the code very ugly and slightly impact the performance. I'm of the opinion that VSF should be lightweight and simple set of tools that you can build your website with. If something is not flexible enough then feel free to replace it (which is super easy). We don't need to have every feature there is in e-commerce because many simply won't use it.

Also keep in mind that even if we add extension points, there always will be something that at the moment is not flexible enough and overriding that composable will be inevitable.

filrak commented 4 years ago

@lukeromanowicz you're absolutely right that being too extensible is not always a good idea especially when composables are already very granular. There are some granular and common use cases for overriding where replacing whole composable seems to be a bit too much though and it would be great to cover that. I'm talking mainly about extending GQL queries but also integrating with 3rd party services for carts etc (cart object sometimes has to be modified from other composables and it would be nice being able to override just the ones that are using default cart object with a new one but perhaps we can come up with something more elegant here like a single source of truth for setCart that can be overwritten)

Instead of extensionPoints we can provide something more opinionated and restrict it to api and gql object


const factoryParams = {
    api: { getProduct },
    gql: { getProduct },
    productsSearch: async ({ params, api, gql }) {
       const response = await api.getProduct(params, gql.getProduct)
       return response
   }
}